Work around an issue with expanded menus that exists for a long time already. I briefly tried fixing it, but does not seem straight forward unfortunately without breaking stuff. Also see the comment in `ui_item_enum_expand_exec`.
3107 lines
115 KiB
C++
3107 lines
115 KiB
C++
/* SPDX-FileCopyrightText: 2005 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup modifiers
|
|
*/
|
|
|
|
#include <cstring>
|
|
#include <fmt/format.h>
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_multi_value_map.hh"
|
|
#include "BLI_set.hh"
|
|
#include "BLI_string.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "DNA_array_utils.hh"
|
|
#include "DNA_collection_types.h"
|
|
#include "DNA_curves_types.h"
|
|
#include "DNA_defaults.h"
|
|
#include "DNA_material_types.h"
|
|
#include "DNA_mesh_types.h"
|
|
#include "DNA_modifier_types.h"
|
|
#include "DNA_node_types.h"
|
|
#include "DNA_object_types.h"
|
|
#include "DNA_pointcloud_types.h"
|
|
#include "DNA_scene_types.h"
|
|
#include "DNA_screen_types.h"
|
|
#include "DNA_space_types.h"
|
|
#include "DNA_view3d_types.h"
|
|
#include "DNA_windowmanager_types.h"
|
|
|
|
#include "BKE_bake_data_block_map.hh"
|
|
#include "BKE_bake_geometry_nodes_modifier.hh"
|
|
#include "BKE_compute_context_cache.hh"
|
|
#include "BKE_compute_contexts.hh"
|
|
#include "BKE_customdata.hh"
|
|
#include "BKE_global.hh"
|
|
#include "BKE_idprop.hh"
|
|
#include "BKE_lib_id.hh"
|
|
#include "BKE_lib_query.hh"
|
|
#include "BKE_main.hh"
|
|
#include "BKE_mesh.hh"
|
|
#include "BKE_modifier.hh"
|
|
#include "BKE_node_legacy_types.hh"
|
|
#include "BKE_node_runtime.hh"
|
|
#include "BKE_node_tree_update.hh"
|
|
#include "BKE_object.hh"
|
|
#include "BKE_packedFile.hh"
|
|
#include "BKE_pointcloud.hh"
|
|
#include "BKE_screen.hh"
|
|
#include "BKE_workspace.hh"
|
|
|
|
#include "BLO_read_write.hh"
|
|
|
|
#include "UI_interface.hh"
|
|
#include "UI_resources.hh"
|
|
|
|
#include "BLT_translation.hh"
|
|
|
|
#include "WM_api.hh"
|
|
#include "WM_types.hh"
|
|
|
|
#include "RNA_access.hh"
|
|
#include "RNA_enum_types.hh"
|
|
#include "RNA_prototypes.hh"
|
|
|
|
#include "DEG_depsgraph_build.hh"
|
|
#include "DEG_depsgraph_query.hh"
|
|
#include "DEG_depsgraph_writeback_sync.hh"
|
|
|
|
#include "MOD_modifiertypes.hh"
|
|
#include "MOD_nodes.hh"
|
|
#include "MOD_ui_common.hh"
|
|
|
|
#include "ED_node.hh"
|
|
#include "ED_object.hh"
|
|
#include "ED_screen.hh"
|
|
#include "ED_undo.hh"
|
|
#include "ED_viewer_path.hh"
|
|
|
|
#include "NOD_geometry.hh"
|
|
#include "NOD_geometry_nodes_dependencies.hh"
|
|
#include "NOD_geometry_nodes_execute.hh"
|
|
#include "NOD_geometry_nodes_gizmos.hh"
|
|
#include "NOD_geometry_nodes_lazy_function.hh"
|
|
#include "NOD_node_declaration.hh"
|
|
#include "NOD_socket_usage_inference.hh"
|
|
|
|
namespace lf = blender::fn::lazy_function;
|
|
namespace geo_log = blender::nodes::geo_eval_log;
|
|
namespace bake = blender::bke::bake;
|
|
|
|
namespace blender {
|
|
|
|
static void init_data(ModifierData *md)
|
|
{
|
|
NodesModifierData *nmd = (NodesModifierData *)md;
|
|
|
|
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(nmd, modifier));
|
|
nmd->modifier.layout_panel_open_flag |= 1 << NODES_MODIFIER_PANEL_WARNINGS;
|
|
|
|
MEMCPY_STRUCT_AFTER(nmd, DNA_struct_default_get(NodesModifierData), modifier);
|
|
nmd->runtime = MEM_new<NodesModifierRuntime>(__func__);
|
|
nmd->runtime->cache = std::make_shared<bake::ModifierCache>();
|
|
}
|
|
|
|
static void find_dependencies_from_settings(const NodesModifierSettings &settings,
|
|
nodes::GeometryNodesEvalDependencies &deps)
|
|
{
|
|
IDP_foreach_property(settings.properties, IDP_TYPE_FILTER_ID, [&](IDProperty *property) {
|
|
if (ID *id = IDP_Id(property)) {
|
|
deps.add_generic_id_full(id);
|
|
}
|
|
});
|
|
}
|
|
|
|
/* We don't know exactly what attributes from the other object we will need. */
|
|
static const CustomData_MeshMasks dependency_data_mask{CD_MASK_PROP_ALL | CD_MASK_MDEFORMVERT,
|
|
CD_MASK_PROP_ALL,
|
|
CD_MASK_PROP_ALL,
|
|
CD_MASK_PROP_ALL,
|
|
CD_MASK_PROP_ALL};
|
|
|
|
static void add_collection_relation(const ModifierUpdateDepsgraphContext *ctx,
|
|
Collection &collection)
|
|
{
|
|
DEG_add_collection_geometry_relation(ctx->node, &collection, "Nodes Modifier");
|
|
DEG_add_collection_geometry_customdata_mask(ctx->node, &collection, &dependency_data_mask);
|
|
}
|
|
|
|
static void add_object_relation(
|
|
const ModifierUpdateDepsgraphContext *ctx,
|
|
Object &object,
|
|
const nodes::GeometryNodesEvalDependencies::ObjectDependencyInfo &info)
|
|
{
|
|
if (info.transform) {
|
|
DEG_add_object_relation(ctx->node, &object, DEG_OB_COMP_TRANSFORM, "Nodes Modifier");
|
|
}
|
|
if (&(ID &)object == &ctx->object->id) {
|
|
return;
|
|
}
|
|
if (info.geometry) {
|
|
if (object.type == OB_EMPTY && object.instance_collection != nullptr) {
|
|
add_collection_relation(ctx, *object.instance_collection);
|
|
}
|
|
else if (DEG_object_has_geometry_component(&object)) {
|
|
DEG_add_object_relation(ctx->node, &object, DEG_OB_COMP_GEOMETRY, "Nodes Modifier");
|
|
DEG_add_customdata_mask(ctx->node, &object, &dependency_data_mask);
|
|
}
|
|
}
|
|
if (object.type == OB_CAMERA) {
|
|
if (info.camera_parameters) {
|
|
DEG_add_object_relation(ctx->node, &object, DEG_OB_COMP_PARAMETERS, "Nodes Modifier");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void update_depsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx)
|
|
{
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
if (nmd->node_group == nullptr) {
|
|
return;
|
|
}
|
|
if (ID_MISSING(nmd->node_group)) {
|
|
return;
|
|
}
|
|
|
|
DEG_add_node_tree_output_relation(ctx->node, nmd->node_group, "Nodes Modifier");
|
|
|
|
nodes::GeometryNodesEvalDependencies eval_deps =
|
|
nodes::gather_geometry_nodes_eval_dependencies_recursive(*nmd->node_group);
|
|
|
|
/* Create dependencies to data-blocks referenced by the settings in the modifier. */
|
|
find_dependencies_from_settings(nmd->settings, eval_deps);
|
|
|
|
if (ctx->object->type == OB_CURVES) {
|
|
Curves *curves_id = static_cast<Curves *>(ctx->object->data);
|
|
if (curves_id->surface != nullptr) {
|
|
eval_deps.add_object(curves_id->surface);
|
|
}
|
|
}
|
|
|
|
for (const NodesModifierBake &bake : Span(nmd->bakes, nmd->bakes_num)) {
|
|
for (const NodesModifierDataBlock &data_block : Span(bake.data_blocks, bake.data_blocks_num)) {
|
|
if (data_block.id) {
|
|
eval_deps.add_generic_id_full(data_block.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (ID *id : eval_deps.ids.values()) {
|
|
switch ((ID_Type)GS(id->name)) {
|
|
case ID_OB: {
|
|
Object *object = reinterpret_cast<Object *>(id);
|
|
add_object_relation(
|
|
ctx, *object, eval_deps.objects_info.lookup_default(object->id.session_uid, {}));
|
|
break;
|
|
}
|
|
case ID_GR: {
|
|
Collection *collection = reinterpret_cast<Collection *>(id);
|
|
add_collection_relation(ctx, *collection);
|
|
break;
|
|
}
|
|
case ID_IM:
|
|
case ID_TE: {
|
|
DEG_add_generic_id_relation(ctx->node, id, "Nodes Modifier");
|
|
break;
|
|
}
|
|
default: {
|
|
/* Purposefully don't add relations for materials. While there are material sockets,
|
|
* the pointers are only passed around as handles rather than dereferenced. */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (eval_deps.needs_own_transform) {
|
|
DEG_add_depends_on_transform_relation(ctx->node, "Nodes Modifier");
|
|
}
|
|
if (eval_deps.needs_active_camera) {
|
|
DEG_add_scene_camera_relation(ctx->node, ctx->scene, DEG_OB_COMP_TRANSFORM, "Nodes Modifier");
|
|
}
|
|
/* Active camera is a scene parameter that can change, so we need a relation for that, too. */
|
|
if (eval_deps.needs_active_camera || eval_deps.needs_scene_render_params) {
|
|
DEG_add_scene_relation(ctx->node, ctx->scene, DEG_SCENE_COMP_PARAMETERS, "Nodes Modifier");
|
|
}
|
|
}
|
|
|
|
static bool depends_on_time(Scene * /*scene*/, ModifierData *md)
|
|
{
|
|
const NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
const bNodeTree *tree = nmd->node_group;
|
|
if (tree == nullptr) {
|
|
return false;
|
|
}
|
|
if (ID_MISSING(tree)) {
|
|
return false;
|
|
}
|
|
for (const NodesModifierBake &bake : Span(nmd->bakes, nmd->bakes_num)) {
|
|
if (bake.bake_mode == NODES_MODIFIER_BAKE_MODE_ANIMATION) {
|
|
return true;
|
|
}
|
|
}
|
|
nodes::GeometryNodesEvalDependencies eval_deps =
|
|
nodes::gather_geometry_nodes_eval_dependencies_recursive(*nmd->node_group);
|
|
return eval_deps.time_dependent;
|
|
}
|
|
|
|
static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void *user_data)
|
|
{
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
walk(user_data, ob, (ID **)&nmd->node_group, IDWALK_CB_USER);
|
|
|
|
IDP_foreach_property(nmd->settings.properties, IDP_TYPE_FILTER_ID, [&](IDProperty *id_prop) {
|
|
walk(user_data, ob, (ID **)&id_prop->data.pointer, IDWALK_CB_USER);
|
|
});
|
|
|
|
for (NodesModifierBake &bake : MutableSpan(nmd->bakes, nmd->bakes_num)) {
|
|
for (NodesModifierDataBlock &data_block : MutableSpan(bake.data_blocks, bake.data_blocks_num))
|
|
{
|
|
walk(user_data, ob, &data_block.id, IDWALK_CB_USER);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void foreach_tex_link(ModifierData *md, Object *ob, TexWalkFunc walk, void *user_data)
|
|
{
|
|
PointerRNA ptr = RNA_pointer_create_discrete(&ob->id, &RNA_Modifier, md);
|
|
PropertyRNA *prop = RNA_struct_find_property(&ptr, "texture");
|
|
walk(user_data, ob, md, &ptr, prop);
|
|
}
|
|
|
|
static bool is_disabled(const Scene * /*scene*/, ModifierData *md, bool /*use_render_params*/)
|
|
{
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
|
|
if (nmd->node_group == nullptr) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool logging_enabled(const ModifierEvalContext *ctx)
|
|
{
|
|
if (!DEG_is_active(ctx->depsgraph)) {
|
|
return false;
|
|
}
|
|
if ((ctx->flag & MOD_APPLY_ORCO) != 0) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void update_id_properties_from_node_group(NodesModifierData *nmd)
|
|
{
|
|
if (nmd->node_group == nullptr) {
|
|
if (nmd->settings.properties) {
|
|
IDP_FreeProperty(nmd->settings.properties);
|
|
nmd->settings.properties = nullptr;
|
|
}
|
|
return;
|
|
}
|
|
|
|
IDProperty *old_properties = nmd->settings.properties;
|
|
nmd->settings.properties = bke::idprop::create_group("Nodes Modifier Settings").release();
|
|
IDProperty *new_properties = nmd->settings.properties;
|
|
|
|
nodes::update_input_properties_from_node_tree(*nmd->node_group, old_properties, *new_properties);
|
|
nodes::update_output_properties_from_node_tree(
|
|
*nmd->node_group, old_properties, *new_properties);
|
|
|
|
if (old_properties != nullptr) {
|
|
IDP_FreeProperty(old_properties);
|
|
}
|
|
}
|
|
|
|
static void remove_outdated_bake_caches(NodesModifierData &nmd)
|
|
{
|
|
if (!nmd.runtime->cache) {
|
|
if (nmd.bakes_num == 0) {
|
|
return;
|
|
}
|
|
nmd.runtime->cache = std::make_shared<bake::ModifierCache>();
|
|
}
|
|
bake::ModifierCache &modifier_cache = *nmd.runtime->cache;
|
|
std::lock_guard lock{modifier_cache.mutex};
|
|
|
|
Set<int> existing_bake_ids;
|
|
for (const NodesModifierBake &bake : Span{nmd.bakes, nmd.bakes_num}) {
|
|
existing_bake_ids.add(bake.id);
|
|
}
|
|
|
|
auto remove_predicate = [&](auto item) { return !existing_bake_ids.contains(item.key); };
|
|
|
|
modifier_cache.bake_cache_by_id.remove_if(remove_predicate);
|
|
modifier_cache.simulation_cache_by_id.remove_if(remove_predicate);
|
|
}
|
|
|
|
static void update_bakes_from_node_group(NodesModifierData &nmd)
|
|
{
|
|
Map<int, NodesModifierBake *> old_bake_by_id;
|
|
for (NodesModifierBake &bake : MutableSpan(nmd.bakes, nmd.bakes_num)) {
|
|
old_bake_by_id.add(bake.id, &bake);
|
|
}
|
|
|
|
Vector<int> new_bake_ids;
|
|
if (nmd.node_group) {
|
|
for (const bNestedNodeRef &ref : nmd.node_group->nested_node_refs_span()) {
|
|
const bNode *node = nmd.node_group->find_nested_node(ref.id);
|
|
if (node) {
|
|
if (ELEM(node->type_legacy, GEO_NODE_SIMULATION_OUTPUT, GEO_NODE_BAKE)) {
|
|
new_bake_ids.append(ref.id);
|
|
}
|
|
}
|
|
else if (old_bake_by_id.contains(ref.id)) {
|
|
/* Keep baked data in case linked data is missing so that it still exists when the linked
|
|
* data has been found. */
|
|
new_bake_ids.append(ref.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
NodesModifierBake *new_bake_data = MEM_calloc_arrayN<NodesModifierBake>(new_bake_ids.size(),
|
|
__func__);
|
|
for (const int i : new_bake_ids.index_range()) {
|
|
const int id = new_bake_ids[i];
|
|
NodesModifierBake *old_bake = old_bake_by_id.lookup_default(id, nullptr);
|
|
NodesModifierBake &new_bake = new_bake_data[i];
|
|
if (old_bake) {
|
|
new_bake = *old_bake;
|
|
/* The ownership of this data was moved to `new_bake`. */
|
|
old_bake->directory = nullptr;
|
|
old_bake->data_blocks = nullptr;
|
|
old_bake->data_blocks_num = 0;
|
|
old_bake->packed = nullptr;
|
|
}
|
|
else {
|
|
new_bake.id = id;
|
|
new_bake.frame_start = 1;
|
|
new_bake.frame_end = 100;
|
|
new_bake.bake_mode = NODES_MODIFIER_BAKE_MODE_STILL;
|
|
}
|
|
}
|
|
|
|
for (NodesModifierBake &old_bake : MutableSpan(nmd.bakes, nmd.bakes_num)) {
|
|
nodes_modifier_bake_destruct(&old_bake, true);
|
|
}
|
|
MEM_SAFE_FREE(nmd.bakes);
|
|
|
|
nmd.bakes = new_bake_data;
|
|
nmd.bakes_num = new_bake_ids.size();
|
|
|
|
remove_outdated_bake_caches(nmd);
|
|
}
|
|
|
|
static void update_panels_from_node_group(NodesModifierData &nmd)
|
|
{
|
|
Map<int, NodesModifierPanel *> old_panel_by_id;
|
|
for (NodesModifierPanel &panel : MutableSpan(nmd.panels, nmd.panels_num)) {
|
|
old_panel_by_id.add(panel.id, &panel);
|
|
}
|
|
|
|
Vector<const bNodeTreeInterfacePanel *> interface_panels;
|
|
if (nmd.node_group) {
|
|
nmd.node_group->ensure_interface_cache();
|
|
nmd.node_group->tree_interface.foreach_item([&](const bNodeTreeInterfaceItem &item) {
|
|
if (item.item_type != NODE_INTERFACE_PANEL) {
|
|
return true;
|
|
}
|
|
interface_panels.append(reinterpret_cast<const bNodeTreeInterfacePanel *>(&item));
|
|
return true;
|
|
});
|
|
}
|
|
|
|
NodesModifierPanel *new_panels = MEM_calloc_arrayN<NodesModifierPanel>(interface_panels.size(),
|
|
__func__);
|
|
|
|
for (const int i : interface_panels.index_range()) {
|
|
const bNodeTreeInterfacePanel &interface_panel = *interface_panels[i];
|
|
const int id = interface_panel.identifier;
|
|
NodesModifierPanel *old_panel = old_panel_by_id.lookup_default(id, nullptr);
|
|
NodesModifierPanel &new_panel = new_panels[i];
|
|
if (old_panel) {
|
|
new_panel = *old_panel;
|
|
}
|
|
else {
|
|
new_panel.id = id;
|
|
const bool default_closed = interface_panel.flag & NODE_INTERFACE_PANEL_DEFAULT_CLOSED;
|
|
SET_FLAG_FROM_TEST(new_panel.flag, !default_closed, NODES_MODIFIER_PANEL_OPEN);
|
|
}
|
|
}
|
|
|
|
MEM_SAFE_FREE(nmd.panels);
|
|
|
|
nmd.panels = new_panels;
|
|
nmd.panels_num = interface_panels.size();
|
|
}
|
|
|
|
} // namespace blender
|
|
|
|
void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd)
|
|
{
|
|
using namespace blender;
|
|
update_id_properties_from_node_group(nmd);
|
|
update_bakes_from_node_group(*nmd);
|
|
update_panels_from_node_group(*nmd);
|
|
|
|
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
|
|
}
|
|
|
|
NodesModifierBake *NodesModifierData::find_bake(const int id)
|
|
{
|
|
return const_cast<NodesModifierBake *>(std::as_const(*this).find_bake(id));
|
|
}
|
|
|
|
const NodesModifierBake *NodesModifierData::find_bake(const int id) const
|
|
{
|
|
for (const NodesModifierBake &bake : blender::Span{this->bakes, this->bakes_num}) {
|
|
if (bake.id == id) {
|
|
return &bake;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
namespace blender {
|
|
|
|
/**
|
|
* Setup side effects nodes so that the given node in the given compute context will be executed.
|
|
* To make sure that it is executed, all parent group nodes and zones have to be set to have side
|
|
* effects as well.
|
|
*/
|
|
static void try_add_side_effect_node(const ModifierEvalContext &ctx,
|
|
const ComputeContext &final_compute_context,
|
|
const int final_node_id,
|
|
const NodesModifierData &nmd,
|
|
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes)
|
|
{
|
|
if (nmd.node_group == nullptr) {
|
|
return;
|
|
}
|
|
|
|
Vector<const ComputeContext *> compute_context_vec;
|
|
for (const ComputeContext *c = &final_compute_context; c; c = c->parent()) {
|
|
compute_context_vec.append(c);
|
|
}
|
|
std::reverse(compute_context_vec.begin(), compute_context_vec.end());
|
|
|
|
const auto *modifier_compute_context = dynamic_cast<const bke::ModifierComputeContext *>(
|
|
compute_context_vec[0]);
|
|
if (modifier_compute_context == nullptr) {
|
|
return;
|
|
}
|
|
if (modifier_compute_context->modifier_name() != nmd.modifier.name) {
|
|
return;
|
|
}
|
|
|
|
const bNodeTree *current_tree = nmd.node_group;
|
|
const bke::bNodeTreeZone *current_zone = nullptr;
|
|
|
|
/* Write side effect nodes to a new map and only if everything succeeds, move the nodes to the
|
|
* caller. This is easier than changing r_side_effect_nodes directly and then undoing changes in
|
|
* case of errors. */
|
|
nodes::GeoNodesSideEffectNodes local_side_effect_nodes;
|
|
for (const ComputeContext *compute_context_generic : compute_context_vec.as_span().drop_front(1))
|
|
{
|
|
const bke::bNodeTreeZones *current_zones = current_tree->zones();
|
|
if (current_zones == nullptr) {
|
|
return;
|
|
}
|
|
const auto *lf_graph_info = nodes::ensure_geometry_nodes_lazy_function_graph(*current_tree);
|
|
if (lf_graph_info == nullptr) {
|
|
return;
|
|
}
|
|
const ComputeContextHash &parent_compute_context_hash =
|
|
compute_context_generic->parent()->hash();
|
|
if (const auto *compute_context = dynamic_cast<const bke::SimulationZoneComputeContext *>(
|
|
compute_context_generic))
|
|
{
|
|
const bke::bNodeTreeZone *simulation_zone = current_zones->get_zone_by_node(
|
|
compute_context->output_node_id());
|
|
if (simulation_zone == nullptr) {
|
|
return;
|
|
}
|
|
if (simulation_zone->parent_zone != current_zone) {
|
|
return;
|
|
}
|
|
const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default(
|
|
simulation_zone, nullptr);
|
|
if (lf_zone_node == nullptr) {
|
|
return;
|
|
}
|
|
const lf::FunctionNode *lf_simulation_output_node =
|
|
lf_graph_info->mapping.possible_side_effect_node_map.lookup_default(
|
|
simulation_zone->output_node, nullptr);
|
|
if (lf_simulation_output_node == nullptr) {
|
|
return;
|
|
}
|
|
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_zone_node);
|
|
/* By making the simulation output node a side-effect-node, we can ensure that the simulation
|
|
* runs when it contains an active viewer. */
|
|
local_side_effect_nodes.nodes_by_context.add(compute_context_generic->hash(),
|
|
lf_simulation_output_node);
|
|
|
|
current_zone = simulation_zone;
|
|
}
|
|
else if (const auto *compute_context = dynamic_cast<const bke::RepeatZoneComputeContext *>(
|
|
compute_context_generic))
|
|
{
|
|
const bke::bNodeTreeZone *repeat_zone = current_zones->get_zone_by_node(
|
|
compute_context->output_node_id());
|
|
if (repeat_zone == nullptr) {
|
|
return;
|
|
}
|
|
if (repeat_zone->parent_zone != current_zone) {
|
|
return;
|
|
}
|
|
const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default(
|
|
repeat_zone, nullptr);
|
|
if (lf_zone_node == nullptr) {
|
|
return;
|
|
}
|
|
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_zone_node);
|
|
local_side_effect_nodes.iterations_by_iteration_zone.add(
|
|
{parent_compute_context_hash, compute_context->output_node_id()},
|
|
compute_context->iteration());
|
|
current_zone = repeat_zone;
|
|
}
|
|
else if (const auto *compute_context =
|
|
dynamic_cast<const bke::ForeachGeometryElementZoneComputeContext *>(
|
|
compute_context_generic))
|
|
{
|
|
const bke::bNodeTreeZone *foreach_zone = current_zones->get_zone_by_node(
|
|
compute_context->output_node_id());
|
|
if (foreach_zone == nullptr) {
|
|
return;
|
|
}
|
|
if (foreach_zone->parent_zone != current_zone) {
|
|
return;
|
|
}
|
|
const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default(
|
|
foreach_zone, nullptr);
|
|
if (lf_zone_node == nullptr) {
|
|
return;
|
|
}
|
|
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_zone_node);
|
|
local_side_effect_nodes.iterations_by_iteration_zone.add(
|
|
{parent_compute_context_hash, compute_context->output_node_id()},
|
|
compute_context->index());
|
|
current_zone = foreach_zone;
|
|
}
|
|
else if (const auto *compute_context = dynamic_cast<const bke::GroupNodeComputeContext *>(
|
|
compute_context_generic))
|
|
{
|
|
const bNode *group_node = current_tree->node_by_id(compute_context->node_id());
|
|
if (group_node == nullptr) {
|
|
return;
|
|
}
|
|
if (group_node->id == nullptr) {
|
|
return;
|
|
}
|
|
if (group_node->is_muted()) {
|
|
return;
|
|
}
|
|
if (current_zone != current_zones->get_zone_by_node(group_node->identifier)) {
|
|
return;
|
|
}
|
|
const lf::FunctionNode *lf_group_node = lf_graph_info->mapping.group_node_map.lookup_default(
|
|
group_node, nullptr);
|
|
if (lf_group_node == nullptr) {
|
|
return;
|
|
}
|
|
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_group_node);
|
|
current_tree = reinterpret_cast<const bNodeTree *>(group_node->id);
|
|
current_zone = nullptr;
|
|
}
|
|
else if (const auto *compute_context =
|
|
dynamic_cast<const bke::EvaluateClosureComputeContext *>(compute_context_generic))
|
|
{
|
|
const bNode *evaluate_node = current_tree->node_by_id(compute_context->node_id());
|
|
if (!evaluate_node) {
|
|
return;
|
|
}
|
|
if (evaluate_node->is_muted()) {
|
|
return;
|
|
}
|
|
if (current_zone != current_zones->get_zone_by_node(evaluate_node->identifier)) {
|
|
return;
|
|
}
|
|
const std::optional<nodes::ClosureSourceLocation> &source_location =
|
|
compute_context->closure_source_location();
|
|
if (!source_location) {
|
|
return;
|
|
}
|
|
if (!source_location->tree->zones()) {
|
|
return;
|
|
}
|
|
const lf::FunctionNode *lf_evaluate_node =
|
|
lf_graph_info->mapping.possible_side_effect_node_map.lookup_default(evaluate_node,
|
|
nullptr);
|
|
if (!lf_evaluate_node) {
|
|
return;
|
|
}
|
|
/* The tree may sometimes be original and sometimes evaluated, depending on the source of the
|
|
* compute context. */
|
|
const bNodeTree *eval_closure_tree = DEG_is_evaluated(source_location->tree) ?
|
|
source_location->tree :
|
|
reinterpret_cast<const bNodeTree *>(
|
|
DEG_get_evaluated_id(
|
|
ctx.depsgraph, &source_location->tree->id));
|
|
const bNode *closure_output_node = eval_closure_tree->node_by_id(
|
|
source_location->closure_output_node_id);
|
|
if (!closure_output_node) {
|
|
return;
|
|
}
|
|
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_evaluate_node);
|
|
current_tree = eval_closure_tree;
|
|
current_zone = eval_closure_tree->zones()->get_zone_by_node(closure_output_node->identifier);
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
}
|
|
const bNode *final_node = current_tree->node_by_id(final_node_id);
|
|
if (final_node == nullptr) {
|
|
return;
|
|
}
|
|
const auto *lf_graph_info = nodes::ensure_geometry_nodes_lazy_function_graph(*current_tree);
|
|
if (lf_graph_info == nullptr) {
|
|
return;
|
|
}
|
|
const bke::bNodeTreeZones *tree_zones = current_tree->zones();
|
|
if (tree_zones == nullptr) {
|
|
return;
|
|
}
|
|
if (tree_zones->get_zone_by_node(final_node_id) != current_zone) {
|
|
return;
|
|
}
|
|
const lf::FunctionNode *lf_node =
|
|
lf_graph_info->mapping.possible_side_effect_node_map.lookup_default(final_node, nullptr);
|
|
if (lf_node == nullptr) {
|
|
return;
|
|
}
|
|
local_side_effect_nodes.nodes_by_context.add(final_compute_context.hash(), lf_node);
|
|
|
|
/* Successfully found all side effect nodes for the viewer path. */
|
|
for (const auto item : local_side_effect_nodes.nodes_by_context.items()) {
|
|
r_side_effect_nodes.nodes_by_context.add_multiple(item.key, item.value);
|
|
}
|
|
for (const auto item : local_side_effect_nodes.iterations_by_iteration_zone.items()) {
|
|
r_side_effect_nodes.iterations_by_iteration_zone.add_multiple(item.key, item.value);
|
|
}
|
|
}
|
|
|
|
static void find_side_effect_nodes_for_viewer_path(
|
|
const ViewerPath &viewer_path,
|
|
const NodesModifierData &nmd,
|
|
const ModifierEvalContext &ctx,
|
|
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes)
|
|
{
|
|
const std::optional<ed::viewer_path::ViewerPathForGeometryNodesViewer> parsed_path =
|
|
ed::viewer_path::parse_geometry_nodes_viewer(viewer_path);
|
|
if (!parsed_path.has_value()) {
|
|
return;
|
|
}
|
|
if (parsed_path->object != DEG_get_original(ctx.object)) {
|
|
return;
|
|
}
|
|
if (parsed_path->modifier_name != nmd.modifier.name) {
|
|
return;
|
|
}
|
|
|
|
bke::ComputeContextCache compute_context_cache;
|
|
const ComputeContext *current = &compute_context_cache.for_modifier(nullptr, nmd);
|
|
for (const ViewerPathElem *elem : parsed_path->node_path) {
|
|
current = ed::viewer_path::compute_context_for_viewer_path_elem(
|
|
*elem, compute_context_cache, current);
|
|
if (!current) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
try_add_side_effect_node(ctx, *current, parsed_path->viewer_node_id, nmd, r_side_effect_nodes);
|
|
}
|
|
|
|
static void find_side_effect_nodes_for_nested_node(
|
|
const ModifierEvalContext &ctx,
|
|
const NodesModifierData &nmd,
|
|
const int root_nested_node_id,
|
|
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes)
|
|
{
|
|
bke::ComputeContextCache compute_context_cache;
|
|
const ComputeContext *compute_context = &compute_context_cache.for_modifier(nullptr, nmd);
|
|
|
|
int nested_node_id = root_nested_node_id;
|
|
const bNodeTree *tree = nmd.node_group;
|
|
while (true) {
|
|
const bNestedNodeRef *ref = tree->find_nested_node_ref(nested_node_id);
|
|
if (!ref) {
|
|
return;
|
|
}
|
|
const bNode *node = tree->node_by_id(ref->path.node_id);
|
|
if (!node) {
|
|
return;
|
|
}
|
|
const bke::bNodeTreeZones *zones = tree->zones();
|
|
if (!zones) {
|
|
return;
|
|
}
|
|
if (zones->get_zone_by_node(node->identifier) != nullptr) {
|
|
/* Only top level nodes are allowed here. */
|
|
return;
|
|
}
|
|
if (node->is_group()) {
|
|
if (!node->id) {
|
|
return;
|
|
}
|
|
compute_context = &compute_context_cache.for_group_node(compute_context, *node, *tree);
|
|
tree = reinterpret_cast<const bNodeTree *>(node->id);
|
|
nested_node_id = ref->path.id_in_node;
|
|
}
|
|
else {
|
|
try_add_side_effect_node(ctx, *compute_context, ref->path.node_id, nmd, r_side_effect_nodes);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This ensures that nodes that the user wants to bake are actually evaluated. Otherwise they might
|
|
* not be if they are not connected to the output.
|
|
*/
|
|
static void find_side_effect_nodes_for_baking(const NodesModifierData &nmd,
|
|
const ModifierEvalContext &ctx,
|
|
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes)
|
|
{
|
|
if (!nmd.runtime->cache) {
|
|
return;
|
|
}
|
|
if (!DEG_is_active(ctx.depsgraph)) {
|
|
/* Only the active depsgraph can bake. */
|
|
return;
|
|
}
|
|
bake::ModifierCache &modifier_cache = *nmd.runtime->cache;
|
|
for (const bNestedNodeRef &ref : nmd.node_group->nested_node_refs_span()) {
|
|
if (!modifier_cache.requested_bakes.contains(ref.id)) {
|
|
continue;
|
|
}
|
|
find_side_effect_nodes_for_nested_node(ctx, nmd, ref.id, r_side_effect_nodes);
|
|
}
|
|
}
|
|
|
|
static void find_side_effect_nodes_for_active_gizmos(
|
|
const NodesModifierData &nmd,
|
|
const ModifierEvalContext &ctx,
|
|
const wmWindowManager &wm,
|
|
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes,
|
|
Set<ComputeContextHash> &r_socket_log_contexts)
|
|
{
|
|
Object *object_orig = DEG_get_original(ctx.object);
|
|
const NodesModifierData &nmd_orig = *reinterpret_cast<const NodesModifierData *>(
|
|
BKE_modifier_get_original(ctx.object, const_cast<ModifierData *>(&nmd.modifier)));
|
|
bke::ComputeContextCache compute_context_cache;
|
|
nodes::gizmos::foreach_active_gizmo_in_modifier(
|
|
*object_orig,
|
|
nmd_orig,
|
|
wm,
|
|
compute_context_cache,
|
|
[&](const ComputeContext &compute_context,
|
|
const bNode &gizmo_node,
|
|
const bNodeSocket &gizmo_socket) {
|
|
try_add_side_effect_node(
|
|
ctx, compute_context, gizmo_node.identifier, nmd, r_side_effect_nodes);
|
|
r_socket_log_contexts.add(compute_context.hash());
|
|
|
|
nodes::gizmos::foreach_compute_context_on_gizmo_path(
|
|
compute_context, gizmo_node, gizmo_socket, [&](const ComputeContext &node_context) {
|
|
/* Make sure that all intermediate sockets are logged. This is necessary to be able
|
|
* to evaluate the nodes in reverse for the gizmo. */
|
|
r_socket_log_contexts.add(node_context.hash());
|
|
});
|
|
});
|
|
}
|
|
|
|
static void find_side_effect_nodes(const NodesModifierData &nmd,
|
|
const ModifierEvalContext &ctx,
|
|
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes,
|
|
Set<ComputeContextHash> &r_socket_log_contexts)
|
|
{
|
|
Main *bmain = DEG_get_bmain(ctx.depsgraph);
|
|
wmWindowManager *wm = (wmWindowManager *)bmain->wm.first;
|
|
if (wm == nullptr) {
|
|
return;
|
|
}
|
|
LISTBASE_FOREACH (const wmWindow *, window, &wm->windows) {
|
|
const bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook);
|
|
const WorkSpace *workspace = BKE_workspace_active_get(window->workspace_hook);
|
|
find_side_effect_nodes_for_viewer_path(workspace->viewer_path, nmd, ctx, r_side_effect_nodes);
|
|
LISTBASE_FOREACH (const ScrArea *, area, &screen->areabase) {
|
|
const SpaceLink *sl = static_cast<SpaceLink *>(area->spacedata.first);
|
|
if (sl->spacetype == SPACE_SPREADSHEET) {
|
|
const SpaceSpreadsheet &sspreadsheet = *reinterpret_cast<const SpaceSpreadsheet *>(sl);
|
|
find_side_effect_nodes_for_viewer_path(
|
|
sspreadsheet.viewer_path, nmd, ctx, r_side_effect_nodes);
|
|
}
|
|
if (sl->spacetype == SPACE_VIEW3D) {
|
|
const View3D &v3d = *reinterpret_cast<const View3D *>(sl);
|
|
find_side_effect_nodes_for_viewer_path(v3d.viewer_path, nmd, ctx, r_side_effect_nodes);
|
|
}
|
|
}
|
|
}
|
|
|
|
find_side_effect_nodes_for_baking(nmd, ctx, r_side_effect_nodes);
|
|
find_side_effect_nodes_for_active_gizmos(
|
|
nmd, ctx, *wm, r_side_effect_nodes, r_socket_log_contexts);
|
|
}
|
|
|
|
static void find_socket_log_contexts(const NodesModifierData &nmd,
|
|
const ModifierEvalContext &ctx,
|
|
Set<ComputeContextHash> &r_socket_log_contexts)
|
|
{
|
|
Main *bmain = DEG_get_bmain(ctx.depsgraph);
|
|
wmWindowManager *wm = (wmWindowManager *)bmain->wm.first;
|
|
if (wm == nullptr) {
|
|
return;
|
|
}
|
|
bke::ComputeContextCache compute_context_cache;
|
|
LISTBASE_FOREACH (const wmWindow *, window, &wm->windows) {
|
|
const bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook);
|
|
LISTBASE_FOREACH (const ScrArea *, area, &screen->areabase) {
|
|
const SpaceLink *sl = static_cast<SpaceLink *>(area->spacedata.first);
|
|
if (sl->spacetype == SPACE_NODE) {
|
|
const SpaceNode &snode = *reinterpret_cast<const SpaceNode *>(sl);
|
|
if (snode.edittree == nullptr || snode.edittree->type != NTREE_GEOMETRY) {
|
|
continue;
|
|
}
|
|
if (!ed::space_node::node_editor_is_for_geometry_nodes_modifier(snode, *ctx.object, nmd)) {
|
|
continue;
|
|
}
|
|
const Map<const bke::bNodeTreeZone *, ComputeContextHash> hash_by_zone =
|
|
geo_log::GeoModifierLog::get_context_hash_by_zone_for_node_editor(
|
|
snode, compute_context_cache);
|
|
for (const ComputeContextHash &hash : hash_by_zone.values()) {
|
|
r_socket_log_contexts.add(hash);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \note This could be done in #initialize_group_input, though that would require adding the
|
|
* the object as a parameter, so it's likely better to this check as a separate step.
|
|
*/
|
|
static void check_property_socket_sync(const Object *ob,
|
|
const nodes::PropertiesVectorSet &properties,
|
|
ModifierData *md)
|
|
{
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
|
|
int geometry_socket_count = 0;
|
|
|
|
nmd->node_group->ensure_interface_cache();
|
|
for (const int i : nmd->node_group->interface_inputs().index_range()) {
|
|
const bNodeTreeInterfaceSocket *socket = nmd->node_group->interface_inputs()[i];
|
|
const bke::bNodeSocketType *typeinfo = socket->socket_typeinfo();
|
|
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
|
|
if (type == SOCK_GEOMETRY) {
|
|
geometry_socket_count++;
|
|
}
|
|
/* The first socket is the special geometry socket for the modifier object. */
|
|
if (i == 0 && type == SOCK_GEOMETRY) {
|
|
continue;
|
|
}
|
|
|
|
IDProperty *property = properties.lookup_key_default_as(socket->identifier, nullptr);
|
|
if (property == nullptr) {
|
|
if (!ELEM(type, SOCK_GEOMETRY, SOCK_MATRIX, SOCK_BUNDLE, SOCK_CLOSURE)) {
|
|
BKE_modifier_set_error(
|
|
ob, md, "Missing property for input socket \"%s\"", socket->name ? socket->name : "");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!nodes::id_property_type_matches_socket(*socket, *property)) {
|
|
BKE_modifier_set_error(ob,
|
|
md,
|
|
"Property type does not match input socket \"(%s)\"",
|
|
socket->name ? socket->name : "");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (geometry_socket_count == 1) {
|
|
const bNodeTreeInterfaceSocket *first_socket = nmd->node_group->interface_inputs()[0];
|
|
const bke::bNodeSocketType *typeinfo = first_socket->socket_typeinfo();
|
|
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
|
|
if (type != SOCK_GEOMETRY) {
|
|
BKE_modifier_set_error(ob, md, "Node group's geometry input must be the first");
|
|
}
|
|
}
|
|
}
|
|
|
|
class NodesModifierBakeDataBlockMap : public bake::BakeDataBlockMap {
|
|
/** Protects access to `new_mappings` which may be added to from multiple threads. */
|
|
Mutex mutex_;
|
|
|
|
public:
|
|
Map<bake::BakeDataBlockID, ID *> old_mappings;
|
|
Map<bake::BakeDataBlockID, ID *> new_mappings;
|
|
|
|
ID *lookup_or_remember_missing(const bake::BakeDataBlockID &key) override
|
|
{
|
|
if (ID *id = this->old_mappings.lookup_default(key, nullptr)) {
|
|
return id;
|
|
}
|
|
if (this->old_mappings.contains(key)) {
|
|
/* Don't allow overwriting old mappings. */
|
|
return nullptr;
|
|
}
|
|
std::lock_guard lock{mutex_};
|
|
return this->new_mappings.lookup_or_add(key, nullptr);
|
|
}
|
|
|
|
void try_add(ID &id) override
|
|
{
|
|
bake::BakeDataBlockID key{id};
|
|
if (this->old_mappings.contains(key)) {
|
|
return;
|
|
}
|
|
std::lock_guard lock{mutex_};
|
|
this->new_mappings.add_overwrite(std::move(key), &id);
|
|
}
|
|
|
|
private:
|
|
ID *lookup_in_map(Map<bake::BakeDataBlockID, ID *> &map,
|
|
const bake::BakeDataBlockID &key,
|
|
const std::optional<ID_Type> &type)
|
|
{
|
|
ID *id = map.lookup_default(key, nullptr);
|
|
if (!id) {
|
|
return nullptr;
|
|
}
|
|
if (type && GS(id->name) != *type) {
|
|
return nullptr;
|
|
}
|
|
return id;
|
|
}
|
|
};
|
|
|
|
namespace sim_input = nodes::sim_input;
|
|
namespace sim_output = nodes::sim_output;
|
|
|
|
struct BakeFrameIndices {
|
|
std::optional<int> prev;
|
|
std::optional<int> current;
|
|
std::optional<int> next;
|
|
};
|
|
|
|
static BakeFrameIndices get_bake_frame_indices(
|
|
const Span<std::unique_ptr<bake::FrameCache>> frame_caches, const SubFrame frame)
|
|
{
|
|
BakeFrameIndices frame_indices;
|
|
if (!frame_caches.is_empty()) {
|
|
const int first_future_frame_index = binary_search::first_if(
|
|
frame_caches,
|
|
[&](const std::unique_ptr<bake::FrameCache> &value) { return value->frame > frame; });
|
|
frame_indices.next = (first_future_frame_index == frame_caches.size()) ?
|
|
std::nullopt :
|
|
std::optional<int>(first_future_frame_index);
|
|
if (first_future_frame_index > 0) {
|
|
const int index = first_future_frame_index - 1;
|
|
if (frame_caches[index]->frame < frame) {
|
|
frame_indices.prev = index;
|
|
}
|
|
else {
|
|
BLI_assert(frame_caches[index]->frame == frame);
|
|
frame_indices.current = index;
|
|
if (index > 0) {
|
|
frame_indices.prev = index - 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return frame_indices;
|
|
}
|
|
|
|
static void ensure_bake_loaded(bake::NodeBakeCache &bake_cache, bake::FrameCache &frame_cache)
|
|
{
|
|
if (!frame_cache.state.items_by_id.is_empty()) {
|
|
return;
|
|
}
|
|
if (!frame_cache.meta_data_source.has_value()) {
|
|
return;
|
|
}
|
|
if (bake_cache.memory_blob_reader) {
|
|
if (const auto *meta_buffer = std::get_if<Span<std::byte>>(&*frame_cache.meta_data_source)) {
|
|
const std::string meta_str{reinterpret_cast<const char *>(meta_buffer->data()),
|
|
size_t(meta_buffer->size())};
|
|
std::istringstream meta_stream{meta_str};
|
|
std::optional<bake::BakeState> bake_state = bake::deserialize_bake(
|
|
meta_stream, *bake_cache.memory_blob_reader, *bake_cache.blob_sharing);
|
|
if (!bake_state.has_value()) {
|
|
return;
|
|
}
|
|
frame_cache.state = std::move(*bake_state);
|
|
return;
|
|
}
|
|
}
|
|
if (!bake_cache.blobs_dir) {
|
|
return;
|
|
}
|
|
const auto *meta_path = std::get_if<std::string>(&*frame_cache.meta_data_source);
|
|
if (!meta_path) {
|
|
return;
|
|
}
|
|
bake::DiskBlobReader blob_reader{*bake_cache.blobs_dir};
|
|
fstream meta_file{*meta_path};
|
|
std::optional<bake::BakeState> bake_state = bake::deserialize_bake(
|
|
meta_file, blob_reader, *bake_cache.blob_sharing);
|
|
if (!bake_state.has_value()) {
|
|
return;
|
|
}
|
|
frame_cache.state = std::move(*bake_state);
|
|
}
|
|
|
|
static bool try_find_baked_data(const NodesModifierBake &bake,
|
|
bake::NodeBakeCache &bake_cache,
|
|
const Main &bmain,
|
|
const Object &object,
|
|
const NodesModifierData &nmd,
|
|
const int id)
|
|
{
|
|
if (bake.packed) {
|
|
if (bake.packed->meta_files_num == 0) {
|
|
return false;
|
|
}
|
|
bake_cache.reset();
|
|
Map<SubFrame, const NodesModifierBakeFile *> file_by_frame;
|
|
for (const NodesModifierBakeFile &meta_file :
|
|
Span{bake.packed->meta_files, bake.packed->meta_files_num})
|
|
{
|
|
const std::optional<SubFrame> frame = bake::file_name_to_frame(meta_file.name);
|
|
if (!frame) {
|
|
return false;
|
|
}
|
|
if (!file_by_frame.add(*frame, &meta_file)) {
|
|
/* Can only have on file per (sub)frame. */
|
|
return false;
|
|
}
|
|
}
|
|
/* Make sure frames processed in the right order. */
|
|
Vector<SubFrame> frames;
|
|
frames.extend(file_by_frame.keys().begin(), file_by_frame.keys().end());
|
|
|
|
for (const SubFrame &frame : frames) {
|
|
const NodesModifierBakeFile &meta_file = *file_by_frame.lookup(frame);
|
|
auto frame_cache = std::make_unique<bake::FrameCache>();
|
|
frame_cache->frame = frame;
|
|
frame_cache->meta_data_source = meta_file.data();
|
|
bake_cache.frames.append(std::move(frame_cache));
|
|
}
|
|
|
|
bake_cache.memory_blob_reader = std::make_unique<bake::MemoryBlobReader>();
|
|
for (const NodesModifierBakeFile &blob_file :
|
|
Span{bake.packed->blob_files, bake.packed->blob_files_num})
|
|
{
|
|
bake_cache.memory_blob_reader->add(blob_file.name, blob_file.data());
|
|
}
|
|
bake_cache.blob_sharing = std::make_unique<bake::BlobReadSharing>();
|
|
return true;
|
|
}
|
|
|
|
std::optional<bake::BakePath> bake_path = bake::get_node_bake_path(bmain, object, nmd, id);
|
|
if (!bake_path) {
|
|
return false;
|
|
}
|
|
Vector<bake::MetaFile> meta_files = bake::find_sorted_meta_files(bake_path->meta_dir);
|
|
if (meta_files.is_empty()) {
|
|
return false;
|
|
}
|
|
bake_cache.reset();
|
|
for (const bake::MetaFile &meta_file : meta_files) {
|
|
auto frame_cache = std::make_unique<bake::FrameCache>();
|
|
frame_cache->frame = meta_file.frame;
|
|
frame_cache->meta_data_source = meta_file.path;
|
|
bake_cache.frames.append(std::move(frame_cache));
|
|
}
|
|
bake_cache.blobs_dir = bake_path->blobs_dir;
|
|
bake_cache.blob_sharing = std::make_unique<bake::BlobReadSharing>();
|
|
return true;
|
|
}
|
|
|
|
class NodesModifierSimulationParams : public nodes::GeoNodesSimulationParams {
|
|
private:
|
|
static constexpr float max_delta_frames = 1.0f;
|
|
|
|
const NodesModifierData &nmd_;
|
|
const ModifierEvalContext &ctx_;
|
|
const Main *bmain_;
|
|
const Scene *scene_;
|
|
SubFrame current_frame_;
|
|
bool use_frame_cache_;
|
|
bool depsgraph_is_active_;
|
|
bake::ModifierCache *modifier_cache_;
|
|
float fps_;
|
|
bool has_invalid_simulation_ = false;
|
|
|
|
public:
|
|
struct DataPerZone {
|
|
nodes::SimulationZoneBehavior behavior;
|
|
NodesModifierBakeDataBlockMap data_block_map;
|
|
};
|
|
|
|
mutable Map<int, std::unique_ptr<DataPerZone>> data_by_zone_id;
|
|
|
|
NodesModifierSimulationParams(NodesModifierData &nmd, const ModifierEvalContext &ctx)
|
|
: nmd_(nmd), ctx_(ctx)
|
|
{
|
|
const Depsgraph *depsgraph = ctx_.depsgraph;
|
|
bmain_ = DEG_get_bmain(depsgraph);
|
|
current_frame_ = DEG_get_ctime(depsgraph);
|
|
const Scene *scene = DEG_get_input_scene(depsgraph);
|
|
scene_ = scene;
|
|
use_frame_cache_ = ctx_.object->flag & OB_FLAG_USE_SIMULATION_CACHE;
|
|
depsgraph_is_active_ = DEG_is_active(depsgraph);
|
|
modifier_cache_ = nmd.runtime->cache.get();
|
|
fps_ = FPS;
|
|
|
|
if (!modifier_cache_) {
|
|
return;
|
|
}
|
|
std::lock_guard lock{modifier_cache_->mutex};
|
|
if (depsgraph_is_active_) {
|
|
/* Invalidate data on user edits. */
|
|
if (nmd.modifier.flag & eModifierFlag_UserModified) {
|
|
for (std::unique_ptr<bake::SimulationNodeCache> &node_cache :
|
|
modifier_cache_->simulation_cache_by_id.values())
|
|
{
|
|
if (node_cache->cache_status != bake::CacheStatus::Baked) {
|
|
node_cache->cache_status = bake::CacheStatus::Invalid;
|
|
if (!node_cache->bake.frames.is_empty()) {
|
|
if (node_cache->bake.frames.last()->frame == current_frame_) {
|
|
/* Remove the last (which is the current) cached frame so that it is simulated
|
|
* again. */
|
|
node_cache->bake.frames.pop_last();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
this->reset_invalid_node_bakes();
|
|
}
|
|
for (const std::unique_ptr<bake::SimulationNodeCache> &node_cache_ptr :
|
|
modifier_cache_->simulation_cache_by_id.values())
|
|
{
|
|
const bake::SimulationNodeCache &node_cache = *node_cache_ptr;
|
|
if (node_cache.cache_status == bake::CacheStatus::Invalid) {
|
|
has_invalid_simulation_ = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void reset_invalid_node_bakes()
|
|
{
|
|
for (auto item : modifier_cache_->simulation_cache_by_id.items()) {
|
|
const int id = item.key;
|
|
bake::SimulationNodeCache &node_cache = *item.value;
|
|
if (node_cache.cache_status != bake::CacheStatus::Invalid) {
|
|
continue;
|
|
}
|
|
const std::optional<IndexRange> sim_frame_range = bake::get_node_bake_frame_range(
|
|
*scene_, *ctx_.object, nmd_, id);
|
|
if (!sim_frame_range.has_value()) {
|
|
continue;
|
|
}
|
|
const SubFrame start_frame{int(sim_frame_range->start())};
|
|
if (current_frame_ <= start_frame) {
|
|
node_cache.reset();
|
|
}
|
|
if (!node_cache.bake.frames.is_empty() &&
|
|
current_frame_ < node_cache.bake.frames.first()->frame)
|
|
{
|
|
node_cache.reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
nodes::SimulationZoneBehavior *get(const int zone_id) const override
|
|
{
|
|
if (!modifier_cache_) {
|
|
return nullptr;
|
|
}
|
|
std::lock_guard lock{modifier_cache_->mutex};
|
|
return &this->data_by_zone_id
|
|
.lookup_or_add_cb(zone_id,
|
|
[&]() {
|
|
auto data = std::make_unique<DataPerZone>();
|
|
data->behavior.data_block_map = &data->data_block_map;
|
|
this->init_simulation_info(
|
|
zone_id, data->behavior, data->data_block_map);
|
|
return data;
|
|
})
|
|
->behavior;
|
|
}
|
|
|
|
void init_simulation_info(const int zone_id,
|
|
nodes::SimulationZoneBehavior &zone_behavior,
|
|
NodesModifierBakeDataBlockMap &data_block_map) const
|
|
{
|
|
bake::SimulationNodeCache &node_cache =
|
|
*modifier_cache_->simulation_cache_by_id.lookup_or_add_cb(
|
|
zone_id, []() { return std::make_unique<bake::SimulationNodeCache>(); });
|
|
const NodesModifierBake &bake = *nmd_.find_bake(zone_id);
|
|
const IndexRange sim_frame_range = *bake::get_node_bake_frame_range(
|
|
*scene_, *ctx_.object, nmd_, zone_id);
|
|
const SubFrame sim_start_frame{int(sim_frame_range.first())};
|
|
const SubFrame sim_end_frame{int(sim_frame_range.last())};
|
|
|
|
if (!modifier_cache_->requested_bakes.contains(zone_id)) {
|
|
/* Try load baked data. */
|
|
if (!node_cache.bake.failed_finding_bake) {
|
|
if (node_cache.cache_status != bake::CacheStatus::Baked) {
|
|
if (try_find_baked_data(bake, node_cache.bake, *bmain_, *ctx_.object, nmd_, zone_id)) {
|
|
node_cache.cache_status = bake::CacheStatus::Baked;
|
|
}
|
|
else {
|
|
node_cache.bake.failed_finding_bake = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If there are no baked frames, we don't need keep track of the data-blocks. */
|
|
if (!node_cache.bake.frames.is_empty() || node_cache.prev_cache.has_value()) {
|
|
for (const NodesModifierDataBlock &data_block : Span{bake.data_blocks, bake.data_blocks_num})
|
|
{
|
|
data_block_map.old_mappings.add(data_block, data_block.id);
|
|
}
|
|
}
|
|
|
|
const BakeFrameIndices frame_indices = get_bake_frame_indices(node_cache.bake.frames,
|
|
current_frame_);
|
|
if (node_cache.cache_status == bake::CacheStatus::Baked) {
|
|
this->read_from_cache(frame_indices, node_cache, zone_behavior);
|
|
return;
|
|
}
|
|
if (use_frame_cache_) {
|
|
/* If the depsgraph is active, we allow creating new simulation states. Otherwise, the access
|
|
* is read-only. */
|
|
if (depsgraph_is_active_) {
|
|
if (node_cache.bake.frames.is_empty()) {
|
|
if (current_frame_ < sim_start_frame || current_frame_ > sim_end_frame) {
|
|
/* Outside of simulation frame range, so ignore the simulation if there is no cache. */
|
|
this->input_pass_through(zone_behavior);
|
|
this->output_pass_through(zone_behavior);
|
|
return;
|
|
}
|
|
/* Initialize the simulation. */
|
|
if (current_frame_ > sim_start_frame || has_invalid_simulation_) {
|
|
node_cache.cache_status = bake::CacheStatus::Invalid;
|
|
}
|
|
this->input_pass_through(zone_behavior);
|
|
this->output_store_frame_cache(node_cache, zone_behavior);
|
|
return;
|
|
}
|
|
if (frame_indices.prev && !frame_indices.current && !frame_indices.next &&
|
|
current_frame_ <= sim_end_frame)
|
|
{
|
|
/* Read the previous frame's data and store the newly computed simulation state. */
|
|
auto &output_copy_info = zone_behavior.input.emplace<sim_input::OutputCopy>();
|
|
const bake::FrameCache &prev_frame_cache = *node_cache.bake.frames[*frame_indices.prev];
|
|
const float real_delta_frames = float(current_frame_) - float(prev_frame_cache.frame);
|
|
if (real_delta_frames != 1) {
|
|
node_cache.cache_status = bake::CacheStatus::Invalid;
|
|
}
|
|
const float delta_frames = std::min(max_delta_frames, real_delta_frames);
|
|
output_copy_info.delta_time = delta_frames / fps_;
|
|
output_copy_info.state = prev_frame_cache.state;
|
|
this->output_store_frame_cache(node_cache, zone_behavior);
|
|
return;
|
|
}
|
|
}
|
|
this->read_from_cache(frame_indices, node_cache, zone_behavior);
|
|
return;
|
|
}
|
|
|
|
/* When there is no per-frame cache, check if there is a previous state. */
|
|
if (node_cache.prev_cache) {
|
|
if (node_cache.prev_cache->frame < current_frame_) {
|
|
/* Do a simulation step. */
|
|
const float delta_frames = std::min(
|
|
max_delta_frames, float(current_frame_) - float(node_cache.prev_cache->frame));
|
|
auto &output_move_info = zone_behavior.input.emplace<sim_input::OutputMove>();
|
|
output_move_info.delta_time = delta_frames / fps_;
|
|
output_move_info.state = std::move(node_cache.prev_cache->state);
|
|
this->store_as_prev_items(node_cache, zone_behavior);
|
|
return;
|
|
}
|
|
if (node_cache.prev_cache->frame == current_frame_) {
|
|
/* Just read from the previous state if the frame has not changed. */
|
|
auto &output_copy_info = zone_behavior.input.emplace<sim_input::OutputCopy>();
|
|
output_copy_info.delta_time = 0.0f;
|
|
output_copy_info.state = node_cache.prev_cache->state;
|
|
auto &read_single_info = zone_behavior.output.emplace<sim_output::ReadSingle>();
|
|
read_single_info.state = node_cache.prev_cache->state;
|
|
return;
|
|
}
|
|
if (!depsgraph_is_active_) {
|
|
/* There is no previous state, and it's not possible to initialize the simulation because
|
|
* the depsgraph is not active. */
|
|
zone_behavior.input.emplace<sim_input::PassThrough>();
|
|
zone_behavior.output.emplace<sim_output::PassThrough>();
|
|
return;
|
|
}
|
|
/* Reset the simulation when the scene time moved backwards. */
|
|
node_cache.prev_cache.reset();
|
|
}
|
|
zone_behavior.input.emplace<sim_input::PassThrough>();
|
|
if (depsgraph_is_active_) {
|
|
/* Initialize the simulation. */
|
|
this->store_as_prev_items(node_cache, zone_behavior);
|
|
}
|
|
else {
|
|
zone_behavior.output.emplace<sim_output::PassThrough>();
|
|
}
|
|
}
|
|
|
|
void input_pass_through(nodes::SimulationZoneBehavior &zone_behavior) const
|
|
{
|
|
zone_behavior.input.emplace<sim_input::PassThrough>();
|
|
}
|
|
|
|
void output_pass_through(nodes::SimulationZoneBehavior &zone_behavior) const
|
|
{
|
|
zone_behavior.output.emplace<sim_output::PassThrough>();
|
|
}
|
|
|
|
void output_store_frame_cache(bake::SimulationNodeCache &node_cache,
|
|
nodes::SimulationZoneBehavior &zone_behavior) const
|
|
{
|
|
auto &store_new_state_info = zone_behavior.output.emplace<sim_output::StoreNewState>();
|
|
store_new_state_info.store_fn = [simulation_cache = modifier_cache_,
|
|
node_cache = &node_cache,
|
|
current_frame = current_frame_](bke::bake::BakeState state) {
|
|
std::lock_guard lock{simulation_cache->mutex};
|
|
auto frame_cache = std::make_unique<bake::FrameCache>();
|
|
frame_cache->frame = current_frame;
|
|
frame_cache->state = std::move(state);
|
|
node_cache->bake.frames.append(std::move(frame_cache));
|
|
};
|
|
}
|
|
|
|
void store_as_prev_items(bake::SimulationNodeCache &node_cache,
|
|
nodes::SimulationZoneBehavior &zone_behavior) const
|
|
{
|
|
auto &store_new_state_info = zone_behavior.output.emplace<sim_output::StoreNewState>();
|
|
store_new_state_info.store_fn = [simulation_cache = modifier_cache_,
|
|
node_cache = &node_cache,
|
|
current_frame = current_frame_](bke::bake::BakeState state) {
|
|
std::lock_guard lock{simulation_cache->mutex};
|
|
if (!node_cache->prev_cache) {
|
|
node_cache->prev_cache.emplace();
|
|
}
|
|
node_cache->prev_cache->state = std::move(state);
|
|
node_cache->prev_cache->frame = current_frame;
|
|
};
|
|
}
|
|
|
|
void read_from_cache(const BakeFrameIndices &frame_indices,
|
|
bake::SimulationNodeCache &node_cache,
|
|
nodes::SimulationZoneBehavior &zone_behavior) const
|
|
{
|
|
if (frame_indices.prev) {
|
|
auto &output_copy_info = zone_behavior.input.emplace<sim_input::OutputCopy>();
|
|
bake::FrameCache &frame_cache = *node_cache.bake.frames[*frame_indices.prev];
|
|
const float delta_frames = std::min(max_delta_frames,
|
|
float(current_frame_) - float(frame_cache.frame));
|
|
output_copy_info.delta_time = delta_frames / fps_;
|
|
output_copy_info.state = frame_cache.state;
|
|
}
|
|
else {
|
|
zone_behavior.input.emplace<sim_input::PassThrough>();
|
|
}
|
|
if (frame_indices.current) {
|
|
this->read_single(*frame_indices.current, node_cache, zone_behavior);
|
|
}
|
|
else if (frame_indices.next) {
|
|
if (frame_indices.prev) {
|
|
this->read_interpolated(
|
|
*frame_indices.prev, *frame_indices.next, node_cache, zone_behavior);
|
|
}
|
|
else {
|
|
this->output_pass_through(zone_behavior);
|
|
}
|
|
}
|
|
else if (frame_indices.prev) {
|
|
this->read_single(*frame_indices.prev, node_cache, zone_behavior);
|
|
}
|
|
else {
|
|
this->output_pass_through(zone_behavior);
|
|
}
|
|
}
|
|
|
|
void read_single(const int frame_index,
|
|
bake::SimulationNodeCache &node_cache,
|
|
nodes::SimulationZoneBehavior &zone_behavior) const
|
|
{
|
|
bake::FrameCache &frame_cache = *node_cache.bake.frames[frame_index];
|
|
ensure_bake_loaded(node_cache.bake, frame_cache);
|
|
auto &read_single_info = zone_behavior.output.emplace<sim_output::ReadSingle>();
|
|
read_single_info.state = frame_cache.state;
|
|
}
|
|
|
|
void read_interpolated(const int prev_frame_index,
|
|
const int next_frame_index,
|
|
bake::SimulationNodeCache &node_cache,
|
|
nodes::SimulationZoneBehavior &zone_behavior) const
|
|
{
|
|
bake::FrameCache &prev_frame_cache = *node_cache.bake.frames[prev_frame_index];
|
|
bake::FrameCache &next_frame_cache = *node_cache.bake.frames[next_frame_index];
|
|
ensure_bake_loaded(node_cache.bake, prev_frame_cache);
|
|
ensure_bake_loaded(node_cache.bake, next_frame_cache);
|
|
auto &read_interpolated_info = zone_behavior.output.emplace<sim_output::ReadInterpolated>();
|
|
read_interpolated_info.mix_factor = (float(current_frame_) - float(prev_frame_cache.frame)) /
|
|
(float(next_frame_cache.frame) -
|
|
float(prev_frame_cache.frame));
|
|
read_interpolated_info.prev_state = prev_frame_cache.state;
|
|
read_interpolated_info.next_state = next_frame_cache.state;
|
|
}
|
|
};
|
|
|
|
class NodesModifierBakeParams : public nodes::GeoNodesBakeParams {
|
|
private:
|
|
const NodesModifierData &nmd_;
|
|
const ModifierEvalContext &ctx_;
|
|
Main *bmain_;
|
|
SubFrame current_frame_;
|
|
bake::ModifierCache *modifier_cache_;
|
|
bool depsgraph_is_active_;
|
|
|
|
public:
|
|
struct DataPerNode {
|
|
nodes::BakeNodeBehavior behavior;
|
|
NodesModifierBakeDataBlockMap data_block_map;
|
|
};
|
|
|
|
mutable Map<int, std::unique_ptr<DataPerNode>> data_by_node_id;
|
|
|
|
NodesModifierBakeParams(NodesModifierData &nmd, const ModifierEvalContext &ctx)
|
|
: nmd_(nmd), ctx_(ctx)
|
|
{
|
|
const Depsgraph *depsgraph = ctx_.depsgraph;
|
|
current_frame_ = DEG_get_ctime(depsgraph);
|
|
modifier_cache_ = nmd.runtime->cache.get();
|
|
depsgraph_is_active_ = DEG_is_active(depsgraph);
|
|
bmain_ = DEG_get_bmain(depsgraph);
|
|
}
|
|
|
|
nodes::BakeNodeBehavior *get(const int id) const override
|
|
{
|
|
if (!modifier_cache_) {
|
|
return nullptr;
|
|
}
|
|
std::lock_guard lock{modifier_cache_->mutex};
|
|
return &this->data_by_node_id
|
|
.lookup_or_add_cb(id,
|
|
[&]() {
|
|
auto data = std::make_unique<DataPerNode>();
|
|
data->behavior.data_block_map = &data->data_block_map;
|
|
this->init_bake_behavior(
|
|
id, data->behavior, data->data_block_map);
|
|
return data;
|
|
})
|
|
->behavior;
|
|
return nullptr;
|
|
}
|
|
|
|
private:
|
|
void init_bake_behavior(const int id,
|
|
nodes::BakeNodeBehavior &behavior,
|
|
NodesModifierBakeDataBlockMap &data_block_map) const
|
|
{
|
|
bake::BakeNodeCache &node_cache = *modifier_cache_->bake_cache_by_id.lookup_or_add_cb(
|
|
id, []() { return std::make_unique<bake::BakeNodeCache>(); });
|
|
const NodesModifierBake &bake = *nmd_.find_bake(id);
|
|
|
|
for (const NodesModifierDataBlock &data_block : Span{bake.data_blocks, bake.data_blocks_num}) {
|
|
data_block_map.old_mappings.add(data_block, data_block.id);
|
|
}
|
|
|
|
if (depsgraph_is_active_) {
|
|
if (modifier_cache_->requested_bakes.contains(id)) {
|
|
/* This node is baked during the current evaluation. */
|
|
auto &store_info = behavior.behavior.emplace<sim_output::StoreNewState>();
|
|
store_info.store_fn = [modifier_cache = modifier_cache_,
|
|
node_cache = &node_cache,
|
|
current_frame = current_frame_](bake::BakeState state) {
|
|
std::lock_guard lock{modifier_cache->mutex};
|
|
auto frame_cache = std::make_unique<bake::FrameCache>();
|
|
frame_cache->frame = current_frame;
|
|
frame_cache->state = std::move(state);
|
|
auto &frames = node_cache->bake.frames;
|
|
const int insert_index = binary_search::first_if(
|
|
frames, [&](const std::unique_ptr<bake::FrameCache> &frame_cache) {
|
|
return frame_cache->frame > current_frame;
|
|
});
|
|
frames.insert(insert_index, std::move(frame_cache));
|
|
};
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Try load baked data. */
|
|
if (node_cache.bake.frames.is_empty()) {
|
|
if (!node_cache.bake.failed_finding_bake) {
|
|
if (!try_find_baked_data(bake, node_cache.bake, *bmain_, *ctx_.object, nmd_, id)) {
|
|
node_cache.bake.failed_finding_bake = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (node_cache.bake.frames.is_empty()) {
|
|
behavior.behavior.emplace<sim_output::PassThrough>();
|
|
return;
|
|
}
|
|
const BakeFrameIndices frame_indices = get_bake_frame_indices(node_cache.bake.frames,
|
|
current_frame_);
|
|
if (frame_indices.current) {
|
|
this->read_single(*frame_indices.current, node_cache, behavior);
|
|
return;
|
|
}
|
|
if (frame_indices.prev && frame_indices.next) {
|
|
this->read_interpolated(*frame_indices.prev, *frame_indices.next, node_cache, behavior);
|
|
return;
|
|
}
|
|
if (frame_indices.prev) {
|
|
this->read_single(*frame_indices.prev, node_cache, behavior);
|
|
return;
|
|
}
|
|
if (frame_indices.next) {
|
|
this->read_single(*frame_indices.next, node_cache, behavior);
|
|
return;
|
|
}
|
|
BLI_assert_unreachable();
|
|
}
|
|
|
|
void read_single(const int frame_index,
|
|
bake::BakeNodeCache &node_cache,
|
|
nodes::BakeNodeBehavior &behavior) const
|
|
{
|
|
bake::FrameCache &frame_cache = *node_cache.bake.frames[frame_index];
|
|
ensure_bake_loaded(node_cache.bake, frame_cache);
|
|
if (this->check_read_error(frame_cache, behavior)) {
|
|
return;
|
|
}
|
|
auto &read_single_info = behavior.behavior.emplace<sim_output::ReadSingle>();
|
|
read_single_info.state = frame_cache.state;
|
|
}
|
|
|
|
void read_interpolated(const int prev_frame_index,
|
|
const int next_frame_index,
|
|
bake::BakeNodeCache &node_cache,
|
|
nodes::BakeNodeBehavior &behavior) const
|
|
{
|
|
bake::FrameCache &prev_frame_cache = *node_cache.bake.frames[prev_frame_index];
|
|
bake::FrameCache &next_frame_cache = *node_cache.bake.frames[next_frame_index];
|
|
ensure_bake_loaded(node_cache.bake, prev_frame_cache);
|
|
ensure_bake_loaded(node_cache.bake, next_frame_cache);
|
|
if (this->check_read_error(prev_frame_cache, behavior) ||
|
|
this->check_read_error(next_frame_cache, behavior))
|
|
{
|
|
return;
|
|
}
|
|
auto &read_interpolated_info = behavior.behavior.emplace<sim_output::ReadInterpolated>();
|
|
read_interpolated_info.mix_factor = (float(current_frame_) - float(prev_frame_cache.frame)) /
|
|
(float(next_frame_cache.frame) -
|
|
float(prev_frame_cache.frame));
|
|
read_interpolated_info.prev_state = prev_frame_cache.state;
|
|
read_interpolated_info.next_state = next_frame_cache.state;
|
|
}
|
|
|
|
[[nodiscard]] bool check_read_error(const bake::FrameCache &frame_cache,
|
|
nodes::BakeNodeBehavior &behavior) const
|
|
{
|
|
if (frame_cache.meta_data_source && frame_cache.state.items_by_id.is_empty()) {
|
|
auto &read_error_info = behavior.behavior.emplace<sim_output::ReadError>();
|
|
read_error_info.message = RPT_("Cannot load the baked data");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
static void add_missing_data_block_mappings(
|
|
NodesModifierBake &bake,
|
|
const Span<bake::BakeDataBlockID> missing,
|
|
FunctionRef<ID *(const bake::BakeDataBlockID &)> get_data_block)
|
|
{
|
|
const int old_num = bake.data_blocks_num;
|
|
const int new_num = old_num + missing.size();
|
|
bake.data_blocks = reinterpret_cast<NodesModifierDataBlock *>(
|
|
MEM_recallocN(bake.data_blocks, sizeof(NodesModifierDataBlock) * new_num));
|
|
for (const int i : missing.index_range()) {
|
|
NodesModifierDataBlock &data_block = bake.data_blocks[old_num + i];
|
|
const blender::bke::bake::BakeDataBlockID &key = missing[i];
|
|
|
|
data_block.id_name = BLI_strdup(key.id_name.c_str());
|
|
if (!key.lib_name.empty()) {
|
|
data_block.lib_name = BLI_strdup(key.lib_name.c_str());
|
|
}
|
|
data_block.id_type = int(key.type);
|
|
ID *id = get_data_block(key);
|
|
if (id) {
|
|
data_block.id = id;
|
|
}
|
|
}
|
|
bake.data_blocks_num = new_num;
|
|
}
|
|
|
|
void nodes_modifier_data_block_destruct(NodesModifierDataBlock *data_block, const bool do_id_user)
|
|
{
|
|
MEM_SAFE_FREE(data_block->id_name);
|
|
MEM_SAFE_FREE(data_block->lib_name);
|
|
if (do_id_user) {
|
|
id_us_min(data_block->id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* During evaluation we might have baked geometry that contains references to other data-blocks
|
|
* (such as materials). We need to make sure that those data-blocks stay dependencies of the
|
|
* modifier. Otherwise, the data-block references might not work when the baked data is loaded
|
|
* again. Therefor, the dependencies are written back to the original modifier.
|
|
*/
|
|
static void add_data_block_items_writeback(const ModifierEvalContext &ctx,
|
|
NodesModifierData &nmd_eval,
|
|
NodesModifierData &nmd_orig,
|
|
NodesModifierSimulationParams &simulation_params,
|
|
NodesModifierBakeParams &bake_params)
|
|
{
|
|
Depsgraph *depsgraph = ctx.depsgraph;
|
|
Main *bmain = DEG_get_bmain(depsgraph);
|
|
|
|
struct DataPerBake {
|
|
bool reset_first = false;
|
|
Map<bake::BakeDataBlockID, ID *> new_mappings;
|
|
};
|
|
Map<int, DataPerBake> writeback_data;
|
|
for (auto item : simulation_params.data_by_zone_id.items()) {
|
|
DataPerBake data;
|
|
NodesModifierBake &bake = *nmd_eval.find_bake(item.key);
|
|
if (item.value->data_block_map.old_mappings.size() < bake.data_blocks_num) {
|
|
data.reset_first = true;
|
|
}
|
|
if (bake::SimulationNodeCache *node_cache = nmd_eval.runtime->cache->get_simulation_node_cache(
|
|
item.key))
|
|
{
|
|
/* Only writeback if the bake node has actually baked anything. */
|
|
if (!node_cache->bake.frames.is_empty() || node_cache->prev_cache.has_value()) {
|
|
data.new_mappings = std::move(item.value->data_block_map.new_mappings);
|
|
}
|
|
}
|
|
if (data.reset_first || !data.new_mappings.is_empty()) {
|
|
writeback_data.add(item.key, std::move(data));
|
|
}
|
|
}
|
|
for (auto item : bake_params.data_by_node_id.items()) {
|
|
if (bake::BakeNodeCache *node_cache = nmd_eval.runtime->cache->get_bake_node_cache(item.key)) {
|
|
/* Only writeback if the bake node has actually baked anything. */
|
|
if (!node_cache->bake.frames.is_empty()) {
|
|
DataPerBake data;
|
|
data.new_mappings = std::move(item.value->data_block_map.new_mappings);
|
|
writeback_data.add(item.key, std::move(data));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (writeback_data.is_empty()) {
|
|
/* Nothing to do. */
|
|
return;
|
|
}
|
|
|
|
deg::sync_writeback::add(
|
|
*depsgraph,
|
|
[object_eval = ctx.object,
|
|
bmain,
|
|
&nmd_orig,
|
|
&nmd_eval,
|
|
writeback_data = std::move(writeback_data)]() {
|
|
for (auto item : writeback_data.items()) {
|
|
const int bake_id = item.key;
|
|
DataPerBake data = item.value;
|
|
|
|
NodesModifierBake &bake_orig = *nmd_orig.find_bake(bake_id);
|
|
NodesModifierBake &bake_eval = *nmd_eval.find_bake(bake_id);
|
|
|
|
if (data.reset_first) {
|
|
/* Reset data-block list on original data. */
|
|
dna::array::clear<NodesModifierDataBlock>(&bake_orig.data_blocks,
|
|
&bake_orig.data_blocks_num,
|
|
&bake_orig.active_data_block,
|
|
[](NodesModifierDataBlock *data_block) {
|
|
nodes_modifier_data_block_destruct(
|
|
data_block, true);
|
|
});
|
|
/* Reset data-block list on evaluated data. */
|
|
dna::array::clear<NodesModifierDataBlock>(&bake_eval.data_blocks,
|
|
&bake_eval.data_blocks_num,
|
|
&bake_eval.active_data_block,
|
|
[](NodesModifierDataBlock *data_block) {
|
|
nodes_modifier_data_block_destruct(
|
|
data_block, false);
|
|
});
|
|
}
|
|
|
|
Vector<bake::BakeDataBlockID> sorted_new_mappings;
|
|
sorted_new_mappings.extend(data.new_mappings.keys().begin(),
|
|
data.new_mappings.keys().end());
|
|
bool needs_reevaluation = false;
|
|
/* Add new data block mappings to the original modifier. This may do a name lookup in
|
|
* bmain to find the data block if there is not faster way to get it. */
|
|
add_missing_data_block_mappings(
|
|
bake_orig, sorted_new_mappings, [&](const bake::BakeDataBlockID &key) -> ID * {
|
|
ID *id_orig = nullptr;
|
|
if (ID *id_eval = data.new_mappings.lookup_default(key, nullptr)) {
|
|
id_orig = DEG_get_original(id_eval);
|
|
}
|
|
else {
|
|
needs_reevaluation = true;
|
|
id_orig = BKE_libblock_find_name_and_library(
|
|
bmain, short(key.type), key.id_name.c_str(), key.lib_name.c_str());
|
|
}
|
|
if (id_orig) {
|
|
id_us_plus(id_orig);
|
|
}
|
|
return id_orig;
|
|
});
|
|
/* Add new data block mappings to the evaluated modifier. In most cases this makes it so
|
|
* the evaluated modifier is in the same state as if it were copied from the updated
|
|
* original again. The exception is when a missing data block was found that is not in
|
|
* the depsgraph currently. */
|
|
add_missing_data_block_mappings(
|
|
bake_eval, sorted_new_mappings, [&](const bake::BakeDataBlockID &key) -> ID * {
|
|
return data.new_mappings.lookup_default(key, nullptr);
|
|
});
|
|
|
|
if (needs_reevaluation) {
|
|
Object *object_orig = DEG_get_original(object_eval);
|
|
DEG_id_tag_update(&object_orig->id, ID_RECALC_GEOMETRY);
|
|
DEG_relations_tag_update(bmain);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
static void modifyGeometry(ModifierData *md,
|
|
const ModifierEvalContext *ctx,
|
|
bke::GeometrySet &geometry_set)
|
|
{
|
|
using namespace blender;
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
if (nmd->node_group == nullptr) {
|
|
return;
|
|
}
|
|
NodesModifierData *nmd_orig = reinterpret_cast<NodesModifierData *>(
|
|
BKE_modifier_get_original(ctx->object, &nmd->modifier));
|
|
|
|
nodes::PropertiesVectorSet properties = nodes::build_properties_vector_set(
|
|
nmd->settings.properties);
|
|
|
|
const bNodeTree &tree = *nmd->node_group;
|
|
check_property_socket_sync(ctx->object, properties, md);
|
|
|
|
tree.ensure_topology_cache();
|
|
const bNode *output_node = tree.group_output_node();
|
|
if (output_node == nullptr) {
|
|
BKE_modifier_set_error(ctx->object, md, "Node group must have a group output node");
|
|
geometry_set.clear();
|
|
return;
|
|
}
|
|
|
|
Span<const bNodeSocket *> group_outputs = output_node->input_sockets().drop_back(1);
|
|
if (group_outputs.is_empty()) {
|
|
BKE_modifier_set_error(ctx->object, md, "Node group must have an output socket");
|
|
geometry_set.clear();
|
|
return;
|
|
}
|
|
|
|
const bNodeSocket *first_output_socket = group_outputs[0];
|
|
if (!STREQ(first_output_socket->idname, "NodeSocketGeometry")) {
|
|
BKE_modifier_set_error(ctx->object, md, "Node group's first output must be a geometry");
|
|
geometry_set.clear();
|
|
return;
|
|
}
|
|
|
|
const nodes::GeometryNodesLazyFunctionGraphInfo *lf_graph_info =
|
|
nodes::ensure_geometry_nodes_lazy_function_graph(tree);
|
|
if (lf_graph_info == nullptr) {
|
|
BKE_modifier_set_error(ctx->object, md, "Cannot evaluate node group");
|
|
geometry_set.clear();
|
|
return;
|
|
}
|
|
|
|
bool use_orig_index_verts = false;
|
|
bool use_orig_index_edges = false;
|
|
bool use_orig_index_faces = false;
|
|
if (const Mesh *mesh = geometry_set.get_mesh()) {
|
|
use_orig_index_verts = CustomData_has_layer(&mesh->vert_data, CD_ORIGINDEX);
|
|
use_orig_index_edges = CustomData_has_layer(&mesh->edge_data, CD_ORIGINDEX);
|
|
use_orig_index_faces = CustomData_has_layer(&mesh->face_data, CD_ORIGINDEX);
|
|
}
|
|
|
|
nodes::GeoNodesCallData call_data;
|
|
|
|
nodes::GeoNodesModifierData modifier_eval_data{};
|
|
modifier_eval_data.depsgraph = ctx->depsgraph;
|
|
modifier_eval_data.self_object = ctx->object;
|
|
auto eval_log = std::make_unique<geo_log::GeoModifierLog>();
|
|
call_data.modifier_data = &modifier_eval_data;
|
|
|
|
NodesModifierSimulationParams simulation_params(*nmd, *ctx);
|
|
call_data.simulation_params = &simulation_params;
|
|
NodesModifierBakeParams bake_params{*nmd, *ctx};
|
|
call_data.bake_params = &bake_params;
|
|
|
|
Set<ComputeContextHash> socket_log_contexts;
|
|
if (logging_enabled(ctx)) {
|
|
call_data.eval_log = eval_log.get();
|
|
|
|
find_socket_log_contexts(*nmd, *ctx, socket_log_contexts);
|
|
call_data.socket_log_contexts = &socket_log_contexts;
|
|
}
|
|
|
|
nodes::GeoNodesSideEffectNodes side_effect_nodes;
|
|
find_side_effect_nodes(*nmd, *ctx, side_effect_nodes, socket_log_contexts);
|
|
call_data.side_effect_nodes = &side_effect_nodes;
|
|
|
|
bke::ModifierComputeContext modifier_compute_context{nullptr, *nmd};
|
|
|
|
geometry_set = nodes::execute_geometry_nodes_on_geometry(
|
|
tree, properties, modifier_compute_context, call_data, std::move(geometry_set));
|
|
|
|
if (logging_enabled(ctx)) {
|
|
nmd_orig->runtime->eval_log = std::move(eval_log);
|
|
}
|
|
|
|
if (DEG_is_active(ctx->depsgraph) && !(ctx->flag & MOD_APPLY_TO_ORIGINAL)) {
|
|
add_data_block_items_writeback(*ctx, *nmd, *nmd_orig, simulation_params, bake_params);
|
|
}
|
|
|
|
if (use_orig_index_verts || use_orig_index_edges || use_orig_index_faces) {
|
|
if (Mesh *mesh = geometry_set.get_mesh_for_write()) {
|
|
/* Add #CD_ORIGINDEX layers if they don't exist already. This is required because the
|
|
* #eModifierTypeFlag_SupportsMapping flag is set. If the layers did not exist before, it is
|
|
* assumed that the output mesh does not have a mapping to the original mesh. */
|
|
if (use_orig_index_verts) {
|
|
CustomData_add_layer(&mesh->vert_data, CD_ORIGINDEX, CD_SET_DEFAULT, mesh->verts_num);
|
|
}
|
|
if (use_orig_index_edges) {
|
|
CustomData_add_layer(&mesh->edge_data, CD_ORIGINDEX, CD_SET_DEFAULT, mesh->edges_num);
|
|
}
|
|
if (use_orig_index_faces) {
|
|
CustomData_add_layer(&mesh->face_data, CD_ORIGINDEX, CD_SET_DEFAULT, mesh->faces_num);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh)
|
|
{
|
|
bke::GeometrySet geometry_set = bke::GeometrySet::from_mesh(
|
|
mesh, bke::GeometryOwnershipType::Editable);
|
|
|
|
modifyGeometry(md, ctx, geometry_set);
|
|
|
|
bke::MeshComponent &mesh_component = geometry_set.get_component_for_write<bke::MeshComponent>();
|
|
if (mesh_component.get() != mesh) {
|
|
/* If this is the same as the input mesh, it's not necessary to make a copy of it even if it's
|
|
* not owned by the geometry set. That's because we know that the caller manages the ownership
|
|
* of the mesh. */
|
|
mesh_component.ensure_owns_direct_data();
|
|
}
|
|
Mesh *new_mesh = mesh_component.release();
|
|
if (new_mesh == nullptr) {
|
|
return BKE_mesh_new_nomain(0, 0, 0, 0);
|
|
}
|
|
return new_mesh;
|
|
}
|
|
|
|
static void modify_geometry_set(ModifierData *md,
|
|
const ModifierEvalContext *ctx,
|
|
bke::GeometrySet *geometry_set)
|
|
{
|
|
modifyGeometry(md, ctx, *geometry_set);
|
|
}
|
|
|
|
struct SocketSearchData {
|
|
uint32_t object_session_uid;
|
|
char modifier_name[MAX_NAME];
|
|
char socket_identifier[MAX_NAME];
|
|
bool is_output;
|
|
};
|
|
/* This class must not have a destructor, since it is used by buttons and freed with #MEM_freeN. */
|
|
BLI_STATIC_ASSERT(std::is_trivially_destructible_v<SocketSearchData>, "");
|
|
|
|
struct DrawGroupInputsContext {
|
|
const bContext &C;
|
|
NodesModifierData &nmd;
|
|
nodes::PropertiesVectorSet properties;
|
|
PointerRNA *md_ptr;
|
|
PointerRNA *bmain_ptr;
|
|
Array<bool> input_usages;
|
|
};
|
|
|
|
static NodesModifierData *get_modifier_data(Main &bmain,
|
|
const wmWindowManager &wm,
|
|
const SocketSearchData &data)
|
|
{
|
|
if (ED_screen_animation_playing(&wm)) {
|
|
/* Work around an issue where the attribute search exec function has stale pointers when data
|
|
* is reallocated when evaluating the node tree, causing a crash. This would be solved by
|
|
* allowing the UI search data to own arbitrary memory rather than just referencing it. */
|
|
return nullptr;
|
|
}
|
|
|
|
const Object *object = (Object *)BKE_libblock_find_session_uid(
|
|
&bmain, ID_OB, data.object_session_uid);
|
|
if (object == nullptr) {
|
|
return nullptr;
|
|
}
|
|
ModifierData *md = BKE_modifiers_findby_name(object, data.modifier_name);
|
|
if (md == nullptr) {
|
|
return nullptr;
|
|
}
|
|
BLI_assert(md->type == eModifierType_Nodes);
|
|
return reinterpret_cast<NodesModifierData *>(md);
|
|
}
|
|
|
|
static geo_log::GeoTreeLog *get_root_tree_log(const NodesModifierData &nmd)
|
|
{
|
|
if (!nmd.runtime->eval_log) {
|
|
return nullptr;
|
|
}
|
|
bke::ModifierComputeContext compute_context{nullptr, nmd};
|
|
return &nmd.runtime->eval_log->get_tree_log(compute_context.hash());
|
|
}
|
|
|
|
static void attribute_search_update_fn(
|
|
const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first)
|
|
{
|
|
SocketSearchData &data = *static_cast<SocketSearchData *>(arg);
|
|
const NodesModifierData *nmd = get_modifier_data(*CTX_data_main(C), *CTX_wm_manager(C), data);
|
|
if (nmd == nullptr) {
|
|
return;
|
|
}
|
|
if (nmd->node_group == nullptr) {
|
|
return;
|
|
}
|
|
geo_log::GeoTreeLog *tree_log = get_root_tree_log(*nmd);
|
|
if (tree_log == nullptr) {
|
|
return;
|
|
}
|
|
tree_log->ensure_existing_attributes();
|
|
nmd->node_group->ensure_topology_cache();
|
|
|
|
Vector<const bNodeSocket *> sockets_to_check;
|
|
if (data.is_output) {
|
|
for (const bNode *node : nmd->node_group->nodes_by_type("NodeGroupOutput")) {
|
|
for (const bNodeSocket *socket : node->input_sockets()) {
|
|
if (socket->type == SOCK_GEOMETRY) {
|
|
sockets_to_check.append(socket);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (const bNode *node : nmd->node_group->group_input_nodes()) {
|
|
for (const bNodeSocket *socket : node->output_sockets()) {
|
|
if (socket->type == SOCK_GEOMETRY) {
|
|
sockets_to_check.append(socket);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Set<StringRef> names;
|
|
Vector<const geo_log::GeometryAttributeInfo *> attributes;
|
|
for (const bNodeSocket *socket : sockets_to_check) {
|
|
const geo_log::ValueLog *value_log = tree_log->find_socket_value_log(*socket);
|
|
if (value_log == nullptr) {
|
|
continue;
|
|
}
|
|
if (const auto *geo_log = dynamic_cast<const geo_log::GeometryInfoLog *>(value_log)) {
|
|
for (const geo_log::GeometryAttributeInfo &attribute : geo_log->attributes) {
|
|
if (names.add(attribute.name)) {
|
|
attributes.append(&attribute);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ui::attribute_search_add_items(str, data.is_output, attributes.as_span(), items, is_first);
|
|
}
|
|
|
|
static void attribute_search_exec_fn(bContext *C, void *data_v, void *item_v)
|
|
{
|
|
if (item_v == nullptr) {
|
|
return;
|
|
}
|
|
SocketSearchData &data = *static_cast<SocketSearchData *>(data_v);
|
|
const auto &item = *static_cast<const geo_log::GeometryAttributeInfo *>(item_v);
|
|
const NodesModifierData *nmd = get_modifier_data(*CTX_data_main(C), *CTX_wm_manager(C), data);
|
|
if (nmd == nullptr) {
|
|
return;
|
|
}
|
|
|
|
const std::string attribute_prop_name = data.socket_identifier +
|
|
nodes::input_attribute_name_suffix;
|
|
IDProperty &name_property = *IDP_GetPropertyFromGroup(nmd->settings.properties,
|
|
attribute_prop_name);
|
|
IDP_AssignString(&name_property, item.name.c_str());
|
|
|
|
ED_undo_push(C, "Assign Attribute Name");
|
|
}
|
|
|
|
static void add_attribute_search_button(DrawGroupInputsContext &ctx,
|
|
uiLayout *layout,
|
|
const StringRefNull rna_path_attribute_name,
|
|
const bNodeTreeInterfaceSocket &socket,
|
|
const bool is_output)
|
|
{
|
|
if (!ctx.nmd.runtime->eval_log) {
|
|
layout->prop(ctx.md_ptr, rna_path_attribute_name, UI_ITEM_NONE, "", ICON_NONE);
|
|
return;
|
|
}
|
|
|
|
uiBlock *block = uiLayoutGetBlock(layout);
|
|
uiBut *but = uiDefIconTextButR(block,
|
|
UI_BTYPE_SEARCH_MENU,
|
|
0,
|
|
ICON_NONE,
|
|
"",
|
|
0,
|
|
0,
|
|
10 * UI_UNIT_X, /* Dummy value, replaced by layout system. */
|
|
UI_UNIT_Y,
|
|
ctx.md_ptr,
|
|
rna_path_attribute_name,
|
|
0,
|
|
0.0f,
|
|
0.0f,
|
|
StringRef(socket.description));
|
|
|
|
const Object *object = ed::object::context_object(&ctx.C);
|
|
BLI_assert(object != nullptr);
|
|
if (object == nullptr) {
|
|
return;
|
|
}
|
|
|
|
SocketSearchData *data = MEM_callocN<SocketSearchData>(__func__);
|
|
data->object_session_uid = object->id.session_uid;
|
|
STRNCPY(data->modifier_name, ctx.nmd.modifier.name);
|
|
STRNCPY(data->socket_identifier, socket.identifier);
|
|
data->is_output = is_output;
|
|
|
|
UI_but_func_search_set_results_are_suggestions(but, true);
|
|
UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP);
|
|
UI_but_func_search_set(but,
|
|
nullptr,
|
|
attribute_search_update_fn,
|
|
static_cast<void *>(data),
|
|
true,
|
|
nullptr,
|
|
attribute_search_exec_fn,
|
|
nullptr);
|
|
|
|
char *attribute_name = RNA_string_get_alloc(
|
|
ctx.md_ptr, rna_path_attribute_name.c_str(), nullptr, 0, nullptr);
|
|
const bool access_allowed = bke::allow_procedural_attribute_access(attribute_name);
|
|
MEM_freeN(attribute_name);
|
|
if (!access_allowed) {
|
|
UI_but_flag_enable(but, UI_BUT_REDALERT);
|
|
}
|
|
}
|
|
|
|
static void add_attribute_search_or_value_buttons(DrawGroupInputsContext &ctx,
|
|
uiLayout *layout,
|
|
const StringRef socket_id_esc,
|
|
const StringRefNull rna_path,
|
|
const bNodeTreeInterfaceSocket &socket)
|
|
{
|
|
const bke::bNodeSocketType *typeinfo = socket.socket_typeinfo();
|
|
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
|
|
const std::string rna_path_attribute_name = fmt::format(
|
|
"[\"{}{}\"]", socket_id_esc, nodes::input_attribute_name_suffix);
|
|
|
|
/* We're handling this manually in this case. */
|
|
uiLayoutSetPropDecorate(layout, false);
|
|
|
|
uiLayout *split = &layout->split(0.4f, false);
|
|
uiLayout *name_row = &split->row(false);
|
|
uiLayoutSetAlignment(name_row, UI_LAYOUT_ALIGN_RIGHT);
|
|
|
|
uiLayout *prop_row = nullptr;
|
|
|
|
const std::optional<StringRef> attribute_name = nodes::input_attribute_name_get(ctx.properties,
|
|
socket);
|
|
if (type == SOCK_BOOLEAN && !attribute_name) {
|
|
name_row->label("", ICON_NONE);
|
|
prop_row = &split->row(true);
|
|
}
|
|
else {
|
|
prop_row = &layout->row(true);
|
|
}
|
|
|
|
if (type == SOCK_BOOLEAN) {
|
|
uiLayoutSetPropSep(prop_row, false);
|
|
uiLayoutSetAlignment(prop_row, UI_LAYOUT_ALIGN_EXPAND);
|
|
}
|
|
|
|
if (attribute_name) {
|
|
name_row->label(socket.name ? IFACE_(socket.name) : "", ICON_NONE);
|
|
prop_row = &split->row(true);
|
|
add_attribute_search_button(ctx, prop_row, rna_path_attribute_name, socket, false);
|
|
layout->label("", ICON_BLANK1);
|
|
}
|
|
else {
|
|
const char *name = socket.name ? IFACE_(socket.name) : "";
|
|
prop_row->prop(ctx.md_ptr, rna_path, UI_ITEM_NONE, name, ICON_NONE);
|
|
uiItemDecoratorR(layout, ctx.md_ptr, rna_path.c_str(), -1);
|
|
}
|
|
|
|
PointerRNA props;
|
|
uiItemFullO(prop_row,
|
|
"object.geometry_nodes_input_attribute_toggle",
|
|
"",
|
|
ICON_SPREADSHEET,
|
|
nullptr,
|
|
WM_OP_INVOKE_DEFAULT,
|
|
UI_ITEM_NONE,
|
|
&props);
|
|
RNA_string_set(&props, "modifier_name", ctx.nmd.modifier.name);
|
|
RNA_string_set(&props, "input_name", socket.identifier);
|
|
}
|
|
|
|
static void layer_name_search_update_fn(
|
|
const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first)
|
|
{
|
|
const SocketSearchData &data = *static_cast<SocketSearchData *>(arg);
|
|
const NodesModifierData *nmd = get_modifier_data(*CTX_data_main(C), *CTX_wm_manager(C), data);
|
|
if (nmd == nullptr) {
|
|
return;
|
|
}
|
|
if (nmd->node_group == nullptr) {
|
|
return;
|
|
}
|
|
geo_log::GeoTreeLog *tree_log = get_root_tree_log(*nmd);
|
|
if (tree_log == nullptr) {
|
|
return;
|
|
}
|
|
tree_log->ensure_layer_names();
|
|
nmd->node_group->ensure_topology_cache();
|
|
|
|
Vector<const bNodeSocket *> sockets_to_check;
|
|
for (const bNode *node : nmd->node_group->group_input_nodes()) {
|
|
for (const bNodeSocket *socket : node->output_sockets()) {
|
|
if (socket->type == SOCK_GEOMETRY) {
|
|
sockets_to_check.append(socket);
|
|
}
|
|
}
|
|
}
|
|
|
|
Set<StringRef> names;
|
|
Vector<const std::string *> layer_names;
|
|
for (const bNodeSocket *socket : sockets_to_check) {
|
|
const geo_log::ValueLog *value_log = tree_log->find_socket_value_log(*socket);
|
|
if (value_log == nullptr) {
|
|
continue;
|
|
}
|
|
if (const auto *geo_log = dynamic_cast<const geo_log::GeometryInfoLog *>(value_log)) {
|
|
if (const std::optional<geo_log::GeometryInfoLog::GreasePencilInfo> &grease_pencil_info =
|
|
geo_log->grease_pencil_info)
|
|
{
|
|
for (const std::string &name : grease_pencil_info->layer_names) {
|
|
if (names.add(name)) {
|
|
layer_names.append(&name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
BLI_assert(items);
|
|
ui::grease_pencil_layer_search_add_items(str, layer_names.as_span(), *items, is_first);
|
|
}
|
|
|
|
static void layer_name_search_exec_fn(bContext *C, void *data_v, void *item_v)
|
|
{
|
|
const SocketSearchData &data = *static_cast<SocketSearchData *>(data_v);
|
|
const std::string *item = static_cast<std::string *>(item_v);
|
|
if (item == nullptr) {
|
|
return;
|
|
}
|
|
const NodesModifierData *nmd = get_modifier_data(*CTX_data_main(C), *CTX_wm_manager(C), data);
|
|
if (nmd == nullptr) {
|
|
return;
|
|
}
|
|
|
|
IDProperty &name_property = *IDP_GetPropertyFromGroup(nmd->settings.properties,
|
|
data.socket_identifier);
|
|
IDP_AssignString(&name_property, item->c_str());
|
|
|
|
ED_undo_push(C, "Assign Layer Name");
|
|
}
|
|
|
|
static void add_layer_name_search_button(DrawGroupInputsContext &ctx,
|
|
uiLayout *layout,
|
|
const StringRefNull socket_id_esc,
|
|
const bNodeTreeInterfaceSocket &socket)
|
|
{
|
|
const std::string rna_path = fmt::format("[\"{}\"]", socket_id_esc);
|
|
if (!ctx.nmd.runtime->eval_log) {
|
|
layout->prop(ctx.md_ptr, rna_path, UI_ITEM_NONE, "", ICON_NONE);
|
|
return;
|
|
}
|
|
|
|
uiLayoutSetPropDecorate(layout, false);
|
|
|
|
uiLayout *split = &layout->split(0.4f, false);
|
|
uiLayout *name_row = &split->row(false);
|
|
uiLayoutSetAlignment(name_row, UI_LAYOUT_ALIGN_RIGHT);
|
|
|
|
name_row->label(socket.name ? IFACE_(socket.name) : "", ICON_NONE);
|
|
uiLayout *prop_row = &split->row(true);
|
|
|
|
uiBlock *block = uiLayoutGetBlock(prop_row);
|
|
uiBut *but = uiDefIconTextButR(block,
|
|
UI_BTYPE_SEARCH_MENU,
|
|
0,
|
|
ICON_OUTLINER_DATA_GP_LAYER,
|
|
"",
|
|
0,
|
|
0,
|
|
10 * UI_UNIT_X, /* Dummy value, replaced by layout system. */
|
|
UI_UNIT_Y,
|
|
ctx.md_ptr,
|
|
rna_path,
|
|
0,
|
|
0.0f,
|
|
0.0f,
|
|
StringRef(socket.description));
|
|
UI_but_placeholder_set(but, "Layer");
|
|
layout->label("", ICON_BLANK1);
|
|
|
|
const Object *object = ed::object::context_object(&ctx.C);
|
|
BLI_assert(object != nullptr);
|
|
if (object == nullptr) {
|
|
return;
|
|
}
|
|
|
|
SocketSearchData *data = MEM_callocN<SocketSearchData>(__func__);
|
|
data->object_session_uid = object->id.session_uid;
|
|
STRNCPY(data->modifier_name, ctx.nmd.modifier.name);
|
|
STRNCPY(data->socket_identifier, socket.identifier);
|
|
|
|
UI_but_func_search_set_results_are_suggestions(but, true);
|
|
UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP);
|
|
UI_but_func_search_set(but,
|
|
nullptr,
|
|
layer_name_search_update_fn,
|
|
static_cast<void *>(data),
|
|
true,
|
|
nullptr,
|
|
layer_name_search_exec_fn,
|
|
nullptr);
|
|
}
|
|
|
|
/* Drawing the properties manually with #uiLayout::prop instead of #uiDefAutoButsRNA allows using
|
|
* the node socket identifier for the property names, since they are unique, but also having
|
|
* the correct label displayed in the UI. */
|
|
static void draw_property_for_socket(DrawGroupInputsContext &ctx,
|
|
uiLayout *layout,
|
|
const bNodeTreeInterfaceSocket &socket)
|
|
{
|
|
const StringRefNull identifier = socket.identifier;
|
|
/* The property should be created in #MOD_nodes_update_interface with the correct type. */
|
|
IDProperty *property = ctx.properties.lookup_key_default_as(identifier, nullptr);
|
|
|
|
/* IDProperties can be removed with python, so there could be a situation where
|
|
* there isn't a property for a socket or it doesn't have the correct type. */
|
|
if (property == nullptr || !nodes::id_property_type_matches_socket(socket, *property)) {
|
|
return;
|
|
}
|
|
|
|
const std::string socket_id_esc = BLI_str_escape(identifier.c_str());
|
|
const std::string rna_path = fmt::format("[\"{}\"]", socket_id_esc);
|
|
|
|
const int input_index = ctx.nmd.node_group->interface_input_index(socket);
|
|
|
|
uiLayout *row = &layout->row(true);
|
|
uiLayoutSetPropDecorate(row, true);
|
|
uiLayoutSetActive(row, ctx.input_usages[input_index]);
|
|
|
|
/* Use #uiItemPointerR to draw pointer properties because #uiLayout::prop would not have enough
|
|
* information about what type of ID to select for editing the values. This is because
|
|
* pointer IDProperties contain no information about their type. */
|
|
const bke::bNodeSocketType *typeinfo = socket.socket_typeinfo();
|
|
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
|
|
const char *name = socket.name ? IFACE_(socket.name) : "";
|
|
switch (type) {
|
|
case SOCK_OBJECT: {
|
|
uiItemPointerR(row, ctx.md_ptr, rna_path, ctx.bmain_ptr, "objects", name, ICON_OBJECT_DATA);
|
|
break;
|
|
}
|
|
case SOCK_COLLECTION: {
|
|
uiItemPointerR(
|
|
row, ctx.md_ptr, rna_path, ctx.bmain_ptr, "collections", name, ICON_OUTLINER_COLLECTION);
|
|
break;
|
|
}
|
|
case SOCK_MATERIAL: {
|
|
uiItemPointerR(row, ctx.md_ptr, rna_path, ctx.bmain_ptr, "materials", name, ICON_MATERIAL);
|
|
break;
|
|
}
|
|
case SOCK_TEXTURE: {
|
|
uiItemPointerR(row, ctx.md_ptr, rna_path, ctx.bmain_ptr, "textures", name, ICON_TEXTURE);
|
|
break;
|
|
}
|
|
case SOCK_IMAGE: {
|
|
uiTemplateID(row,
|
|
&ctx.C,
|
|
ctx.md_ptr,
|
|
rna_path,
|
|
"image.new",
|
|
"image.open",
|
|
nullptr,
|
|
UI_TEMPLATE_ID_FILTER_ALL,
|
|
false,
|
|
name);
|
|
break;
|
|
}
|
|
case SOCK_MENU: {
|
|
if (socket.flag & NODE_INTERFACE_SOCKET_MENU_EXPANDED) {
|
|
/* Use a single space when the name is empty to work around a bug with expanded enums. Also
|
|
* see #ui_item_enum_expand_exec. */
|
|
row->prop(ctx.md_ptr,
|
|
rna_path,
|
|
UI_ITEM_R_EXPAND,
|
|
StringRef(name).is_empty() ? " " : name,
|
|
ICON_NONE);
|
|
}
|
|
else {
|
|
row->prop(ctx.md_ptr, rna_path, UI_ITEM_NONE, name, ICON_NONE);
|
|
}
|
|
break;
|
|
}
|
|
case SOCK_BOOLEAN: {
|
|
if (is_layer_selection_field(socket)) {
|
|
add_layer_name_search_button(ctx, row, socket_id_esc, socket);
|
|
/* Adds a spacing at the end of the row. */
|
|
row->label("", ICON_BLANK1);
|
|
break;
|
|
}
|
|
ATTR_FALLTHROUGH;
|
|
}
|
|
default: {
|
|
if (nodes::input_has_attribute_toggle(*ctx.nmd.node_group, input_index)) {
|
|
add_attribute_search_or_value_buttons(ctx, row, socket_id_esc, rna_path, socket);
|
|
}
|
|
else {
|
|
row->prop(ctx.md_ptr, rna_path, UI_ITEM_NONE, name, ICON_NONE);
|
|
}
|
|
}
|
|
}
|
|
if (!nodes::input_has_attribute_toggle(*ctx.nmd.node_group, input_index)) {
|
|
row->label("", ICON_BLANK1);
|
|
}
|
|
}
|
|
|
|
static void draw_property_for_output_socket(DrawGroupInputsContext &ctx,
|
|
uiLayout *layout,
|
|
const bNodeTreeInterfaceSocket &socket)
|
|
{
|
|
const StringRefNull identifier = socket.identifier;
|
|
char socket_id_esc[MAX_NAME * 2];
|
|
BLI_str_escape(socket_id_esc, identifier.c_str(), sizeof(socket_id_esc));
|
|
const std::string rna_path_attribute_name = fmt::format(
|
|
"[\"{}{}\"]", socket_id_esc, nodes::input_attribute_name_suffix);
|
|
|
|
uiLayout *split = &layout->split(0.4f, false);
|
|
uiLayout *name_row = &split->row(false);
|
|
uiLayoutSetAlignment(name_row, UI_LAYOUT_ALIGN_RIGHT);
|
|
name_row->label(socket.name ? socket.name : "", ICON_NONE);
|
|
|
|
uiLayout *row = &split->row(true);
|
|
add_attribute_search_button(ctx, row, rna_path_attribute_name, socket, true);
|
|
}
|
|
|
|
static NodesModifierPanel *find_panel_by_id(NodesModifierData &nmd, const int id)
|
|
{
|
|
for (const int i : IndexRange(nmd.panels_num)) {
|
|
if (nmd.panels[i].id == id) {
|
|
return &nmd.panels[i];
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static bool interface_panel_has_socket(const bNodeTreeInterfacePanel &interface_panel)
|
|
{
|
|
for (const bNodeTreeInterfaceItem *item : interface_panel.items()) {
|
|
if (item->item_type == NODE_INTERFACE_SOCKET) {
|
|
const bNodeTreeInterfaceSocket &socket = *reinterpret_cast<const bNodeTreeInterfaceSocket *>(
|
|
item);
|
|
if ((socket.flag &
|
|
(NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER | NODE_INTERFACE_SOCKET_OUTPUT)) == 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
if (item->item_type == NODE_INTERFACE_PANEL) {
|
|
if (interface_panel_has_socket(*reinterpret_cast<const bNodeTreeInterfacePanel *>(item))) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool interface_panel_affects_output(DrawGroupInputsContext &ctx,
|
|
const bNodeTreeInterfacePanel &panel)
|
|
{
|
|
for (const bNodeTreeInterfaceItem *item : panel.items()) {
|
|
if (item->item_type == NODE_INTERFACE_SOCKET) {
|
|
const auto &socket = *reinterpret_cast<const bNodeTreeInterfaceSocket *>(item);
|
|
if (socket.flag & NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER) {
|
|
continue;
|
|
}
|
|
if (!(socket.flag & NODE_INTERFACE_SOCKET_INPUT)) {
|
|
continue;
|
|
}
|
|
const int input_index = ctx.nmd.node_group->interface_input_index(socket);
|
|
if (ctx.input_usages[input_index]) {
|
|
return true;
|
|
}
|
|
}
|
|
else if (item->item_type == NODE_INTERFACE_PANEL) {
|
|
const auto &sub_interface_panel = *reinterpret_cast<const bNodeTreeInterfacePanel *>(item);
|
|
if (interface_panel_affects_output(ctx, sub_interface_panel)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void draw_interface_panel_content(DrawGroupInputsContext &ctx,
|
|
uiLayout *layout,
|
|
const bNodeTreeInterfacePanel &interface_panel,
|
|
const bool skip_first = false)
|
|
{
|
|
for (const bNodeTreeInterfaceItem *item : interface_panel.items().drop_front(skip_first ? 1 : 0))
|
|
{
|
|
if (item->item_type == NODE_INTERFACE_PANEL) {
|
|
const auto &sub_interface_panel = *reinterpret_cast<const bNodeTreeInterfacePanel *>(item);
|
|
if (!interface_panel_has_socket(sub_interface_panel)) {
|
|
continue;
|
|
}
|
|
NodesModifierPanel *panel = find_panel_by_id(ctx.nmd, sub_interface_panel.identifier);
|
|
PointerRNA panel_ptr = RNA_pointer_create_discrete(
|
|
ctx.md_ptr->owner_id, &RNA_NodesModifierPanel, panel);
|
|
PanelLayout panel_layout;
|
|
bool skip_first = false;
|
|
/* Check if the panel should have a toggle in the header. */
|
|
const bNodeTreeInterfaceSocket *toggle_socket = sub_interface_panel.header_toggle_socket();
|
|
if (toggle_socket && !(toggle_socket->flag & NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER)) {
|
|
const StringRefNull identifier = toggle_socket->identifier;
|
|
IDProperty *property = ctx.properties.lookup_key_default_as(identifier, nullptr);
|
|
/* IDProperties can be removed with python, so there could be a situation where
|
|
* there isn't a property for a socket or it doesn't have the correct type. */
|
|
if (property == nullptr ||
|
|
!nodes::id_property_type_matches_socket(*toggle_socket, *property))
|
|
{
|
|
continue;
|
|
}
|
|
char socket_id_esc[MAX_NAME * 2];
|
|
BLI_str_escape(socket_id_esc, identifier.c_str(), sizeof(socket_id_esc));
|
|
|
|
char rna_path[sizeof(socket_id_esc) + 4];
|
|
SNPRINTF(rna_path, "[\"%s\"]", socket_id_esc);
|
|
|
|
panel_layout = layout->panel_prop_with_bool_header(
|
|
&ctx.C, &panel_ptr, "is_open", ctx.md_ptr, rna_path, IFACE_(sub_interface_panel.name));
|
|
skip_first = true;
|
|
}
|
|
else {
|
|
panel_layout = layout->panel_prop(&ctx.C, &panel_ptr, "is_open");
|
|
panel_layout.header->label(IFACE_(sub_interface_panel.name), ICON_NONE);
|
|
}
|
|
if (!interface_panel_affects_output(ctx, sub_interface_panel)) {
|
|
uiLayoutSetActive(panel_layout.header, false);
|
|
}
|
|
uiLayoutSetTooltipFunc(
|
|
panel_layout.header,
|
|
[](bContext * /*C*/, void *panel_arg, const StringRef /*tip*/) -> std::string {
|
|
const auto *panel = static_cast<bNodeTreeInterfacePanel *>(panel_arg);
|
|
return StringRef(panel->description);
|
|
},
|
|
const_cast<bNodeTreeInterfacePanel *>(&sub_interface_panel),
|
|
nullptr,
|
|
nullptr);
|
|
if (panel_layout.body) {
|
|
draw_interface_panel_content(ctx, panel_layout.body, sub_interface_panel, skip_first);
|
|
}
|
|
}
|
|
else {
|
|
const auto &interface_socket = *reinterpret_cast<const bNodeTreeInterfaceSocket *>(item);
|
|
if (interface_socket.flag & NODE_INTERFACE_SOCKET_INPUT) {
|
|
if (!(interface_socket.flag & NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER)) {
|
|
draw_property_for_socket(ctx, layout, interface_socket);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool has_output_attribute(const NodesModifierData &nmd)
|
|
{
|
|
if (!nmd.node_group) {
|
|
return false;
|
|
}
|
|
for (const bNodeTreeInterfaceSocket *interface_socket : nmd.node_group->interface_outputs()) {
|
|
const bke::bNodeSocketType *typeinfo = interface_socket->socket_typeinfo();
|
|
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
|
|
if (nodes::socket_type_has_attribute_toggle(type)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void draw_output_attributes_panel(DrawGroupInputsContext &ctx, uiLayout *layout)
|
|
{
|
|
if (ctx.nmd.node_group != nullptr && ctx.nmd.settings.properties != nullptr) {
|
|
for (const bNodeTreeInterfaceSocket *socket : ctx.nmd.node_group->interface_outputs()) {
|
|
const bke::bNodeSocketType *typeinfo = socket->socket_typeinfo();
|
|
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) :
|
|
SOCK_CUSTOM;
|
|
if (nodes::socket_type_has_attribute_toggle(type)) {
|
|
draw_property_for_output_socket(ctx, layout, *socket);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void draw_bake_panel(uiLayout *layout, PointerRNA *modifier_ptr)
|
|
{
|
|
uiLayout *col = &layout->column(false);
|
|
uiLayoutSetPropSep(col, true);
|
|
uiLayoutSetPropDecorate(col, false);
|
|
col->prop(modifier_ptr, "bake_target", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
col->prop(modifier_ptr, "bake_directory", UI_ITEM_NONE, IFACE_("Bake Path"), ICON_NONE);
|
|
}
|
|
|
|
static void draw_named_attributes_panel(uiLayout *layout, NodesModifierData &nmd)
|
|
{
|
|
if (G.is_rendering) {
|
|
/* Avoid accessing this data while baking in a separate thread. */
|
|
return;
|
|
}
|
|
geo_log::GeoTreeLog *tree_log = get_root_tree_log(nmd);
|
|
if (tree_log == nullptr) {
|
|
return;
|
|
}
|
|
|
|
tree_log->ensure_used_named_attributes();
|
|
const Map<StringRefNull, geo_log::NamedAttributeUsage> &usage_by_attribute =
|
|
tree_log->used_named_attributes;
|
|
|
|
if (usage_by_attribute.is_empty()) {
|
|
layout->label(RPT_("No named attributes used"), ICON_INFO);
|
|
return;
|
|
}
|
|
|
|
struct NameWithUsage {
|
|
StringRefNull name;
|
|
geo_log::NamedAttributeUsage usage;
|
|
};
|
|
|
|
Vector<NameWithUsage> sorted_used_attribute;
|
|
for (auto &&item : usage_by_attribute.items()) {
|
|
sorted_used_attribute.append({item.key, item.value});
|
|
}
|
|
std::sort(sorted_used_attribute.begin(),
|
|
sorted_used_attribute.end(),
|
|
[](const NameWithUsage &a, const NameWithUsage &b) {
|
|
return BLI_strcasecmp_natural(a.name.c_str(), b.name.c_str()) < 0;
|
|
});
|
|
|
|
for (const NameWithUsage &attribute : sorted_used_attribute) {
|
|
const StringRef attribute_name = attribute.name;
|
|
const geo_log::NamedAttributeUsage usage = attribute.usage;
|
|
|
|
/* #uiLayoutRowWithHeading doesn't seem to work in this case. */
|
|
uiLayout *split = &layout->split(0.4f, false);
|
|
|
|
std::stringstream ss;
|
|
Vector<std::string> usages;
|
|
if ((usage & geo_log::NamedAttributeUsage::Read) != geo_log::NamedAttributeUsage::None) {
|
|
usages.append(IFACE_("Read"));
|
|
}
|
|
if ((usage & geo_log::NamedAttributeUsage::Write) != geo_log::NamedAttributeUsage::None) {
|
|
usages.append(IFACE_("Write"));
|
|
}
|
|
if ((usage & geo_log::NamedAttributeUsage::Remove) != geo_log::NamedAttributeUsage::None) {
|
|
usages.append(CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove"));
|
|
}
|
|
for (const int i : usages.index_range()) {
|
|
ss << usages[i];
|
|
if (i < usages.size() - 1) {
|
|
ss << ", ";
|
|
}
|
|
}
|
|
|
|
uiLayout *row = &split->row(false);
|
|
uiLayoutSetAlignment(row, UI_LAYOUT_ALIGN_RIGHT);
|
|
uiLayoutSetActive(row, false);
|
|
row->label(ss.str(), ICON_NONE);
|
|
|
|
row = &split->row(false);
|
|
row->label(attribute_name, ICON_NONE);
|
|
}
|
|
}
|
|
|
|
static void draw_manage_panel(const bContext *C,
|
|
uiLayout *layout,
|
|
PointerRNA *modifier_ptr,
|
|
NodesModifierData &nmd)
|
|
{
|
|
if (uiLayout *panel_layout = layout->panel_prop(
|
|
C, modifier_ptr, "open_bake_panel", IFACE_("Bake")))
|
|
{
|
|
draw_bake_panel(panel_layout, modifier_ptr);
|
|
}
|
|
if (uiLayout *panel_layout = layout->panel_prop(
|
|
C, modifier_ptr, "open_named_attributes_panel", IFACE_("Named Attributes")))
|
|
{
|
|
draw_named_attributes_panel(panel_layout, nmd);
|
|
}
|
|
}
|
|
|
|
static void draw_warnings(const bContext *C,
|
|
const NodesModifierData &nmd,
|
|
uiLayout *layout,
|
|
PointerRNA *md_ptr)
|
|
{
|
|
if (G.is_rendering) {
|
|
/* Avoid accessing this data while baking in a separate thread. */
|
|
return;
|
|
}
|
|
using namespace geo_log;
|
|
GeoTreeLog *tree_log = get_root_tree_log(nmd);
|
|
if (!tree_log) {
|
|
return;
|
|
}
|
|
tree_log->ensure_node_warnings(*CTX_data_main(C));
|
|
const int warnings_num = tree_log->all_warnings.size();
|
|
if (warnings_num == 0) {
|
|
return;
|
|
}
|
|
PanelLayout panel = layout->panel_prop(C, md_ptr, "open_warnings_panel");
|
|
panel.header->label(fmt::format(fmt::runtime(IFACE_("Warnings ({})")), warnings_num).c_str(),
|
|
ICON_NONE);
|
|
if (!panel.body) {
|
|
return;
|
|
}
|
|
Vector<const NodeWarning *> warnings(tree_log->all_warnings.size());
|
|
for (const int i : warnings.index_range()) {
|
|
warnings[i] = &tree_log->all_warnings[i];
|
|
}
|
|
std::sort(warnings.begin(), warnings.end(), [](const NodeWarning *a, const NodeWarning *b) {
|
|
const int severity_a = node_warning_type_severity(a->type);
|
|
const int severity_b = node_warning_type_severity(b->type);
|
|
if (severity_a > severity_b) {
|
|
return true;
|
|
}
|
|
if (severity_a < severity_b) {
|
|
return false;
|
|
}
|
|
return BLI_strcasecmp_natural(a->message.c_str(), b->message.c_str()) < 0;
|
|
});
|
|
|
|
uiLayout *col = &panel.body->column(false);
|
|
for (const NodeWarning *warning : warnings) {
|
|
const int icon = node_warning_type_icon(warning->type);
|
|
col->label(warning->message, icon);
|
|
}
|
|
}
|
|
|
|
static void panel_draw(const bContext *C, Panel *panel)
|
|
{
|
|
uiLayout *layout = panel->layout;
|
|
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr);
|
|
NodesModifierData *nmd = static_cast<NodesModifierData *>(ptr->data);
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
/* Decorators are added manually for supported properties because the
|
|
* attribute/value toggle requires a manually built layout anyway. */
|
|
uiLayoutSetPropDecorate(layout, false);
|
|
|
|
if (!(nmd->flag & NODES_MODIFIER_HIDE_DATABLOCK_SELECTOR)) {
|
|
const char *newop = (nmd->node_group == nullptr) ? "node.new_geometry_node_group_assign" :
|
|
"object.geometry_node_tree_copy_assign";
|
|
uiTemplateID(layout, C, ptr, "node_group", newop, nullptr, nullptr);
|
|
}
|
|
|
|
Main *bmain = CTX_data_main(C);
|
|
PointerRNA bmain_ptr = RNA_main_pointer_create(bmain);
|
|
DrawGroupInputsContext ctx{
|
|
*C, *nmd, nodes::build_properties_vector_set(nmd->settings.properties), ptr, &bmain_ptr};
|
|
|
|
if (nmd->node_group != nullptr && nmd->settings.properties != nullptr) {
|
|
nmd->node_group->ensure_interface_cache();
|
|
ctx.input_usages.reinitialize(nmd->node_group->interface_inputs().size());
|
|
nodes::socket_usage_inference::infer_group_interface_inputs_usage(
|
|
*nmd->node_group, ctx.properties, ctx.input_usages);
|
|
draw_interface_panel_content(ctx, layout, nmd->node_group->tree_interface.root_panel);
|
|
}
|
|
|
|
modifier_error_message_draw(layout, ptr);
|
|
|
|
draw_warnings(C, *nmd, layout, ptr);
|
|
|
|
if (has_output_attribute(*nmd)) {
|
|
if (uiLayout *panel_layout = layout->panel_prop(
|
|
C, ptr, "open_output_attributes_panel", IFACE_("Output Attributes")))
|
|
{
|
|
draw_output_attributes_panel(ctx, panel_layout);
|
|
}
|
|
}
|
|
if (uiLayout *panel_layout = layout->panel_prop(C, ptr, "open_manage_panel", IFACE_("Manage"))) {
|
|
draw_manage_panel(C, panel_layout, ptr, *nmd);
|
|
}
|
|
}
|
|
|
|
static void panel_register(ARegionType *region_type)
|
|
{
|
|
using namespace blender;
|
|
modifier_panel_register(region_type, eModifierType_Nodes, panel_draw);
|
|
}
|
|
|
|
static void blend_write(BlendWriter *writer, const ID * /*id_owner*/, const ModifierData *md)
|
|
{
|
|
const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md);
|
|
|
|
BLO_write_struct(writer, NodesModifierData, nmd);
|
|
|
|
BLO_write_string(writer, nmd->bake_directory);
|
|
|
|
if (nmd->settings.properties != nullptr) {
|
|
Map<IDProperty *, IDPropertyUIDataBool *> boolean_props;
|
|
if (!BLO_write_is_undo(writer)) {
|
|
/* Boolean properties are added automatically for boolean node group inputs. Integer
|
|
* properties are automatically converted to boolean sockets where applicable as well.
|
|
* However, boolean properties will crash old versions of Blender, so convert them to integer
|
|
* properties for writing. The actual value is stored in the same variable for both types */
|
|
LISTBASE_FOREACH (IDProperty *, prop, &nmd->settings.properties->data.group) {
|
|
if (prop->type == IDP_BOOLEAN) {
|
|
boolean_props.add_new(prop, reinterpret_cast<IDPropertyUIDataBool *>(prop->ui_data));
|
|
prop->type = IDP_INT;
|
|
prop->ui_data = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Note that the property settings are based on the socket type info
|
|
* and don't necessarily need to be written, but we can't just free them. */
|
|
IDP_BlendWrite(writer, nmd->settings.properties);
|
|
|
|
BLO_write_struct_array(writer, NodesModifierBake, nmd->bakes_num, nmd->bakes);
|
|
for (const NodesModifierBake &bake : Span(nmd->bakes, nmd->bakes_num)) {
|
|
BLO_write_string(writer, bake.directory);
|
|
|
|
BLO_write_struct_array(
|
|
writer, NodesModifierDataBlock, bake.data_blocks_num, bake.data_blocks);
|
|
for (const NodesModifierDataBlock &item : Span(bake.data_blocks, bake.data_blocks_num)) {
|
|
BLO_write_string(writer, item.id_name);
|
|
BLO_write_string(writer, item.lib_name);
|
|
}
|
|
if (bake.packed) {
|
|
BLO_write_struct(writer, NodesModifierPackedBake, bake.packed);
|
|
BLO_write_struct_array(
|
|
writer, NodesModifierBakeFile, bake.packed->meta_files_num, bake.packed->meta_files);
|
|
BLO_write_struct_array(
|
|
writer, NodesModifierBakeFile, bake.packed->blob_files_num, bake.packed->blob_files);
|
|
const auto write_bake_file = [&](const NodesModifierBakeFile &bake_file) {
|
|
BLO_write_string(writer, bake_file.name);
|
|
if (bake_file.packed_file) {
|
|
BKE_packedfile_blend_write(writer, bake_file.packed_file);
|
|
}
|
|
};
|
|
for (const NodesModifierBakeFile &meta_file :
|
|
Span{bake.packed->meta_files, bake.packed->meta_files_num})
|
|
{
|
|
write_bake_file(meta_file);
|
|
}
|
|
for (const NodesModifierBakeFile &blob_file :
|
|
Span{bake.packed->blob_files, bake.packed->blob_files_num})
|
|
{
|
|
write_bake_file(blob_file);
|
|
}
|
|
}
|
|
}
|
|
BLO_write_struct_array(writer, NodesModifierPanel, nmd->panels_num, nmd->panels);
|
|
|
|
if (!BLO_write_is_undo(writer)) {
|
|
LISTBASE_FOREACH (IDProperty *, prop, &nmd->settings.properties->data.group) {
|
|
if (prop->type == IDP_INT) {
|
|
if (IDPropertyUIDataBool **ui_data = boolean_props.lookup_ptr(prop)) {
|
|
prop->type = IDP_BOOLEAN;
|
|
if (ui_data) {
|
|
prop->ui_data = reinterpret_cast<IDPropertyUIData *>(*ui_data);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void blend_read(BlendDataReader *reader, ModifierData *md)
|
|
{
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
BLO_read_string(reader, &nmd->bake_directory);
|
|
if (nmd->node_group == nullptr) {
|
|
nmd->settings.properties = nullptr;
|
|
}
|
|
else {
|
|
BLO_read_struct(reader, IDProperty, &nmd->settings.properties);
|
|
IDP_BlendDataRead(reader, &nmd->settings.properties);
|
|
}
|
|
|
|
BLO_read_struct_array(reader, NodesModifierBake, nmd->bakes_num, &nmd->bakes);
|
|
|
|
if (nmd->bakes_num > 0 && nmd->bakes == nullptr) {
|
|
/* This case generally shouldn't be allowed to happen. However, there is a bug report with a
|
|
* corrupted .blend file (#123974) that triggers this case. Unfortunately, it's not clear how
|
|
* that could have happened. For now, handle this case more gracefully in release builds, while
|
|
* still crashing in debug builds. */
|
|
nmd->bakes_num = 0;
|
|
BLI_assert_unreachable();
|
|
}
|
|
|
|
for (NodesModifierBake &bake : MutableSpan(nmd->bakes, nmd->bakes_num)) {
|
|
BLO_read_string(reader, &bake.directory);
|
|
|
|
BLO_read_struct_array(reader, NodesModifierDataBlock, bake.data_blocks_num, &bake.data_blocks);
|
|
for (NodesModifierDataBlock &data_block : MutableSpan(bake.data_blocks, bake.data_blocks_num))
|
|
{
|
|
BLO_read_string(reader, &data_block.id_name);
|
|
BLO_read_string(reader, &data_block.lib_name);
|
|
}
|
|
|
|
BLO_read_struct(reader, NodesModifierPackedBake, &bake.packed);
|
|
if (bake.packed) {
|
|
BLO_read_struct_array(
|
|
reader, NodesModifierBakeFile, bake.packed->meta_files_num, &bake.packed->meta_files);
|
|
BLO_read_struct_array(
|
|
reader, NodesModifierBakeFile, bake.packed->blob_files_num, &bake.packed->blob_files);
|
|
const auto read_bake_file = [&](NodesModifierBakeFile &bake_file) {
|
|
BLO_read_string(reader, &bake_file.name);
|
|
if (bake_file.packed_file) {
|
|
BKE_packedfile_blend_read(reader, &bake_file.packed_file, "");
|
|
}
|
|
};
|
|
for (NodesModifierBakeFile &meta_file :
|
|
MutableSpan{bake.packed->meta_files, bake.packed->meta_files_num})
|
|
{
|
|
read_bake_file(meta_file);
|
|
}
|
|
for (NodesModifierBakeFile &blob_file :
|
|
MutableSpan{bake.packed->blob_files, bake.packed->blob_files_num})
|
|
{
|
|
read_bake_file(blob_file);
|
|
}
|
|
}
|
|
}
|
|
BLO_read_struct_array(reader, NodesModifierPanel, nmd->panels_num, &nmd->panels);
|
|
|
|
nmd->runtime = MEM_new<NodesModifierRuntime>(__func__);
|
|
nmd->runtime->cache = std::make_shared<bake::ModifierCache>();
|
|
}
|
|
|
|
static void copy_data(const ModifierData *md, ModifierData *target, const int flag)
|
|
{
|
|
const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md);
|
|
NodesModifierData *tnmd = reinterpret_cast<NodesModifierData *>(target);
|
|
|
|
BKE_modifier_copydata_generic(md, target, flag);
|
|
|
|
if (nmd->bakes) {
|
|
tnmd->bakes = static_cast<NodesModifierBake *>(MEM_dupallocN(nmd->bakes));
|
|
for (const int i : IndexRange(nmd->bakes_num)) {
|
|
NodesModifierBake &bake = tnmd->bakes[i];
|
|
if (bake.directory) {
|
|
bake.directory = BLI_strdup(bake.directory);
|
|
}
|
|
if (bake.data_blocks) {
|
|
bake.data_blocks = static_cast<NodesModifierDataBlock *>(MEM_dupallocN(bake.data_blocks));
|
|
for (const int i : IndexRange(bake.data_blocks_num)) {
|
|
NodesModifierDataBlock &data_block = bake.data_blocks[i];
|
|
if (data_block.id_name) {
|
|
data_block.id_name = BLI_strdup(data_block.id_name);
|
|
}
|
|
if (data_block.lib_name) {
|
|
data_block.lib_name = BLI_strdup(data_block.lib_name);
|
|
}
|
|
}
|
|
}
|
|
if (bake.packed) {
|
|
bake.packed = static_cast<NodesModifierPackedBake *>(MEM_dupallocN(bake.packed));
|
|
const auto copy_bake_files_inplace = [](NodesModifierBakeFile **bake_files,
|
|
const int bake_files_num) {
|
|
if (!*bake_files) {
|
|
return;
|
|
}
|
|
*bake_files = static_cast<NodesModifierBakeFile *>(MEM_dupallocN(*bake_files));
|
|
for (NodesModifierBakeFile &bake_file : MutableSpan{*bake_files, bake_files_num}) {
|
|
bake_file.name = BLI_strdup_null(bake_file.name);
|
|
if (bake_file.packed_file) {
|
|
bake_file.packed_file = BKE_packedfile_duplicate(bake_file.packed_file);
|
|
}
|
|
}
|
|
};
|
|
copy_bake_files_inplace(&bake.packed->meta_files, bake.packed->meta_files_num);
|
|
copy_bake_files_inplace(&bake.packed->blob_files, bake.packed->blob_files_num);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nmd->panels) {
|
|
tnmd->panels = static_cast<NodesModifierPanel *>(MEM_dupallocN(nmd->panels));
|
|
}
|
|
|
|
tnmd->runtime = MEM_new<NodesModifierRuntime>(__func__);
|
|
|
|
if (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) {
|
|
/* Share the simulation cache between the original and evaluated modifier. */
|
|
tnmd->runtime->cache = nmd->runtime->cache;
|
|
/* Keep bake path in the evaluated modifier. */
|
|
tnmd->bake_directory = nmd->bake_directory ? BLI_strdup(nmd->bake_directory) : nullptr;
|
|
}
|
|
else {
|
|
tnmd->runtime->cache = std::make_shared<bake::ModifierCache>();
|
|
/* Clear the bake path when duplicating. */
|
|
tnmd->bake_directory = nullptr;
|
|
}
|
|
|
|
if (nmd->settings.properties != nullptr) {
|
|
tnmd->settings.properties = IDP_CopyProperty_ex(nmd->settings.properties, flag);
|
|
}
|
|
}
|
|
|
|
void nodes_modifier_packed_bake_free(NodesModifierPackedBake *packed_bake)
|
|
{
|
|
const auto free_packed_files = [](NodesModifierBakeFile *files, const int files_num) {
|
|
for (NodesModifierBakeFile &file : MutableSpan{files, files_num}) {
|
|
MEM_SAFE_FREE(file.name);
|
|
if (file.packed_file) {
|
|
BKE_packedfile_free(file.packed_file);
|
|
}
|
|
}
|
|
MEM_SAFE_FREE(files);
|
|
};
|
|
free_packed_files(packed_bake->meta_files, packed_bake->meta_files_num);
|
|
free_packed_files(packed_bake->blob_files, packed_bake->blob_files_num);
|
|
MEM_SAFE_FREE(packed_bake);
|
|
}
|
|
|
|
void nodes_modifier_bake_destruct(NodesModifierBake *bake, const bool do_id_user)
|
|
{
|
|
MEM_SAFE_FREE(bake->directory);
|
|
|
|
for (NodesModifierDataBlock &data_block : MutableSpan(bake->data_blocks, bake->data_blocks_num))
|
|
{
|
|
nodes_modifier_data_block_destruct(&data_block, do_id_user);
|
|
}
|
|
MEM_SAFE_FREE(bake->data_blocks);
|
|
|
|
if (bake->packed) {
|
|
nodes_modifier_packed_bake_free(bake->packed);
|
|
}
|
|
}
|
|
|
|
static void free_data(ModifierData *md)
|
|
{
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
if (nmd->settings.properties != nullptr) {
|
|
IDP_FreeProperty_ex(nmd->settings.properties, false);
|
|
nmd->settings.properties = nullptr;
|
|
}
|
|
|
|
for (NodesModifierBake &bake : MutableSpan(nmd->bakes, nmd->bakes_num)) {
|
|
nodes_modifier_bake_destruct(&bake, false);
|
|
}
|
|
MEM_SAFE_FREE(nmd->bakes);
|
|
|
|
MEM_SAFE_FREE(nmd->panels);
|
|
|
|
MEM_SAFE_FREE(nmd->bake_directory);
|
|
MEM_delete(nmd->runtime);
|
|
}
|
|
|
|
static void required_data_mask(ModifierData * /*md*/, CustomData_MeshMasks *r_cddata_masks)
|
|
{
|
|
/* We don't know what the node tree will need. If there are vertex groups, it is likely that the
|
|
* node tree wants to access them. */
|
|
r_cddata_masks->vmask |= CD_MASK_MDEFORMVERT;
|
|
r_cddata_masks->vmask |= CD_MASK_PROP_ALL;
|
|
}
|
|
|
|
} // namespace blender
|
|
|
|
ModifierTypeInfo modifierType_Nodes = {
|
|
/*idname*/ "GeometryNodes",
|
|
/*name*/ N_("GeometryNodes"),
|
|
/*struct_name*/ "NodesModifierData",
|
|
/*struct_size*/ sizeof(NodesModifierData),
|
|
/*srna*/ &RNA_NodesModifier,
|
|
/*type*/ ModifierTypeType::Constructive,
|
|
/*flags*/
|
|
(eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_AcceptsCVs |
|
|
eModifierTypeFlag_SupportsEditmode | eModifierTypeFlag_EnableInEditmode |
|
|
eModifierTypeFlag_SupportsMapping | eModifierTypeFlag_AcceptsGreasePencil),
|
|
/*icon*/ ICON_GEOMETRY_NODES,
|
|
|
|
/*copy_data*/ blender::copy_data,
|
|
|
|
/*deform_verts*/ nullptr,
|
|
/*deform_matrices*/ nullptr,
|
|
/*deform_verts_EM*/ nullptr,
|
|
/*deform_matrices_EM*/ nullptr,
|
|
/*modify_mesh*/ blender::modify_mesh,
|
|
/*modify_geometry_set*/ blender::modify_geometry_set,
|
|
|
|
/*init_data*/ blender::init_data,
|
|
/*required_data_mask*/ blender::required_data_mask,
|
|
/*free_data*/ blender::free_data,
|
|
/*is_disabled*/ blender::is_disabled,
|
|
/*update_depsgraph*/ blender::update_depsgraph,
|
|
/*depends_on_time*/ blender::depends_on_time,
|
|
/*depends_on_normals*/ nullptr,
|
|
/*foreach_ID_link*/ blender::foreach_ID_link,
|
|
/*foreach_tex_link*/ blender::foreach_tex_link,
|
|
/*free_runtime_data*/ nullptr,
|
|
/*panel_register*/ blender::panel_register,
|
|
/*blend_write*/ blender::blend_write,
|
|
/*blend_read*/ blender::blend_read,
|
|
/*foreach_cache*/ nullptr,
|
|
};
|