From 3d73b71a97aff632a92ccbcfc29c331bbd56dbd6 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 11 Jul 2023 22:36:10 +0200 Subject: [PATCH] Geometry Nodes: new Repeat Zone This adds support for running a set of nodes repeatedly. The number of iterations can be controlled dynamically as an input of the repeat zone. The repeat zone can be added in via the search or from the Add > Utilities menu. The main use case is to replace long repetitive node chains with a more flexible alternative. Technically, repeat zones can also be used for many other use cases. However, due to their serial nature, performance is very sub-optimal when they are used to solve problems that could be processed in parallel. Better solutions for such use cases will be worked on separately. Repeat zones are similar to simulation zones. The major difference is that they have no concept of time and are always evaluated entirely in the current frame, while in simulations only a single iteration is evaluated per frame. Stopping the repetition early using a dynamic condition is not yet supported. "Break" functionality can be implemented manually using Switch nodes in the loop for now. It's likely that this functionality will be built into the repeat zone in the future. For now, things are kept more simple. Remaining Todos after this first version: * Improve socket inspection and viewer node support. Currently, only the first iteration is taken into account for socket inspection and the viewer. * Make loop evaluation more lazy. Currently, the evaluation is eager, meaning that it evaluates some nodes even though their output may not be required. Pull Request: https://projects.blender.org/blender/blender/pulls/109164 --- .../datafiles/userdef/userdef_default_theme.c | 1 + .../startup/bl_operators/geometry_nodes.py | 102 ++++ scripts/startup/bl_operators/node.py | 30 +- scripts/startup/bl_ui/node_add_menu.py | 6 + .../startup/bl_ui/node_add_menu_geometry.py | 1 + scripts/startup/bl_ui/space_node.py | 76 +++ scripts/startup/bl_ui/space_spreadsheet.py | 2 + .../blenkernel/BKE_compute_contexts.hh | 25 + source/blender/blenkernel/BKE_node.h | 4 + source/blender/blenkernel/BKE_viewer_path.h | 1 + .../blenkernel/intern/compute_contexts.cc | 29 ++ source/blender/blenkernel/intern/node.cc | 17 + .../blender/blenkernel/intern/node_runtime.cc | 17 + .../intern/node_tree_anonymous_attributes.cc | 42 ++ .../intern/node_tree_field_inferencing.cc | 86 ++- .../blenkernel/intern/node_tree_update.cc | 9 + .../blenkernel/intern/node_tree_zones.cc | 18 +- .../blender/blenkernel/intern/viewer_path.cc | 40 +- .../blenloader/intern/versioning_userdef.c | 1 + source/blender/editors/include/UI_resources.h | 1 + source/blender/editors/interface/resources.cc | 3 + .../blender/editors/space_node/node_draw.cc | 48 +- .../blender/editors/space_node/node_edit.cc | 35 +- .../blender/editors/space_node/node_group.cc | 58 ++- .../blender/editors/space_node/node_select.cc | 11 + source/blender/editors/util/ed_viewer_path.cc | 47 +- source/blender/makesdna/DNA_node_types.h | 39 ++ source/blender/makesdna/DNA_userdef_types.h | 2 + .../blender/makesdna/DNA_viewer_path_types.h | 8 + .../blender/makesrna/intern/rna_nodetree.cc | 339 +++++++++++- source/blender/makesrna/intern/rna_space.cc | 17 +- source/blender/makesrna/intern/rna_userdef.cc | 6 + source/blender/modifiers/intern/MOD_nodes.cc | 21 + source/blender/nodes/NOD_geometry.hh | 3 + .../nodes/NOD_geometry_nodes_lazy_function.hh | 4 +- source/blender/nodes/NOD_static_types.h | 2 + source/blender/nodes/geometry/CMakeLists.txt | 2 + .../nodes/geometry/node_geometry_register.cc | 2 + .../nodes/geometry/node_geometry_register.hh | 2 + .../nodes/geometry/node_geometry_util.hh | 3 + .../geometry/nodes/node_geo_repeat_input.cc | 124 +++++ .../geometry/nodes/node_geo_repeat_output.cc | 333 ++++++++++++ .../nodes/node_geo_simulation_input.cc | 11 +- .../nodes/node_geo_simulation_output.cc | 23 +- .../intern/geometry_nodes_lazy_function.cc | 490 +++++++++++++++++- .../nodes/intern/geometry_nodes_log.cc | 33 +- tests/python/CMakeLists.txt | 1 + 47 files changed, 2070 insertions(+), 105 deletions(-) create mode 100644 source/blender/nodes/geometry/nodes/node_geo_repeat_input.cc create mode 100644 source/blender/nodes/geometry/nodes/node_geo_repeat_output.cc diff --git a/release/datafiles/userdef/userdef_default_theme.c b/release/datafiles/userdef/userdef_default_theme.c index 8ee8b653fbb..277bdff0f29 100644 --- a/release/datafiles/userdef/userdef_default_theme.c +++ b/release/datafiles/userdef/userdef_default_theme.c @@ -873,6 +873,7 @@ const bTheme U_theme_default = { .nodeclass_geometry = RGBA(0x00d6a3ff), .nodeclass_attribute = RGBA(0x001566ff), .node_zone_simulation = RGBA(0x66416233), + .node_zone_repeat = RGBA(0x76512f33), .movie = RGBA(0x0f0f0fcc), .gp_vertex_size = 3, .gp_vertex = RGBA(0x97979700), diff --git a/scripts/startup/bl_operators/geometry_nodes.py b/scripts/startup/bl_operators/geometry_nodes.py index f9556f8bb4c..8dcfca0fd80 100644 --- a/scripts/startup/bl_operators/geometry_nodes.py +++ b/scripts/startup/bl_operators/geometry_nodes.py @@ -350,6 +350,105 @@ class SimulationZoneItemMoveOperator(SimulationZoneOperator, Operator): return {'FINISHED'} +class RepeatZoneOperator: + input_node_type = 'GeometryNodeRepeatInput' + output_node_type = 'GeometryNodeRepeatOutput' + + @classmethod + def get_output_node(cls, context): + node = context.active_node + if node.bl_idname == cls.input_node_type: + return node.paired_output + if node.bl_idname == cls.output_node_type: + return node + return None + + @classmethod + def poll(cls, context): + space = context.space_data + # Needs active node editor and a tree. + if not space or space.type != 'NODE_EDITOR' or not space.edit_tree or space.edit_tree.library: + return False + node = context.active_node + if node is None or node.bl_idname not in [cls.input_node_type, cls.output_node_type]: + return False + if cls.get_output_node(context) is None: + return False + return True + + +class RepeatZoneItemAddOperator(RepeatZoneOperator, Operator): + """Add a repeat item to the repeat zone""" + bl_idname = "node.repeat_zone_item_add" + bl_label = "Add Repeat Item" + bl_options = {'REGISTER', 'UNDO'} + + default_socket_type = 'GEOMETRY' + + def execute(self, context): + node = self.get_output_node(context) + repeat_items = node.repeat_items + + # Remember index to move the item. + if node.active_item: + dst_index = node.active_index + 1 + dst_type = node.active_item.socket_type + dst_name = node.active_item.name + else: + dst_index = len(repeat_items) + dst_type = self.default_socket_type + # Empty name so it is based on the type. + dst_name = "" + repeat_items.new(dst_type, dst_name) + repeat_items.move(len(repeat_items) - 1, dst_index) + node.active_index = dst_index + + return {'FINISHED'} + + +class RepeatZoneItemRemoveOperator(RepeatZoneOperator, Operator): + """Remove a repeat item from the repeat zone""" + bl_idname = "node.repeat_zone_item_remove" + bl_label = "Remove Repeat Item" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + node = self.get_output_node(context) + repeat_items = node.repeat_items + + if node.active_item: + repeat_items.remove(node.active_item) + node.active_index = min(node.active_index, len(repeat_items) - 1) + + return {'FINISHED'} + + +class RepeatZoneItemMoveOperator(RepeatZoneOperator, Operator): + """Move a repeat item up or down in the list""" + bl_idname = "node.repeat_zone_item_move" + bl_label = "Move Repeat Item" + bl_options = {'REGISTER', 'UNDO'} + + direction: EnumProperty( + name="Direction", + items=[('UP', "Up", ""), ('DOWN', "Down", "")], + default='UP', + ) + + def execute(self, context): + node = self.get_output_node(context) + repeat_items = node.repeat_items + + if self.direction == 'UP' and node.active_index > 0: + repeat_items.move(node.active_index, node.active_index - 1) + node.active_index = node.active_index - 1 + elif self.direction == 'DOWN' and node.active_index < len(repeat_items) - 1: + repeat_items.move(node.active_index, node.active_index + 1) + node.active_index = node.active_index + 1 + + return {'FINISHED'} + + classes = ( NewGeometryNodesModifier, NewGeometryNodeTreeAssign, @@ -357,4 +456,7 @@ classes = ( SimulationZoneItemAddOperator, SimulationZoneItemRemoveOperator, SimulationZoneItemMoveOperator, + RepeatZoneItemAddOperator, + RepeatZoneItemRemoveOperator, + RepeatZoneItemMoveOperator, ) diff --git a/scripts/startup/bl_operators/node.py b/scripts/startup/bl_operators/node.py index f5dfae789a4..ace5f6a1bd1 100644 --- a/scripts/startup/bl_operators/node.py +++ b/scripts/startup/bl_operators/node.py @@ -154,14 +154,7 @@ class NODE_OT_add_node(NodeAddOperator, Operator): return "" -class NODE_OT_add_simulation_zone(NodeAddOperator, Operator): - """Add simulation zone input and output nodes to the active tree""" - bl_idname = "node.add_simulation_zone" - bl_label = "Add Simulation Zone" - bl_options = {'REGISTER', 'UNDO'} - - input_node_type = "GeometryNodeSimulationInput" - output_node_type = "GeometryNodeSimulationOutput" +class NodeAddZoneOperator(NodeAddOperator): offset: FloatVectorProperty( name="Offset", description="Offset of nodes from the cursor when added", @@ -195,6 +188,26 @@ class NODE_OT_add_simulation_zone(NodeAddOperator, Operator): return {'FINISHED'} +class NODE_OT_add_simulation_zone(NodeAddZoneOperator, Operator): + """Add simulation zone input and output nodes to the active tree""" + bl_idname = "node.add_simulation_zone" + bl_label = "Add Simulation Zone" + bl_options = {'REGISTER', 'UNDO'} + + input_node_type = "GeometryNodeSimulationInput" + output_node_type = "GeometryNodeSimulationOutput" + + +class NODE_OT_add_repeat_zone(NodeAddZoneOperator, Operator): + """Add a repeat zone that allows executing nodes a dynamic number of times""" + bl_idname = "node.add_repeat_zone" + bl_label = "Add Repeat Zone" + bl_options = {'REGISTER', 'UNDO'} + + input_node_type = "GeometryNodeRepeatInput" + output_node_type = "GeometryNodeRepeatOutput" + + class NODE_OT_collapse_hide_unused_toggle(Operator): """Toggle collapsed nodes and hide unused sockets""" @@ -328,6 +341,7 @@ classes = ( NODE_OT_add_node, NODE_OT_add_simulation_zone, + NODE_OT_add_repeat_zone, NODE_OT_collapse_hide_unused_toggle, NODE_OT_panel_add, NODE_OT_panel_remove, diff --git a/scripts/startup/bl_ui/node_add_menu.py b/scripts/startup/bl_ui/node_add_menu.py index 79810a0f25a..94e8f6601d0 100644 --- a/scripts/startup/bl_ui/node_add_menu.py +++ b/scripts/startup/bl_ui/node_add_menu.py @@ -72,6 +72,12 @@ def add_simulation_zone(layout, label): return props +def add_repeat_zone(layout, label): + props = layout.operator("node.add_repeat_zone", text=label) + props.use_transform = True + return props + + classes = ( ) diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index d03706f3821..41fb78662ff 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -530,6 +530,7 @@ class NODE_MT_category_GEO_UTILITIES(Menu): layout.menu("NODE_MT_category_GEO_UTILITIES_ROTATION") layout.separator() node_add_menu.add_node_type(layout, "FunctionNodeRandomValue") + node_add_menu.add_repeat_zone(layout, label="Repeat Zone") node_add_menu.add_node_type(layout, "GeometryNodeSwitch") node_add_menu.draw_assets_for_catalog(layout, self.bl_label) diff --git a/scripts/startup/bl_ui/space_node.py b/scripts/startup/bl_ui/space_node.py index 3350c7b08e3..d4713bcbf8f 100644 --- a/scripts/startup/bl_ui/space_node.py +++ b/scripts/startup/bl_ui/space_node.py @@ -1107,6 +1107,80 @@ class NODE_PT_simulation_zone_items(Panel): layout.prop(active_item, "attribute_domain") +class NODE_UL_repeat_zone_items(bpy.types.UIList): + def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index): + if self.layout_type in {'DEFAULT', 'COMPACT'}: + row = layout.row(align=True) + row.template_node_socket(color=item.color) + row.prop(item, "name", text="", emboss=False, icon_value=icon) + elif self.layout_type == 'GRID': + layout.alignment = 'CENTER' + layout.template_node_socket(color=item.color) + + +class NODE_PT_repeat_zone_items(Panel): + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_category = "Node" + bl_label = "Repeat" + + input_node_type = 'GeometryNodeRepeatInput' + output_node_type = 'GeometryNodeRepeatOutput' + + @classmethod + def get_output_node(cls, context): + node = context.active_node + if node.bl_idname == cls.input_node_type: + return node.paired_output + if node.bl_idname == cls.output_node_type: + return node + return None + + @classmethod + def poll(cls, context): + snode = context.space_data + if snode is None: + return False + node = context.active_node + if node is None or node.bl_idname not in (cls.input_node_type, cls.output_node_type): + return False + if cls.get_output_node(context) is None: + return False + return True + + def draw(self, context): + layout = self.layout + output_node = self.get_output_node(context) + split = layout.row() + split.template_list( + "NODE_UL_repeat_zone_items", + "", + output_node, + "repeat_items", + output_node, + "active_index") + + ops_col = split.column() + + add_remove_col = ops_col.column(align=True) + add_remove_col.operator("node.repeat_zone_item_add", icon='ADD', text="") + add_remove_col.operator("node.repeat_zone_item_remove", icon='REMOVE', text="") + + ops_col.separator() + + up_down_col = ops_col.column(align=True) + props = up_down_col.operator("node.repeat_zone_item_move", icon='TRIA_UP', text="") + props.direction = 'UP' + props = up_down_col.operator("node.repeat_zone_item_move", icon='TRIA_DOWN', text="") + props.direction = 'DOWN' + + active_item = output_node.active_item + if active_item is not None: + layout.use_property_split = True + layout.use_property_decorate = False + layout.prop(active_item, "socket_type") + + # Grease Pencil properties class NODE_PT_annotation(AnnotationDataPanel, Panel): bl_space_type = 'NODE_EDITOR' @@ -1174,6 +1248,8 @@ classes = ( NODE_PT_panels, NODE_UL_simulation_zone_items, NODE_PT_simulation_zone_items, + NODE_UL_repeat_zone_items, + NODE_PT_repeat_zone_items, NODE_PT_active_node_properties, node_panel(EEVEE_MATERIAL_PT_settings), diff --git a/scripts/startup/bl_ui/space_spreadsheet.py b/scripts/startup/bl_ui/space_spreadsheet.py index 1dce85e98a1..f7023e75317 100644 --- a/scripts/startup/bl_ui/space_spreadsheet.py +++ b/scripts/startup/bl_ui/space_spreadsheet.py @@ -91,6 +91,8 @@ class SPREADSHEET_HT_header(bpy.types.Header): layout.label(text=ctx.ui_name, icon='NODE') elif ctx.type == 'SIMULATION_ZONE': layout.label(text="Simulation Zone") + elif ctx.type == 'REPEAT_ZONE': + layout.label(text="Repeat Zone") elif ctx.type == 'VIEWER_NODE': layout.label(text=ctx.ui_name) diff --git a/source/blender/blenkernel/BKE_compute_contexts.hh b/source/blender/blenkernel/BKE_compute_contexts.hh index a1015c7b7f2..c82d5768fb4 100644 --- a/source/blender/blenkernel/BKE_compute_contexts.hh +++ b/source/blender/blenkernel/BKE_compute_contexts.hh @@ -78,4 +78,29 @@ class SimulationZoneComputeContext : public ComputeContext { void print_current_in_line(std::ostream &stream) const override; }; +class RepeatZoneComputeContext : public ComputeContext { + private: + static constexpr const char *s_static_type = "REPEAT_ZONE"; + + int32_t output_node_id_; + int iteration_; + + public: + RepeatZoneComputeContext(const ComputeContext *parent, int32_t output_node_id, int iteration); + RepeatZoneComputeContext(const ComputeContext *parent, const bNode &node, int iteration); + + int32_t output_node_id() const + { + return output_node_id_; + } + + int iteration() const + { + return iteration_; + } + + private: + void print_current_in_line(std::ostream &stream) const override; +}; + } // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 2fdb19299aa..09a15355cdf 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1361,6 +1361,10 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i #define GEO_NODE_INPUT_SIGNED_DISTANCE 2102 #define GEO_NODE_SAMPLE_VOLUME 2103 #define GEO_NODE_MESH_TOPOLOGY_CORNERS_OF_EDGE 2104 +/* Leaving out two indices to avoid crashes with files that were created during the development of + * the repeat zone. */ +#define GEO_NODE_REPEAT_INPUT 2107 +#define GEO_NODE_REPEAT_OUTPUT 2108 /** \} */ diff --git a/source/blender/blenkernel/BKE_viewer_path.h b/source/blender/blenkernel/BKE_viewer_path.h index 8707e2c96c3..1f04ec4d7c8 100644 --- a/source/blender/blenkernel/BKE_viewer_path.h +++ b/source/blender/blenkernel/BKE_viewer_path.h @@ -53,6 +53,7 @@ ModifierViewerPathElem *BKE_viewer_path_elem_new_modifier(void); GroupNodeViewerPathElem *BKE_viewer_path_elem_new_group_node(void); SimulationZoneViewerPathElem *BKE_viewer_path_elem_new_simulation_zone(void); ViewerNodeViewerPathElem *BKE_viewer_path_elem_new_viewer_node(void); +RepeatZoneViewerPathElem *BKE_viewer_path_elem_new_repeat_zone(void); ViewerPathElem *BKE_viewer_path_elem_copy(const ViewerPathElem *src); bool BKE_viewer_path_elem_equal(const ViewerPathElem *a, const ViewerPathElem *b); void BKE_viewer_path_elem_free(ViewerPathElem *elem); diff --git a/source/blender/blenkernel/intern/compute_contexts.cc b/source/blender/blenkernel/intern/compute_contexts.cc index b943aefcfeb..06350194127 100644 --- a/source/blender/blenkernel/intern/compute_contexts.cc +++ b/source/blender/blenkernel/intern/compute_contexts.cc @@ -88,4 +88,33 @@ void SimulationZoneComputeContext::print_current_in_line(std::ostream &stream) c stream << "Simulation Zone ID: " << output_node_id_; } +RepeatZoneComputeContext::RepeatZoneComputeContext(const ComputeContext *parent, + const int32_t output_node_id, + const int iteration) + : ComputeContext(s_static_type, parent), output_node_id_(output_node_id), iteration_(iteration) +{ + /* Mix static type and node id into a single buffer so that only a single call to #mix_in is + * necessary. */ + const int type_size = strlen(s_static_type); + const int buffer_size = type_size + 1 + sizeof(int32_t) + sizeof(int); + DynamicStackBuffer<64, 8> buffer_owner(buffer_size, 8); + char *buffer = static_cast(buffer_owner.buffer()); + memcpy(buffer, s_static_type, type_size + 1); + memcpy(buffer + type_size + 1, &output_node_id_, sizeof(int32_t)); + memcpy(buffer + type_size + 1 + sizeof(int32_t), &iteration_, sizeof(int)); + hash_.mix_in(buffer, buffer_size); +} + +RepeatZoneComputeContext::RepeatZoneComputeContext(const ComputeContext *parent, + const bNode &node, + const int iteration) + : RepeatZoneComputeContext(parent, node.identifier, iteration) +{ +} + +void RepeatZoneComputeContext::print_current_in_line(std::ostream &stream) const +{ + stream << "Repeat Zone ID: " << output_node_id_; +} + } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index e1d337e3055..38dba7e399f 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -675,6 +675,14 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) BLO_write_string(writer, item.name); } } + if (node->type == GEO_NODE_REPEAT_OUTPUT) { + const NodeGeometryRepeatOutput &storage = *static_cast( + node->storage); + BLO_write_struct_array(writer, NodeRepeatItem, storage.items_num, storage.items); + for (const NodeRepeatItem &item : Span(storage.items, storage.items_num)) { + BLO_write_string(writer, item.name); + } + } } LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { @@ -871,6 +879,15 @@ void ntreeBlendReadData(BlendDataReader *reader, ID *owner_id, bNodeTree *ntree) } break; } + case GEO_NODE_REPEAT_OUTPUT: { + NodeGeometryRepeatOutput &storage = *static_cast( + node->storage); + BLO_read_data_address(reader, &storage.items); + for (const NodeRepeatItem &item : Span(storage.items, storage.items_num)) { + BLO_read_data_address(reader, &item.name); + } + break; + } default: break; diff --git a/source/blender/blenkernel/intern/node_runtime.cc b/source/blender/blenkernel/intern/node_runtime.cc index c6ce1121355..e8474766865 100644 --- a/source/blender/blenkernel/intern/node_runtime.cc +++ b/source/blender/blenkernel/intern/node_runtime.cc @@ -294,6 +294,17 @@ static Vector get_implicit_origin_nodes(const bNodeTree &ntree, b } } } + if (node.type == GEO_NODE_REPEAT_OUTPUT) { + for (const bNode *repeat_input_node : + ntree.runtime->nodes_by_type.lookup(nodeTypeFind("GeometryNodeRepeatInput"))) + { + const auto &storage = *static_cast( + repeat_input_node->storage); + if (storage.output_node_id == node.identifier) { + origin_nodes.append(repeat_input_node); + } + } + } return origin_nodes; } @@ -306,6 +317,12 @@ static Vector get_implicit_target_nodes(const bNodeTree &ntree, b target_nodes.append(sim_output_node); } } + if (node.type == GEO_NODE_REPEAT_INPUT) { + const auto &storage = *static_cast(node.storage); + if (const bNode *repeat_output_node = ntree.node_by_id(storage.output_node_id)) { + target_nodes.append(repeat_output_node); + } + } return target_nodes; } diff --git a/source/blender/blenkernel/intern/node_tree_anonymous_attributes.cc b/source/blender/blenkernel/intern/node_tree_anonymous_attributes.cc index c3a8c98c91e..5489dd48707 100644 --- a/source/blender/blenkernel/intern/node_tree_anonymous_attributes.cc +++ b/source/blender/blenkernel/intern/node_tree_anonymous_attributes.cc @@ -99,6 +99,48 @@ static const aal::RelationsInNode &get_relations_in_node(const bNode &node, Reso } return relations; } + if (ELEM(node.type, GEO_NODE_REPEAT_INPUT, GEO_NODE_REPEAT_OUTPUT)) { + aal::RelationsInNode &relations = scope.construct(); + /* TODO: Add a smaller set of relations. This requires changing the inferencing algorithm to + * make it aware of loops. */ + for (const bNodeSocket *socket : node.output_sockets()) { + if (socket->type == SOCK_GEOMETRY) { + for (const bNodeSocket *other_output : node.output_sockets()) { + if (socket_is_field(*other_output)) { + relations.available_relations.append({other_output->index(), socket->index()}); + } + } + for (const bNodeSocket *input_socket : node.input_sockets()) { + if (input_socket->type == SOCK_GEOMETRY) { + relations.propagate_relations.append({input_socket->index(), socket->index()}); + } + } + } + else if (socket_is_field(*socket)) { + /* Reference relations are not added for the output node, because then nodes after the + * repeat zone would have to know about the individual field sources within the repeat + * zone. This is not necessary, because the field outputs of a repeat zone already serve as + * field sources and anonymous attributes are extracted from them. */ + if (node.type == GEO_NODE_REPEAT_INPUT) { + for (const bNodeSocket *input_socket : node.input_sockets()) { + if (socket_is_field(*input_socket)) { + relations.reference_relations.append({input_socket->index(), socket->index()}); + } + } + } + } + } + for (const bNodeSocket *socket : node.input_sockets()) { + if (socket->type == SOCK_GEOMETRY) { + for (const bNodeSocket *other_input : node.input_sockets()) { + if (socket_is_field(*other_input)) { + relations.eval_relations.append({other_input->index(), socket->index()}); + } + } + } + } + return relations; + } if (const NodeDeclaration *node_decl = node.declaration()) { if (const aal::RelationsInNode *relations = node_decl->anonymous_attribute_relations()) { return *relations; diff --git a/source/blender/blenkernel/intern/node_tree_field_inferencing.cc b/source/blender/blenkernel/intern/node_tree_field_inferencing.cc index 05293547674..3efd64ab439 100644 --- a/source/blender/blenkernel/intern/node_tree_field_inferencing.cc +++ b/source/blender/blenkernel/intern/node_tree_field_inferencing.cc @@ -319,6 +319,22 @@ static eFieldStateSyncResult simulation_nodes_field_state_sync( return res; } +static eFieldStateSyncResult repeat_field_state_sync( + const bNode &input_node, + const bNode &output_node, + const MutableSpan field_state_by_socket_id) +{ + eFieldStateSyncResult res = eFieldStateSyncResult::NONE; + for (const int i : output_node.output_sockets().index_range()) { + const bNodeSocket &input_socket = input_node.output_socket(i); + const bNodeSocket &output_socket = output_node.output_socket(i); + SocketFieldState &input_state = field_state_by_socket_id[input_socket.index_in_tree()]; + SocketFieldState &output_state = field_state_by_socket_id[output_socket.index_in_tree()]; + res |= sync_field_states(input_state, output_state); + } + return res; +} + static bool propagate_special_data_requirements( const bNodeTree &tree, const bNode &node, @@ -328,29 +344,59 @@ static bool propagate_special_data_requirements( bool need_update = false; - /* Sync field state between simulation nodes and schedule another pass if necessary. */ - if (node.type == GEO_NODE_SIMULATION_INPUT) { - const NodeGeometrySimulationInput &data = *static_cast( - node.storage); - if (const bNode *output_node = tree.node_by_id(data.output_node_id)) { - const eFieldStateSyncResult sync_result = simulation_nodes_field_state_sync( - node, *output_node, field_state_by_socket_id); - if (bool(sync_result & eFieldStateSyncResult::CHANGED_B)) { - need_update = true; - } - } - } - else if (node.type == GEO_NODE_SIMULATION_OUTPUT) { - for (const bNode *input_node : tree.nodes_by_type("GeometryNodeSimulationInput")) { + /* Sync field state between zone nodes and schedule another pass if necessary. */ + switch (node.type) { + case GEO_NODE_SIMULATION_INPUT: { const NodeGeometrySimulationInput &data = *static_cast( - input_node->storage); - if (node.identifier == data.output_node_id) { + node.storage); + if (const bNode *output_node = tree.node_by_id(data.output_node_id)) { const eFieldStateSyncResult sync_result = simulation_nodes_field_state_sync( - *input_node, node, field_state_by_socket_id); - if (bool(sync_result & eFieldStateSyncResult::CHANGED_A)) { + node, *output_node, field_state_by_socket_id); + if (bool(sync_result & eFieldStateSyncResult::CHANGED_B)) { need_update = true; } } + break; + } + case GEO_NODE_SIMULATION_OUTPUT: { + for (const bNode *input_node : tree.nodes_by_type("GeometryNodeSimulationInput")) { + const NodeGeometrySimulationInput &data = + *static_cast(input_node->storage); + if (node.identifier == data.output_node_id) { + const eFieldStateSyncResult sync_result = simulation_nodes_field_state_sync( + *input_node, node, field_state_by_socket_id); + if (bool(sync_result & eFieldStateSyncResult::CHANGED_A)) { + need_update = true; + } + } + } + break; + } + case GEO_NODE_REPEAT_INPUT: { + const NodeGeometryRepeatInput &data = *static_cast( + node.storage); + if (const bNode *output_node = tree.node_by_id(data.output_node_id)) { + const eFieldStateSyncResult sync_result = repeat_field_state_sync( + node, *output_node, field_state_by_socket_id); + if (bool(sync_result & eFieldStateSyncResult::CHANGED_B)) { + need_update = true; + } + } + break; + } + case GEO_NODE_REPEAT_OUTPUT: { + for (const bNode *input_node : tree.nodes_by_type("GeometryNodeRepeatInput")) { + const NodeGeometryRepeatInput &data = *static_cast( + input_node->storage); + if (node.identifier == data.output_node_id) { + const eFieldStateSyncResult sync_result = repeat_field_state_sync( + *input_node, node, field_state_by_socket_id); + if (bool(sync_result & eFieldStateSyncResult::CHANGED_A)) { + need_update = true; + } + } + } + break; } } @@ -365,8 +411,8 @@ static void propagate_data_requirements_from_right_to_left( const Span toposort_result = tree.toposort_right_to_left(); while (true) { - /* Node updates may require several passes due to cyclic dependencies caused by simulation - * input/output nodes. */ + /* Node updates may require several passes due to cyclic dependencies caused by simulation or + * repeat input/output nodes. */ bool need_update = false; for (const bNode *node : toposort_result) { diff --git a/source/blender/blenkernel/intern/node_tree_update.cc b/source/blender/blenkernel/intern/node_tree_update.cc index ed84a95c5ad..d4a8efe450c 100644 --- a/source/blender/blenkernel/intern/node_tree_update.cc +++ b/source/blender/blenkernel/intern/node_tree_update.cc @@ -588,6 +588,15 @@ class NodeTreeMainUpdater { } } } + if (node.type == GEO_NODE_REPEAT_INPUT) { + const NodeGeometryRepeatInput *data = static_cast( + node.storage); + if (const bNode *output_node = ntree.node_by_id(data->output_node_id)) { + if (output_node->runtime->changed_flag & NTREE_CHANGED_NODE_PROPERTY) { + return true; + } + } + } return false; } diff --git a/source/blender/blenkernel/intern/node_tree_zones.cc b/source/blender/blenkernel/intern/node_tree_zones.cc index 6edb1ffea0f..b0523c737cd 100644 --- a/source/blender/blenkernel/intern/node_tree_zones.cc +++ b/source/blender/blenkernel/intern/node_tree_zones.cc @@ -33,7 +33,10 @@ static Vector> find_zone_nodes( Map &r_zone_by_inout_node) { Vector> zones; - for (const bNode *node : tree.nodes_by_type("GeometryNodeSimulationOutput")) { + Vector zone_output_nodes; + zone_output_nodes.extend(tree.nodes_by_type("GeometryNodeSimulationOutput")); + zone_output_nodes.extend(tree.nodes_by_type("GeometryNodeRepeatOutput")); + for (const bNode *node : zone_output_nodes) { auto zone = std::make_unique(); zone->owner = &owner; zone->index = zones.size(); @@ -50,6 +53,15 @@ static Vector> find_zone_nodes( } } } + for (const bNode *node : tree.nodes_by_type("GeometryNodeRepeatInput")) { + const auto &storage = *static_cast(node->storage); + if (const bNode *repeat_output_node = tree.node_by_id(storage.output_node_id)) { + if (bNodeTreeZone *zone = r_zone_by_inout_node.lookup_default(repeat_output_node, nullptr)) { + zone->input_node = node; + r_zone_by_inout_node.add(node, zone); + } + } + } return zones; } @@ -227,13 +239,13 @@ static std::unique_ptr discover_tree_zones(const bNodeTree &tree depend_on_output_flags |= depend_on_output_flag_array[from_node_i]; } } - if (node->type == GEO_NODE_SIMULATION_INPUT) { + if (ELEM(node->type, GEO_NODE_SIMULATION_INPUT, GEO_NODE_REPEAT_INPUT)) { if (const bNodeTreeZone *zone = zone_by_inout_node.lookup_default(node, nullptr)) { /* Now entering a zone, so set the corresponding bit. */ depend_on_input_flags[zone->index].set(); } } - else if (node->type == GEO_NODE_SIMULATION_OUTPUT) { + else if (ELEM(node->type, GEO_NODE_SIMULATION_OUTPUT, GEO_NODE_REPEAT_OUTPUT)) { if (const bNodeTreeZone *zone = zone_by_inout_node.lookup_default(node, nullptr)) { /* The output is implicitly linked to the input, so also propagate the bits from there. */ if (const bNode *zone_input_node = zone->input_node) { diff --git a/source/blender/blenkernel/intern/viewer_path.cc b/source/blender/blenkernel/intern/viewer_path.cc index 04cd874d567..7ef16636e03 100644 --- a/source/blender/blenkernel/intern/viewer_path.cc +++ b/source/blender/blenkernel/intern/viewer_path.cc @@ -88,6 +88,11 @@ void BKE_viewer_path_blend_write(BlendWriter *writer, const ViewerPath *viewer_p BLO_write_struct(writer, ViewerNodeViewerPathElem, typed_elem); break; } + case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: { + const auto *typed_elem = reinterpret_cast(elem); + BLO_write_struct(writer, RepeatZoneViewerPathElem, typed_elem); + break; + } } BLO_write_string(writer, elem->ui_name); } @@ -102,6 +107,7 @@ void BKE_viewer_path_blend_read_data(BlendDataReader *reader, ViewerPath *viewer case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE: case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE: + case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: case VIEWER_PATH_ELEM_TYPE_ID: { break; } @@ -126,7 +132,8 @@ void BKE_viewer_path_blend_read_lib(BlendLibReader *reader, ID *self_id, ViewerP case VIEWER_PATH_ELEM_TYPE_MODIFIER: case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE: - case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE: { + case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE: + case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: { break; } } @@ -145,7 +152,8 @@ void BKE_viewer_path_foreach_id(LibraryForeachIDData *data, ViewerPath *viewer_p case VIEWER_PATH_ELEM_TYPE_MODIFIER: case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE: - case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE: { + case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE: + case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: { break; } } @@ -164,7 +172,8 @@ void BKE_viewer_path_id_remap(ViewerPath *viewer_path, const IDRemapper *mapping case VIEWER_PATH_ELEM_TYPE_MODIFIER: case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE: - case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE: { + case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE: + case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: { break; } } @@ -196,6 +205,9 @@ ViewerPathElem *BKE_viewer_path_elem_new(const ViewerPathElemType type) case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE: { return &make_elem(type)->base; } + case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: { + return &make_elem(type)->base; + } } BLI_assert_unreachable(); return nullptr; @@ -230,6 +242,12 @@ ViewerNodeViewerPathElem *BKE_viewer_path_elem_new_viewer_node() BKE_viewer_path_elem_new(VIEWER_PATH_ELEM_TYPE_VIEWER_NODE)); } +RepeatZoneViewerPathElem *BKE_viewer_path_elem_new_repeat_zone() +{ + return reinterpret_cast( + BKE_viewer_path_elem_new(VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE)); +} + ViewerPathElem *BKE_viewer_path_elem_copy(const ViewerPathElem *src) { ViewerPathElem *dst = BKE_viewer_path_elem_new(ViewerPathElemType(src->type)); @@ -269,6 +287,13 @@ ViewerPathElem *BKE_viewer_path_elem_copy(const ViewerPathElem *src) new_elem->node_id = old_elem->node_id; break; } + case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: { + const auto *old_elem = reinterpret_cast(src); + auto *new_elem = reinterpret_cast(dst); + new_elem->repeat_output_node_id = old_elem->repeat_output_node_id; + new_elem->iteration = old_elem->iteration; + break; + } } return dst; } @@ -304,6 +329,12 @@ bool BKE_viewer_path_elem_equal(const ViewerPathElem *a, const ViewerPathElem *b const auto *b_elem = reinterpret_cast(b); return a_elem->node_id == b_elem->node_id; } + case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: { + const auto *a_elem = reinterpret_cast(a); + const auto *b_elem = reinterpret_cast(b); + return a_elem->repeat_output_node_id == b_elem->repeat_output_node_id && + a_elem->iteration == b_elem->iteration; + } } return false; } @@ -314,7 +345,8 @@ void BKE_viewer_path_elem_free(ViewerPathElem *elem) case VIEWER_PATH_ELEM_TYPE_ID: case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE: - case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE: { + case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE: + case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: { break; } case VIEWER_PATH_ELEM_TYPE_MODIFIER: { diff --git a/source/blender/blenloader/intern/versioning_userdef.c b/source/blender/blenloader/intern/versioning_userdef.c index e759eb1b926..fbe7e92ca17 100644 --- a/source/blender/blenloader/intern/versioning_userdef.c +++ b/source/blender/blenloader/intern/versioning_userdef.c @@ -116,6 +116,7 @@ static void do_versions_theme(const UserDef *userdef, bTheme *btheme) */ { /* Keep this block, even when empty. */ + FROM_DEFAULT_V4_UCHAR(space_node.node_zone_repeat); } #undef FROM_DEFAULT_V4_UCHAR diff --git a/source/blender/editors/include/UI_resources.h b/source/blender/editors/include/UI_resources.h index 321e1ccc246..75bc05880c8 100644 --- a/source/blender/editors/include/UI_resources.h +++ b/source/blender/editors/include/UI_resources.h @@ -181,6 +181,7 @@ typedef enum ThemeColorID { TH_NODE_ATTRIBUTE, TH_NODE_ZONE_SIMULATION, + TH_NODE_ZONE_REPEAT, TH_SIMULATED_FRAMES, TH_CONSOLE_OUTPUT, diff --git a/source/blender/editors/interface/resources.cc b/source/blender/editors/interface/resources.cc index 13ce35d0c15..634ed10a83d 100644 --- a/source/blender/editors/interface/resources.cc +++ b/source/blender/editors/interface/resources.cc @@ -645,6 +645,9 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid) case TH_NODE_ZONE_SIMULATION: cp = ts->node_zone_simulation; break; + case TH_NODE_ZONE_REPEAT: + cp = ts->node_zone_repeat; + break; case TH_SIMULATED_FRAMES: cp = ts->simulated_frames; break; diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 303e12d92a6..6a3a05f59ee 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -23,6 +23,7 @@ #include "DNA_world_types.h" #include "BLI_array.hh" +#include "BLI_bounds.hh" #include "BLI_convexhull_2d.h" #include "BLI_map.hh" #include "BLI_set.hh" @@ -2195,7 +2196,12 @@ static void node_draw_basis(const bContext &C, } /* Shadow. */ - if (!ELEM(node.type, GEO_NODE_SIMULATION_INPUT, GEO_NODE_SIMULATION_OUTPUT)) { + if (!ELEM(node.type, + GEO_NODE_SIMULATION_INPUT, + GEO_NODE_SIMULATION_OUTPUT, + GEO_NODE_REPEAT_INPUT, + GEO_NODE_REPEAT_OUTPUT)) + { node_draw_shadow(snode, node, BASIS_RAD, 1.0f); } @@ -2475,6 +2481,10 @@ static void node_draw_basis(const bContext &C, UI_GetThemeColor4fv(TH_NODE_ZONE_SIMULATION, color_outline); color_outline[3] = 1.0f; } + else if (ELEM(node.type, GEO_NODE_REPEAT_INPUT, GEO_NODE_REPEAT_OUTPUT)) { + UI_GetThemeColor4fv(TH_NODE_ZONE_REPEAT, color_outline); + color_outline[3] = 1.0f; + } else { UI_GetThemeColorBlendShade4fv(TH_BACK, TH_NODE, 0.4f, -20, color_outline); } @@ -3177,6 +3187,8 @@ static void node_draw_zones(TreeDrawContext & /*tree_draw_ctx*/, Array> bounds_by_zone(zones->zones.size()); Array fillet_curve_by_zone(zones->zones.size()); + /* Bounding box area of zones is used to determine draw order. */ + Array bounding_box_area_by_zone(zones->zones.size()); for (const int zone_i : zones->zones.index_range()) { const bNodeTreeZone &zone = *zones->zones[zone_i]; @@ -3185,6 +3197,11 @@ static void node_draw_zones(TreeDrawContext & /*tree_draw_ctx*/, const Span boundary_positions = bounds_by_zone[zone_i]; const int boundary_positions_num = boundary_positions.size(); + const Bounds bounding_box = *bounds::min_max(boundary_positions); + const float bounding_box_area = (bounding_box.max.x - bounding_box.min.x) * + (bounding_box.max.y - bounding_box.min.y); + bounding_box_area_by_zone[zone_i] = bounding_box_area; + bke::CurvesGeometry boundary_curve(boundary_positions_num, 1); boundary_curve.cyclic_for_write().first() = true; boundary_curve.fill_curve_types(CURVE_TYPE_POLY); @@ -3209,21 +3226,38 @@ static void node_draw_zones(TreeDrawContext & /*tree_draw_ctx*/, float line_width = 1.0f * scale; float viewport[4] = {}; GPU_viewport_size_get_f(viewport); - float zone_color[4]; - UI_GetThemeColor4fv(TH_NODE_ZONE_SIMULATION, zone_color); + + const auto get_theme_id = [&](const int zone_i) { + const bNode *node = zones->zones[zone_i]->output_node; + if (node->type == GEO_NODE_SIMULATION_OUTPUT) { + return TH_NODE_ZONE_SIMULATION; + } + return TH_NODE_ZONE_REPEAT; + }; const uint pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); - /* Draw all the contour lines after to prevent them from getting hidden by overlapping zones. */ + Vector zone_draw_order; for (const int zone_i : zones->zones.index_range()) { + zone_draw_order.append(zone_i); + } + std::sort(zone_draw_order.begin(), zone_draw_order.end(), [&](const int a, const int b) { + /* Draw zones with smaller bounding box on top to make them visible. */ + return bounding_box_area_by_zone[a] > bounding_box_area_by_zone[b]; + }); + + /* Draw all the contour lines after to prevent them from getting hidden by overlapping zones. */ + for (const int zone_i : zone_draw_order) { + float zone_color[4]; + UI_GetThemeColor4fv(get_theme_id(zone_i), zone_color); if (zone_color[3] == 0.0f) { break; } const Span fillet_boundary_positions = fillet_curve_by_zone[zone_i].positions(); /* Draw the background. */ immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); - immUniformThemeColorBlend(TH_BACK, TH_NODE_ZONE_SIMULATION, zone_color[3]); + immUniformThemeColorBlend(TH_BACK, get_theme_id(zone_i), zone_color[3]); immBegin(GPU_PRIM_TRI_FAN, fillet_boundary_positions.size() + 1); for (const float3 &p : fillet_boundary_positions) { @@ -3235,7 +3269,7 @@ static void node_draw_zones(TreeDrawContext & /*tree_draw_ctx*/, immUnbindProgram(); } - for (const int zone_i : zones->zones.index_range()) { + for (const int zone_i : zone_draw_order) { const Span fillet_boundary_positions = fillet_curve_by_zone[zone_i].positions(); /* Draw the contour lines. */ immBindBuiltinProgram(GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR); @@ -3243,7 +3277,7 @@ static void node_draw_zones(TreeDrawContext & /*tree_draw_ctx*/, immUniform2fv("viewportSize", &viewport[2]); immUniform1f("lineWidth", line_width * U.pixelsize); - immUniformThemeColorAlpha(TH_NODE_ZONE_SIMULATION, 1.0f); + immUniformThemeColorAlpha(get_theme_id(zone_i), 1.0f); immBegin(GPU_PRIM_LINE_STRIP, fillet_boundary_positions.size() + 1); for (const float3 &p : fillet_boundary_positions) { immVertex3fv(pos, p); diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index 85b14dce3c7..78ff2c698e2 100644 --- a/source/blender/editors/space_node/node_edit.cc +++ b/source/blender/editors/space_node/node_edit.cc @@ -1273,22 +1273,37 @@ void remap_node_pairing(bNodeTree &dst_tree, const Map & * so we have to build a map first to find copied output nodes in the new tree. */ Map dst_output_node_map; for (const auto &item : node_map.items()) { - if (item.key->type == GEO_NODE_SIMULATION_OUTPUT) { + if (ELEM(item.key->type, GEO_NODE_SIMULATION_OUTPUT, GEO_NODE_REPEAT_OUTPUT)) { dst_output_node_map.add_new(item.key->identifier, item.value); } } for (bNode *dst_node : node_map.values()) { - if (dst_node->type == GEO_NODE_SIMULATION_INPUT) { - NodeGeometrySimulationInput *data = static_cast( - dst_node->storage); - if (const bNode *output_node = dst_output_node_map.lookup_default(data->output_node_id, - nullptr)) { - data->output_node_id = output_node->identifier; + switch (dst_node->type) { + case GEO_NODE_SIMULATION_INPUT: { + NodeGeometrySimulationInput *data = static_cast( + dst_node->storage); + if (const bNode *output_node = dst_output_node_map.lookup_default(data->output_node_id, + nullptr)) { + data->output_node_id = output_node->identifier; + } + else { + data->output_node_id = 0; + blender::nodes::update_node_declaration_and_sockets(dst_tree, *dst_node); + } + break; } - else { - data->output_node_id = 0; - blender::nodes::update_node_declaration_and_sockets(dst_tree, *dst_node); + case GEO_NODE_REPEAT_INPUT: { + NodeGeometryRepeatInput *data = static_cast(dst_node->storage); + if (const bNode *output_node = dst_output_node_map.lookup_default(data->output_node_id, + nullptr)) { + data->output_node_id = output_node->identifier; + } + else { + data->output_node_id = 0; + blender::nodes::update_node_declaration_and_sockets(dst_tree, *dst_node); + } + break; } } } diff --git a/source/blender/editors/space_node/node_group.cc b/source/blender/editors/space_node/node_group.cc index 064362ea72c..0eec374b76c 100644 --- a/source/blender/editors/space_node/node_group.cc +++ b/source/blender/editors/space_node/node_group.cc @@ -147,16 +147,31 @@ static void remap_pairing(bNodeTree &dst_tree, const Map &identifier_map) { for (bNode *dst_node : nodes) { - if (dst_node->type == GEO_NODE_SIMULATION_INPUT) { - NodeGeometrySimulationInput *data = static_cast( - dst_node->storage); - if (data->output_node_id == 0) { - continue; - } + switch (dst_node->type) { + case GEO_NODE_SIMULATION_INPUT: { + NodeGeometrySimulationInput *data = static_cast( + dst_node->storage); + if (data->output_node_id == 0) { + continue; + } - data->output_node_id = identifier_map.lookup_default(data->output_node_id, 0); - if (data->output_node_id == 0) { - blender::nodes::update_node_declaration_and_sockets(dst_tree, *dst_node); + data->output_node_id = identifier_map.lookup_default(data->output_node_id, 0); + if (data->output_node_id == 0) { + blender::nodes::update_node_declaration_and_sockets(dst_tree, *dst_node); + } + break; + } + case GEO_NODE_REPEAT_INPUT: { + NodeGeometryRepeatInput *data = static_cast(dst_node->storage); + if (data->output_node_id == 0) { + continue; + } + + data->output_node_id = identifier_map.lookup_default(data->output_node_id, 0); + if (data->output_node_id == 0) { + blender::nodes::update_node_declaration_and_sockets(dst_tree, *dst_node); + } + break; } } } @@ -803,6 +818,31 @@ static bool node_group_make_test_selected(bNodeTree &ntree, } } } + for (bNode *input_node : ntree.nodes_by_type("GeometryNodeRepeatInput")) { + const NodeGeometryRepeatInput &input_data = *static_cast( + input_node->storage); + + if (bNode *output_node = ntree.node_by_id(input_data.output_node_id)) { + const bool input_selected = nodes_to_group.contains(input_node); + const bool output_selected = nodes_to_group.contains(output_node); + if (input_selected && !output_selected) { + BKE_reportf(&reports, + RPT_WARNING, + "Can not add repeat input node '%s' to a group without its paired output '%s'", + input_node->name, + output_node->name); + return false; + } + if (output_selected && !input_selected) { + BKE_reportf(&reports, + RPT_WARNING, + "Can not add repeat output node '%s' to a group without its paired input '%s'", + output_node->name, + input_node->name); + return false; + } + } + } return true; } diff --git a/source/blender/editors/space_node/node_select.cc b/source/blender/editors/space_node/node_select.cc index 85fa6c48731..d1cae18cf7a 100644 --- a/source/blender/editors/space_node/node_select.cc +++ b/source/blender/editors/space_node/node_select.cc @@ -327,6 +327,17 @@ void node_select_paired(bNodeTree &node_tree) } } } + for (bNode *input_node : node_tree.nodes_by_type("GeometryNodeRepeatInput")) { + const auto *storage = static_cast(input_node->storage); + if (bNode *output_node = node_tree.node_by_id(storage->output_node_id)) { + if (input_node->flag & NODE_SELECT) { + output_node->flag |= NODE_SELECT; + } + if (output_node->flag & NODE_SELECT) { + input_node->flag |= NODE_SELECT; + } + } + } } VectorSet get_selected_nodes(bNodeTree &node_tree) diff --git a/source/blender/editors/util/ed_viewer_path.cc b/source/blender/editors/util/ed_viewer_path.cc index c2207dd93be..5374aebb07f 100644 --- a/source/blender/editors/util/ed_viewer_path.cc +++ b/source/blender/editors/util/ed_viewer_path.cc @@ -26,6 +26,25 @@ namespace blender::ed::viewer_path { using bke::bNodeTreeZone; using bke::bNodeTreeZones; +static ViewerPathElem *viewer_path_elem_for_zone(const bNodeTreeZone &zone) +{ + switch (zone.output_node->type) { + case GEO_NODE_SIMULATION_OUTPUT: { + SimulationZoneViewerPathElem *node_elem = BKE_viewer_path_elem_new_simulation_zone(); + node_elem->sim_output_node_id = zone.output_node->identifier; + return &node_elem->base; + } + case GEO_NODE_REPEAT_OUTPUT: { + RepeatZoneViewerPathElem *node_elem = BKE_viewer_path_elem_new_repeat_zone(); + node_elem->repeat_output_node_id = zone.output_node->identifier; + node_elem->iteration = 0; + return &node_elem->base; + } + } + BLI_assert_unreachable(); + return nullptr; +} + static void viewer_path_for_geometry_node(const SpaceNode &snode, const bNode &node, ViewerPath &r_dst) @@ -83,9 +102,8 @@ static void viewer_path_for_geometry_node(const SpaceNode &snode, const Vector zone_stack = tree_zones->get_zone_stack_for_node( node->identifier); for (const bNodeTreeZone *zone : zone_stack) { - SimulationZoneViewerPathElem *node_elem = BKE_viewer_path_elem_new_simulation_zone(); - node_elem->sim_output_node_id = zone->output_node->identifier; - BLI_addtail(&r_dst.path, node_elem); + ViewerPathElem *zone_elem = viewer_path_elem_for_zone(*zone); + BLI_addtail(&r_dst.path, zone_elem); } GroupNodeViewerPathElem *node_elem = BKE_viewer_path_elem_new_group_node(); @@ -102,9 +120,8 @@ static void viewer_path_for_geometry_node(const SpaceNode &snode, const Vector zone_stack = tree_zones->get_zone_stack_for_node( node.identifier); for (const bNodeTreeZone *zone : zone_stack) { - SimulationZoneViewerPathElem *node_elem = BKE_viewer_path_elem_new_simulation_zone(); - node_elem->sim_output_node_id = zone->output_node->identifier; - BLI_addtail(&r_dst.path, node_elem); + ViewerPathElem *zone_elem = viewer_path_elem_for_zone(*zone); + BLI_addtail(&r_dst.path, zone_elem); } ViewerNodeViewerPathElem *viewer_node_elem = BKE_viewer_path_elem_new_viewer_node(); @@ -221,7 +238,10 @@ std::optional parse_geometry_nodes_viewer( remaining_elems = remaining_elems.drop_front(1); Vector node_path; for (const ViewerPathElem *elem : remaining_elems.drop_back(1)) { - if (!ELEM(elem->type, VIEWER_PATH_ELEM_TYPE_GROUP_NODE, VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE)) + if (!ELEM(elem->type, + VIEWER_PATH_ELEM_TYPE_GROUP_NODE, + VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE, + VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE)) { return std::nullopt; } @@ -275,6 +295,19 @@ bool exists_geometry_nodes_viewer(const ViewerPathForGeometryNodesViewer &parsed zone = next_zone; break; } + case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: { + const auto &typed_elem = *reinterpret_cast(path_elem); + const bNodeTreeZone *next_zone = tree_zones->get_zone_by_node( + typed_elem.repeat_output_node_id); + if (next_zone == nullptr) { + return false; + } + if (next_zone->parent_zone != zone) { + return false; + } + zone = next_zone; + break; + } case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: { const auto &typed_elem = *reinterpret_cast(path_elem); const bNode *group_node = ngroup->node_by_id(typed_elem.node_id); diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 0dd3276d5dc..831aff4e190 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -16,6 +16,7 @@ /** Workaround to forward-declare C++ type in C header. */ #ifdef __cplusplus # include +# include namespace blender { template class Span; @@ -1745,6 +1746,44 @@ typedef struct NodeGeometrySimulationOutput { #endif } NodeGeometrySimulationOutput; +typedef struct NodeRepeatItem { + char *name; + /** #eNodeSocketDatatype. */ + short socket_type; + char _pad[2]; + /** + * Generated unique identifier for sockets which stays the same even when the item order or + * names change. + */ + int identifier; + +#ifdef __cplusplus + static bool supports_type(eNodeSocketDatatype type); + std::string identifier_str() const; +#endif +} NodeRepeatItem; + +typedef struct NodeGeometryRepeatInput { + /** bNode.identifier of the corresponding output node. */ + int32_t output_node_id; +} NodeGeometryRepeatInput; + +typedef struct NodeGeometryRepeatOutput { + NodeRepeatItem *items; + int items_num; + int active_index; + /** Identifier to give to the next repeat item. */ + int next_identifier; + char _pad[4]; + +#ifdef __cplusplus + blender::Span items_span() const; + blender::MutableSpan items_span(); + NodeRepeatItem *add_item(const char *name, eNodeSocketDatatype type); + void set_item_name(NodeRepeatItem &item, const char *name); +#endif +} NodeGeometryRepeatOutput; + typedef struct NodeGeometryDistributePointsInVolume { /** #GeometryNodePointDistributeVolumeMode. */ uint8_t mode; diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index e73af2e96a0..fd6cb6d2732 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -346,6 +346,8 @@ typedef struct ThemeSpace { unsigned char nodeclass_geometry[4], nodeclass_attribute[4]; unsigned char node_zone_simulation[4]; + unsigned char node_zone_repeat[4]; + unsigned char _pad9[4]; unsigned char simulated_frames[4]; /** For sequence editor. */ diff --git a/source/blender/makesdna/DNA_viewer_path_types.h b/source/blender/makesdna/DNA_viewer_path_types.h index e9593e896fa..71609af4921 100644 --- a/source/blender/makesdna/DNA_viewer_path_types.h +++ b/source/blender/makesdna/DNA_viewer_path_types.h @@ -15,6 +15,7 @@ typedef enum ViewerPathElemType { VIEWER_PATH_ELEM_TYPE_GROUP_NODE = 2, VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE = 3, VIEWER_PATH_ELEM_TYPE_VIEWER_NODE = 4, + VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE = 5, } ViewerPathElemType; typedef struct ViewerPathElem { @@ -48,6 +49,13 @@ typedef struct SimulationZoneViewerPathElem { char _pad1[4]; } SimulationZoneViewerPathElem; +typedef struct RepeatZoneViewerPathElem { + ViewerPathElem base; + + int repeat_output_node_id; + int iteration; +} RepeatZoneViewerPathElem; + typedef struct ViewerNodeViewerPathElem { ViewerPathElem base; diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index a7a66da5b45..98bf2ee0375 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -35,6 +35,7 @@ #include "BKE_geometry_set.hh" #include "BKE_image.h" #include "BKE_node.h" +#include "BKE_node_runtime.hh" #include "BKE_node_tree_update.h" #include "BKE_texture.h" @@ -62,6 +63,8 @@ #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" +#include "BLI_string_utils.h" + const EnumPropertyItem rna_enum_node_socket_in_out_items[] = {{SOCK_IN, "IN", 0, "Input", ""}, {SOCK_OUT, "OUT", 0, "Output", ""}, {0, nullptr, 0, nullptr, nullptr}}; @@ -4261,6 +4264,29 @@ static void rna_SimulationStateItem_update(Main *bmain, Scene * /*scene*/, Point ED_node_tree_propagate_change(nullptr, bmain, ntree); } +static bNode *find_node_by_repeat_item(PointerRNA *ptr) +{ + const NodeRepeatItem *item = static_cast(ptr->data); + bNodeTree *ntree = reinterpret_cast(ptr->owner_id); + ntree->ensure_topology_cache(); + for (bNode *node : ntree->nodes_by_type("GeometryNodeRepeatOutput")) { + auto *storage = static_cast(node->storage); + if (storage->items_span().contains_ptr(item)) { + return node; + } + } + return nullptr; +} + +static void rna_RepeatItem_update(Main *bmain, Scene * /*scene*/, PointerRNA *ptr) +{ + bNodeTree *ntree = reinterpret_cast(ptr->owner_id); + bNode *node = find_node_by_repeat_item(ptr); + + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(nullptr, bmain, ntree); +} + static bool rna_SimulationStateItem_socket_type_supported(const EnumPropertyItem *item) { return NOD_geometry_simulation_output_item_socket_type_supported( @@ -4277,6 +4303,20 @@ static const EnumPropertyItem *rna_SimulationStateItem_socket_type_itemf(bContex rna_SimulationStateItem_socket_type_supported); } +static bool rna_RepeatItem_socket_type_supported(const EnumPropertyItem *item) +{ + return NodeRepeatItem::supports_type(eNodeSocketDatatype(item->value)); +} + +static const EnumPropertyItem *rna_RepeatItem_socket_type_itemf(bContext * /*C*/, + PointerRNA * /*ptr*/, + PropertyRNA * /*prop*/, + bool *r_free) +{ + *r_free = true; + return itemf_function_check(node_socket_data_type_items, rna_RepeatItem_socket_type_supported); +} + static void rna_SimulationStateItem_name_set(PointerRNA *ptr, const char *value) { bNodeTree *ntree = reinterpret_cast(ptr->owner_id); @@ -4288,6 +4328,14 @@ static void rna_SimulationStateItem_name_set(PointerRNA *ptr, const char *value) NOD_geometry_simulation_output_item_set_unique_name(sim, item, value, defname); } +static void rna_RepeatItem_name_set(PointerRNA *ptr, const char *value) +{ + bNode *node = find_node_by_repeat_item(ptr); + NodeRepeatItem *item = static_cast(ptr->data); + auto *storage = static_cast(node->storage); + storage->set_item_name(*item, value); +} + static void rna_SimulationStateItem_color_get(PointerRNA *ptr, float *values) { NodeSimulationItem *item = static_cast(ptr->data); @@ -4296,6 +4344,14 @@ static void rna_SimulationStateItem_color_get(PointerRNA *ptr, float *values) ED_node_type_draw_color(socket_type_idname, values); } +static void rna_RepeatItem_color_get(PointerRNA *ptr, float *values) +{ + NodeRepeatItem *item = static_cast(ptr->data); + + const char *socket_type_idname = nodeStaticSocketType(item->socket_type, 0); + ED_node_type_draw_color(socket_type_idname, values); +} + static PointerRNA rna_NodeGeometrySimulationInput_paired_output_get(PointerRNA *ptr) { bNodeTree *ntree = reinterpret_cast(ptr->owner_id); @@ -4306,6 +4362,17 @@ static PointerRNA rna_NodeGeometrySimulationInput_paired_output_get(PointerRNA * return r_ptr; } +static PointerRNA rna_NodeGeometryRepeatInput_paired_output_get(PointerRNA *ptr) +{ + bNodeTree *ntree = reinterpret_cast(ptr->owner_id); + bNode *node = static_cast(ptr->data); + NodeGeometryRepeatInput *storage = static_cast(node->storage); + bNode *output_node = ntree->node_by_id(storage->output_node_id); + PointerRNA r_ptr; + RNA_pointer_create(&ntree->id, &RNA_Node, output_node, &r_ptr); + return r_ptr; +} + static bool rna_GeometryNodeSimulationInput_pair_with_output( ID *id, bNode *node, bContext *C, ReportList *reports, bNode *output_node) { @@ -4327,6 +4394,27 @@ static bool rna_GeometryNodeSimulationInput_pair_with_output( return true; } +static bool rna_GeometryNodeRepeatInput_pair_with_output( + ID *id, bNode *node, bContext *C, ReportList *reports, bNode *output_node) +{ + bNodeTree *ntree = (bNodeTree *)id; + + if (!NOD_geometry_repeat_input_pair_with_output(ntree, node, output_node)) { + BKE_reportf(reports, + RPT_ERROR, + "Failed to pair repeat input node %s with output node %s", + node->name, + output_node->name); + return false; + } + + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(C, CTX_data_main(C), ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); + + return true; +} + static NodeSimulationItem *rna_NodeGeometrySimulationOutput_items_new( ID *id, bNode *node, Main *bmain, ReportList *reports, int socket_type, const char *name) { @@ -4347,6 +4435,24 @@ static NodeSimulationItem *rna_NodeGeometrySimulationOutput_items_new( return item; } +static NodeRepeatItem *rna_NodeGeometryRepeatOutput_items_new( + ID *id, bNode *node, Main *bmain, ReportList *reports, int socket_type, const char *name) +{ + NodeGeometryRepeatOutput *storage = static_cast(node->storage); + NodeRepeatItem *item = storage->add_item(name, eNodeSocketDatatype(socket_type)); + if (item == nullptr) { + BKE_report(reports, RPT_ERROR, "Unable to create socket"); + } + else { + bNodeTree *ntree = reinterpret_cast(id); + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(nullptr, bmain, ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); + } + + return item; +} + static void rna_NodeGeometrySimulationOutput_items_remove( ID *id, bNode *node, Main *bmain, ReportList *reports, NodeSimulationItem *item) { @@ -4364,6 +4470,33 @@ static void rna_NodeGeometrySimulationOutput_items_remove( } } +static void rna_NodeGeometryRepeatOutput_items_remove( + ID *id, bNode *node, Main *bmain, ReportList *reports, NodeRepeatItem *item) +{ + NodeGeometryRepeatOutput *storage = static_cast(node->storage); + if (!storage->items_span().contains_ptr(item)) { + BKE_reportf(reports, RPT_ERROR, "Unable to locate item '%s' in node", item->name); + return; + } + + const int remove_index = item - storage->items; + NodeRepeatItem *old_items = storage->items; + storage->items = MEM_cnew_array(storage->items_num - 1, __func__); + std::copy_n(old_items, remove_index, storage->items); + std::copy_n(old_items + remove_index + 1, + storage->items_num - remove_index - 1, + storage->items + remove_index); + + MEM_SAFE_FREE(old_items[remove_index].name); + storage->items_num--; + MEM_SAFE_FREE(old_items); + + bNodeTree *ntree = reinterpret_cast(id); + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(nullptr, bmain, ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); +} + static void rna_NodeGeometrySimulationOutput_items_clear(ID *id, bNode *node, Main *bmain) { NodeGeometrySimulationOutput *sim = static_cast(node->storage); @@ -4375,6 +4508,17 @@ static void rna_NodeGeometrySimulationOutput_items_clear(ID *id, bNode *node, Ma WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); } +static void rna_NodeGeometryRepeatOutput_items_clear(ID * /*id*/, bNode *node, Main * /*bmain*/) +{ + NodeGeometryRepeatOutput *storage = static_cast(node->storage); + for (NodeRepeatItem &item : storage->items_span()) { + MEM_SAFE_FREE(item.name); + } + MEM_SAFE_FREE(storage->items); + storage->items_num = 0; + storage->active_index = 0; +} + static void rna_NodeGeometrySimulationOutput_items_move( ID *id, bNode *node, Main *bmain, int from_index, int to_index) { @@ -4393,6 +4537,37 @@ static void rna_NodeGeometrySimulationOutput_items_move( WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); } +static void rna_NodeGeometryRepeatOutput_items_move( + ID *id, bNode *node, Main *bmain, int from_index, int to_index) +{ + NodeGeometryRepeatOutput *storage = static_cast(node->storage); + if (from_index < 0 || from_index >= storage->items_num || to_index < 0 || + to_index >= storage->items_num) + { + return; + } + + if (from_index < to_index) { + const NodeRepeatItem tmp = storage->items[from_index]; + for (int i = from_index; i < to_index; i++) { + storage->items[i] = storage->items[i + 1]; + } + storage->items[to_index] = tmp; + } + else if (from_index > to_index) { + const NodeRepeatItem tmp = storage->items[from_index]; + for (int i = from_index; i > to_index; i--) { + storage->items[i] = storage->items[i - 1]; + } + storage->items[to_index] = tmp; + } + + bNodeTree *ntree = reinterpret_cast(id); + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(nullptr, bmain, ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); +} + static PointerRNA rna_NodeGeometrySimulationOutput_active_item_get(PointerRNA *ptr) { bNode *node = static_cast(ptr->data); @@ -4403,6 +4578,18 @@ static PointerRNA rna_NodeGeometrySimulationOutput_active_item_get(PointerRNA *p return r_ptr; } +static PointerRNA rna_NodeGeometryRepeatOutput_active_item_get(PointerRNA *ptr) +{ + bNode *node = static_cast(ptr->data); + NodeGeometryRepeatOutput *storage = static_cast(node->storage); + blender::MutableSpan items = storage->items_span(); + PointerRNA r_ptr{}; + if (items.index_range().contains(storage->active_index)) { + RNA_pointer_create(ptr->owner_id, &RNA_RepeatItem, &items[storage->active_index], &r_ptr); + } + return r_ptr; +} + static void rna_NodeGeometrySimulationOutput_active_item_set(PointerRNA *ptr, PointerRNA value, ReportList * /*reports*/) @@ -4413,6 +4600,18 @@ static void rna_NodeGeometrySimulationOutput_active_item_set(PointerRNA *ptr, static_cast(value.data)); } +static void rna_NodeGeometryRepeatOutput_active_item_set(PointerRNA *ptr, + PointerRNA value, + ReportList * /*reports*/) +{ + bNode *node = static_cast(ptr->data); + NodeGeometryRepeatOutput *storage = static_cast(node->storage); + NodeRepeatItem *item = static_cast(value.data); + if (storage->items_span().contains_ptr(item)) { + storage->active_index = item - storage->items; + } +} + /* ******** Node Socket Types ******** */ static PointerRNA rna_NodeOutputFile_slot_layer_get(CollectionPropertyIterator *iter) @@ -10102,6 +10301,35 @@ static void def_geo_simulation_input(StructRNA *srna) RNA_def_function_return(func, parm); } +static void def_geo_repeat_input(StructRNA *srna) +{ + PropertyRNA *prop; + FunctionRNA *func; + PropertyRNA *parm; + + RNA_def_struct_sdna_from(srna, "NodeGeometryRepeatInput", "storage"); + + prop = RNA_def_property(srna, "paired_output", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "Node"); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_pointer_funcs( + prop, "rna_NodeGeometryRepeatInput_paired_output_get", nullptr, nullptr, nullptr); + RNA_def_property_ui_text( + prop, "Paired Output", "Repeat output node that this input node is paired with"); + + func = RNA_def_function( + srna, "pair_with_output", "rna_GeometryNodeRepeatInput_pair_with_output"); + RNA_def_function_ui_description(func, "Pair a repeat input node with an output node."); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_REPORTS | FUNC_USE_CONTEXT); + parm = RNA_def_pointer( + func, "output_node", "GeometryNode", "Output Node", "Repeat output node to pair with"); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); + /* return value */ + parm = RNA_def_boolean( + func, "result", false, "Result", "True if pairing the node was successful"); + RNA_def_function_return(func, parm); +} + static void rna_def_simulation_state_item(BlenderRNA *brna) { PropertyRNA *prop; @@ -10217,6 +10445,112 @@ static void def_geo_simulation_output(StructRNA *srna) RNA_def_property_update(prop, NC_NODE, nullptr); } +static void rna_def_repeat_item(BlenderRNA *brna) +{ + PropertyRNA *prop; + + StructRNA *srna = RNA_def_struct(brna, "RepeatItem", nullptr); + RNA_def_struct_ui_text(srna, "Repeat Item", ""); + RNA_def_struct_sdna(srna, "NodeRepeatItem"); + + prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_property_string_funcs(prop, nullptr, nullptr, "rna_RepeatItem_name_set"); + RNA_def_property_ui_text(prop, "Name", ""); + RNA_def_struct_name_property(srna, prop); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_RepeatItem_update"); + + prop = RNA_def_property(srna, "socket_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, node_socket_data_type_items); + RNA_def_property_enum_funcs(prop, nullptr, nullptr, "rna_RepeatItem_socket_type_itemf"); + RNA_def_property_ui_text(prop, "Socket Type", ""); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_RepeatItem_update"); + + prop = RNA_def_property(srna, "color", PROP_FLOAT, PROP_COLOR_GAMMA); + RNA_def_property_array(prop, 4); + RNA_def_property_float_funcs(prop, "rna_RepeatItem_color_get", nullptr, nullptr); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text( + prop, "Color", "Color of the corresponding socket type in the node editor"); +} + +static void rna_def_geo_repeat_output_items(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *parm; + FunctionRNA *func; + + srna = RNA_def_struct(brna, "NodeGeometryRepeatOutputItems", nullptr); + RNA_def_struct_sdna(srna, "bNode"); + RNA_def_struct_ui_text(srna, "Items", "Collection of repeat items"); + + func = RNA_def_function(srna, "new", "rna_NodeGeometryRepeatOutput_items_new"); + RNA_def_function_ui_description(func, "Add a item to this repeat zone"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS); + parm = RNA_def_enum(func, + "socket_type", + node_socket_data_type_items, + SOCK_GEOMETRY, + "Socket Type", + "Socket type of the item"); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); + parm = RNA_def_string(func, "name", nullptr, MAX_NAME, "Name", ""); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); + /* return value */ + parm = RNA_def_pointer(func, "item", "RepeatItem", "Item", "New item"); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "remove", "rna_NodeGeometryRepeatOutput_items_remove"); + RNA_def_function_ui_description(func, "Remove an item from this repeat zone"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS); + parm = RNA_def_pointer(func, "item", "RepeatItem", "Item", "The item to remove"); + RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); + + func = RNA_def_function(srna, "clear", "rna_NodeGeometryRepeatOutput_items_clear"); + RNA_def_function_ui_description(func, "Remove all items from this repeat zone"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN); + + func = RNA_def_function(srna, "move", "rna_NodeGeometryRepeatOutput_items_move"); + RNA_def_function_ui_description(func, "Move an item to another position"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN); + parm = RNA_def_int( + func, "from_index", -1, 0, INT_MAX, "From Index", "Index of the item to move", 0, 10000); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); + parm = RNA_def_int( + func, "to_index", -1, 0, INT_MAX, "To Index", "Target index for the item", 0, 10000); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); +} + +static void def_geo_repeat_output(StructRNA *srna) +{ + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeGeometryRepeatOutput", "storage"); + + prop = RNA_def_property(srna, "repeat_items", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, nullptr, "items", "items_num"); + RNA_def_property_struct_type(prop, "RepeatItem"); + RNA_def_property_ui_text(prop, "Items", ""); + RNA_def_property_srna(prop, "NodeGeometryRepeatOutputItems"); + + prop = RNA_def_property(srna, "active_index", PROP_INT, PROP_UNSIGNED); + RNA_def_property_int_sdna(prop, nullptr, "active_index"); + RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, NC_NODE, nullptr); + + prop = RNA_def_property(srna, "active_item", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "RepeatItem"); + RNA_def_property_pointer_funcs(prop, + "rna_NodeGeometryRepeatOutput_active_item_get", + "rna_NodeGeometryRepeatOutput_active_item_set", + nullptr, + nullptr); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item"); + RNA_def_property_update(prop, NC_NODE, nullptr); +} + static void def_geo_curve_handle_type_selection(StructRNA *srna) { PropertyRNA *prop; @@ -13731,7 +14065,6 @@ void RNA_def_nodetree(BlenderRNA *brna) rna_def_compositor_node(brna); rna_def_texture_node(brna); rna_def_geometry_node(brna); - rna_def_simulation_state_item(brna); rna_def_function_node(brna); rna_def_node_socket_panel(brna); @@ -13744,6 +14077,9 @@ void RNA_def_nodetree(BlenderRNA *brna) rna_def_texture_nodetree(brna); rna_def_geometry_nodetree(brna); + rna_def_simulation_state_item(brna); + rna_def_repeat_item(brna); + # define DefNode(Category, ID, DefFunc, EnumName, StructName, UIName, UIDesc) \ { \ srna = define_specific_node( \ @@ -13795,6 +14131,7 @@ void RNA_def_nodetree(BlenderRNA *brna) rna_def_cmp_output_file_slot_file(brna); rna_def_cmp_output_file_slot_layer(brna); rna_def_geo_simulation_output_items(brna); + rna_def_geo_repeat_output_items(brna); rna_def_node_instance_hash(brna); } diff --git a/source/blender/makesrna/intern/rna_space.cc b/source/blender/makesrna/intern/rna_space.cc index 40054dbc6df..f6c0105ef1e 100644 --- a/source/blender/makesrna/intern/rna_space.cc +++ b/source/blender/makesrna/intern/rna_space.cc @@ -3326,7 +3326,7 @@ const EnumPropertyItem *rna_SpaceSpreadsheet_attribute_domain_itemf(bContext * / static StructRNA *rna_viewer_path_elem_refine(PointerRNA *ptr) { ViewerPathElem *elem = static_cast(ptr->data); - switch (elem->type) { + switch (ViewerPathElemType(elem->type)) { case VIEWER_PATH_ELEM_TYPE_ID: return &RNA_IDViewerPathElem; case VIEWER_PATH_ELEM_TYPE_MODIFIER: @@ -3337,6 +3337,8 @@ static StructRNA *rna_viewer_path_elem_refine(PointerRNA *ptr) return &RNA_SimulationZoneViewerPathElem; case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE: return &RNA_ViewerNodeViewerPathElem; + case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: + return &RNA_RepeatZoneViewerPathElem; } BLI_assert_unreachable(); return nullptr; @@ -8079,6 +8081,7 @@ static const EnumPropertyItem viewer_path_elem_type_items[] = { {VIEWER_PATH_ELEM_TYPE_GROUP_NODE, "GROUP_NODE", ICON_NONE, "Group Node", ""}, {VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE, "SIMULATION_ZONE", ICON_NONE, "Simulation Zone", ""}, {VIEWER_PATH_ELEM_TYPE_VIEWER_NODE, "VIEWER_NODE", ICON_NONE, "Viewer Node", ""}, + {VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE, "REPEAT_ZONE", ICON_NONE, "Repeat", ""}, {0, nullptr, 0, nullptr, nullptr}, }; @@ -8146,6 +8149,17 @@ static void rna_def_simulation_zone_viewer_path_elem(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Simulation Output Node ID", ""); } +static void rna_def_repeat_zone_viewer_path_elem(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "RepeatZoneViewerPathElem", "ViewerPathElem"); + + prop = RNA_def_property(srna, "repeat_output_node_id", PROP_INT, PROP_NONE); + RNA_def_property_ui_text(prop, "Repeat Output Node ID", ""); +} + static void rna_def_viewer_node_viewer_path_elem(BlenderRNA *brna) { StructRNA *srna; @@ -8167,6 +8181,7 @@ static void rna_def_viewer_path(BlenderRNA *brna) rna_def_modifier_viewer_path_elem(brna); rna_def_group_node_viewer_path_elem(brna); rna_def_simulation_zone_viewer_path_elem(brna); + rna_def_repeat_zone_viewer_path_elem(brna); rna_def_viewer_node_viewer_path_elem(brna); srna = RNA_def_struct(brna, "ViewerPath", nullptr); diff --git a/source/blender/makesrna/intern/rna_userdef.cc b/source/blender/makesrna/intern/rna_userdef.cc index 142771b5a8e..ebf55f2d5c9 100644 --- a/source/blender/makesrna/intern/rna_userdef.cc +++ b/source/blender/makesrna/intern/rna_userdef.cc @@ -3063,6 +3063,12 @@ static void rna_def_userdef_theme_space_node(BlenderRNA *brna) RNA_def_property_array(prop, 4); RNA_def_property_ui_text(prop, "Simulation Zone", ""); RNA_def_property_update(prop, 0, "rna_userdef_theme_update"); + + prop = RNA_def_property(srna, "repeat_zone", PROP_FLOAT, PROP_COLOR_GAMMA); + RNA_def_property_float_sdna(prop, NULL, "node_zone_repeat"); + RNA_def_property_array(prop, 4); + RNA_def_property_ui_text(prop, "Repeat Zone", ""); + RNA_def_property_update(prop, 0, "rna_userdef_theme_update"); } static void rna_def_userdef_theme_space_buts(BlenderRNA *brna) diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index f0aa23e99e1..f3b85d08277 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -474,6 +474,27 @@ static void find_side_effect_nodes_for_viewer_path( zone = next_zone; break; } + case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: { + const auto &typed_elem = *reinterpret_cast(elem); + const bke::bNodeTreeZone *next_zone = tree_zones->get_zone_by_node( + typed_elem.repeat_output_node_id); + if (next_zone == nullptr) { + return; + } + if (next_zone->parent_zone != zone) { + return; + } + const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default( + next_zone, nullptr); + if (lf_zone_node == nullptr) { + return; + } + local_side_effect_nodes.add(compute_context_builder.hash(), lf_zone_node); + compute_context_builder.push(*next_zone->output_node, + typed_elem.iteration); + zone = next_zone; + break; + } case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: { const auto &typed_elem = *reinterpret_cast(elem); const bNode *node = group->node_by_id(typed_elem.node_id); diff --git a/source/blender/nodes/NOD_geometry.hh b/source/blender/nodes/NOD_geometry.hh index d91ee352fe7..5a9d881627e 100644 --- a/source/blender/nodes/NOD_geometry.hh +++ b/source/blender/nodes/NOD_geometry.hh @@ -24,6 +24,9 @@ bNode *NOD_geometry_simulation_input_get_paired_output(bNodeTree *node_tree, bool NOD_geometry_simulation_input_pair_with_output(const bNodeTree *node_tree, bNode *simulation_input_node, const bNode *simulation_output_node); +bool NOD_geometry_repeat_input_pair_with_output(const bNodeTree *node_tree, + bNode *repeat_input_node, + const bNode *repeat_output_node); /** \} */ diff --git a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh index 7b4b7203001..a817b58f2a4 100644 --- a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh +++ b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh @@ -262,8 +262,8 @@ std::unique_ptr get_simulation_input_lazy_function( GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info); std::unique_ptr get_switch_node_lazy_function(const bNode &node); -bke::sim::SimulationZoneID get_simulation_zone_id(const GeoNodesLFUserData &user_data, - const int output_node_id); +std::optional get_simulation_zone_id( + const GeoNodesLFUserData &user_data, const int output_node_id); /** * An anonymous attribute created by a node. diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index ca346ecdb86..6387722e390 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -396,6 +396,8 @@ DefNode(GeometryNode, GEO_NODE_PROXIMITY, def_geo_proximity, "PROXIMITY", Proxim DefNode(GeometryNode, GEO_NODE_RAYCAST, def_geo_raycast, "RAYCAST", Raycast, "Raycast", "Cast rays from the context geometry onto a target geometry, and retrieve information from each hit point") DefNode(GeometryNode, GEO_NODE_REALIZE_INSTANCES, 0, "REALIZE_INSTANCES", RealizeInstances, "Realize Instances", "Convert instances into real geometry data") DefNode(GeometryNode, GEO_NODE_REMOVE_ATTRIBUTE, 0, "REMOVE_ATTRIBUTE", RemoveAttribute, "Remove Named Attribute", "Delete an attribute with a specified name from a geometry. Typically used to optimize performance") +DefNode(GeometryNode, GEO_NODE_REPEAT_INPUT, def_geo_repeat_input, "REPEAT_INPUT", RepeatInput, "Repeat Input", "") +DefNode(GeometryNode, GEO_NODE_REPEAT_OUTPUT, def_geo_repeat_output, "REPEAT_OUTPUT", RepeatOutput, "Repeat Output", "") DefNode(GeometryNode, GEO_NODE_REPLACE_MATERIAL, 0, "REPLACE_MATERIAL", ReplaceMaterial, "Replace Material", "Swap one material with another") DefNode(GeometryNode, GEO_NODE_RESAMPLE_CURVE, def_geo_curve_resample, "RESAMPLE_CURVE", ResampleCurve, "Resample Curve", "Generate a poly spline for each input spline") DefNode(GeometryNode, GEO_NODE_REVERSE_CURVE, 0, "REVERSE_CURVE", ReverseCurve, "Reverse Curve", "Change the direction of curves by swapping their start and end data") diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 3111dea5acf..cb096b61764 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -148,6 +148,8 @@ set(SRC nodes/node_geo_raycast.cc nodes/node_geo_realize_instances.cc nodes/node_geo_remove_attribute.cc + nodes/node_geo_repeat_input.cc + nodes/node_geo_repeat_output.cc nodes/node_geo_rotate_instances.cc nodes/node_geo_sample_index.cc nodes/node_geo_sample_nearest.cc diff --git a/source/blender/nodes/geometry/node_geometry_register.cc b/source/blender/nodes/geometry/node_geometry_register.cc index 97c24f39a10..c2c2526586d 100644 --- a/source/blender/nodes/geometry/node_geometry_register.cc +++ b/source/blender/nodes/geometry/node_geometry_register.cc @@ -135,6 +135,8 @@ void register_geometry_nodes() register_node_type_geo_raycast(); register_node_type_geo_realize_instances(); register_node_type_geo_remove_attribute(); + register_node_type_geo_repeat_input(); + register_node_type_geo_repeat_output(); register_node_type_geo_rotate_instances(); register_node_type_geo_sample_index(); register_node_type_geo_sample_nearest_surface(); diff --git a/source/blender/nodes/geometry/node_geometry_register.hh b/source/blender/nodes/geometry/node_geometry_register.hh index 879d181343a..afe7b61f693 100644 --- a/source/blender/nodes/geometry/node_geometry_register.hh +++ b/source/blender/nodes/geometry/node_geometry_register.hh @@ -132,6 +132,8 @@ void register_node_type_geo_proximity(); void register_node_type_geo_raycast(); void register_node_type_geo_realize_instances(); void register_node_type_geo_remove_attribute(); +void register_node_type_geo_repeat_input(); +void register_node_type_geo_repeat_output(); void register_node_type_geo_rotate_instances(); void register_node_type_geo_sample_index(); void register_node_type_geo_sample_nearest_surface(); diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index 6c069db8af2..d319dca343a 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -160,4 +160,7 @@ void copy_with_checked_indices(const GVArray &src, const IndexMask &mask, GMutableSpan dst); +void socket_declarations_for_repeat_items(const Span items, + NodeDeclaration &r_declaration); + } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_repeat_input.cc b/source/blender/nodes/geometry/nodes/node_geo_repeat_input.cc new file mode 100644 index 00000000000..e726fd5f7ae --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_repeat_input.cc @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_compute_contexts.hh" +#include "BKE_scene.h" + +#include "DEG_depsgraph_query.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "NOD_geometry.hh" +#include "NOD_socket.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_repeat_input_cc { + +NODE_STORAGE_FUNCS(NodeGeometryRepeatInput); + +static void node_declare_dynamic(const bNodeTree &tree, + const bNode &node, + NodeDeclaration &r_declaration) +{ + NodeDeclarationBuilder b{r_declaration}; + b.add_input(N_("Iterations")).min(0).default_value(1); + + const NodeGeometryRepeatInput &storage = node_storage(node); + const bNode *output_node = tree.node_by_id(storage.output_node_id); + if (output_node != nullptr) { + const NodeGeometryRepeatOutput &output_storage = + *static_cast(output_node->storage); + socket_declarations_for_repeat_items(output_storage.items_span(), r_declaration); + } +} + +static void node_init(bNodeTree * /*tree*/, bNode *node) +{ + NodeGeometryRepeatInput *data = MEM_cnew(__func__); + /* Needs to be initialized for the node to work. */ + data->output_node_id = 0; + node->storage = data; +} + +static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link) +{ + const bNode *output_node = ntree->node_by_id(node_storage(*node).output_node_id); + if (!output_node) { + return true; + } + auto &storage = *static_cast(output_node->storage); + if (link->tonode == node) { + if (link->tosock->identifier == StringRef("__extend__")) { + if (const NodeRepeatItem *item = storage.add_item(link->fromsock->name, + eNodeSocketDatatype(link->fromsock->type))) + { + update_node_declaration_and_sockets(*ntree, *node); + link->tosock = nodeFindSocket(node, SOCK_IN, item->identifier_str().c_str()); + return true; + } + } + else { + return true; + } + } + if (link->fromnode == node) { + if (link->fromsock->identifier == StringRef("__extend__")) { + if (const NodeRepeatItem *item = storage.add_item(link->tosock->name, + eNodeSocketDatatype(link->tosock->type))) + { + update_node_declaration_and_sockets(*ntree, *node); + link->fromsock = nodeFindSocket(node, SOCK_OUT, item->identifier_str().c_str()); + return true; + } + } + else { + return true; + } + } + return false; +} + +} // namespace blender::nodes::node_geo_repeat_input_cc + +void register_node_type_geo_repeat_input() +{ + namespace file_ns = blender::nodes::node_geo_repeat_input_cc; + + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_REPEAT_INPUT, "Repeat Input", NODE_CLASS_INTERFACE); + ntype.initfunc = file_ns::node_init; + ntype.declare_dynamic = file_ns::node_declare_dynamic; + ntype.gather_add_node_search_ops = nullptr; + ntype.gather_link_search_ops = nullptr; + ntype.insert_link = file_ns::node_insert_link; + node_type_storage( + &ntype, "NodeGeometryRepeatInput", node_free_standard_storage, node_copy_standard_storage); + nodeRegisterType(&ntype); +} + +bool NOD_geometry_repeat_input_pair_with_output(const bNodeTree *node_tree, + bNode *repeat_input_node, + const bNode *repeat_output_node) +{ + namespace file_ns = blender::nodes::node_geo_repeat_input_cc; + + BLI_assert(repeat_input_node->type == GEO_NODE_REPEAT_INPUT); + if (repeat_output_node->type != GEO_NODE_REPEAT_OUTPUT) { + return false; + } + + /* Allow only one input paired to an output. */ + for (const bNode *other_input_node : node_tree->nodes_by_type("GeometryNodeRepeatInput")) { + if (other_input_node != repeat_input_node) { + const NodeGeometryRepeatInput &other_storage = file_ns::node_storage(*other_input_node); + if (other_storage.output_node_id == repeat_output_node->identifier) { + return false; + } + } + } + + NodeGeometryRepeatInput &storage = file_ns::node_storage(*repeat_input_node); + storage.output_node_id = repeat_output_node->identifier; + return true; +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_repeat_output.cc b/source/blender/nodes/geometry/nodes/node_geo_repeat_output.cc new file mode 100644 index 00000000000..75842182a34 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_repeat_output.cc @@ -0,0 +1,333 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_compute_contexts.hh" +#include "BKE_scene.h" + +#include "DEG_depsgraph_query.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "NOD_add_node_search.hh" +#include "NOD_geometry.hh" +#include "NOD_socket.hh" + +#include "BLI_string_utils.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static std::unique_ptr socket_declaration_for_repeat_item( + const NodeRepeatItem &item, const eNodeSocketInOut in_out, const int corresponding_input = -1) +{ + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type); + + std::unique_ptr decl; + + auto handle_field_decl = [&](SocketDeclaration &decl) { + if (in_out == SOCK_IN) { + decl.input_field_type = InputSocketFieldType::IsSupported; + } + else { + decl.output_field_dependency = OutputFieldDependency::ForPartiallyDependentField( + {corresponding_input}); + } + }; + + switch (socket_type) { + case SOCK_FLOAT: + decl = std::make_unique(); + handle_field_decl(*decl); + break; + case SOCK_VECTOR: + decl = std::make_unique(); + handle_field_decl(*decl); + break; + case SOCK_RGBA: + decl = std::make_unique(); + handle_field_decl(*decl); + break; + case SOCK_BOOLEAN: + decl = std::make_unique(); + handle_field_decl(*decl); + break; + case SOCK_ROTATION: + decl = std::make_unique(); + handle_field_decl(*decl); + break; + case SOCK_INT: + decl = std::make_unique(); + handle_field_decl(*decl); + break; + case SOCK_STRING: + decl = std::make_unique(); + break; + case SOCK_GEOMETRY: + decl = std::make_unique(); + break; + case SOCK_OBJECT: + decl = std::make_unique(); + break; + case SOCK_IMAGE: + decl = std::make_unique(); + break; + case SOCK_COLLECTION: + decl = std::make_unique(); + break; + case SOCK_MATERIAL: + decl = std::make_unique(); + break; + default: + BLI_assert_unreachable(); + break; + } + + decl->name = item.name ? item.name : ""; + decl->identifier = item.identifier_str(); + decl->in_out = in_out; + return decl; +} + +void socket_declarations_for_repeat_items(const Span items, + NodeDeclaration &r_declaration) +{ + for (const int i : items.index_range()) { + const NodeRepeatItem &item = items[i]; + r_declaration.inputs.append(socket_declaration_for_repeat_item(item, SOCK_IN)); + r_declaration.outputs.append( + socket_declaration_for_repeat_item(item, SOCK_OUT, r_declaration.inputs.size() - 1)); + } + r_declaration.inputs.append(decl::create_extend_declaration(SOCK_IN)); + r_declaration.outputs.append(decl::create_extend_declaration(SOCK_OUT)); +} +} // namespace blender::nodes +namespace blender::nodes::node_geo_repeat_output_cc { + +NODE_STORAGE_FUNCS(NodeGeometryRepeatOutput); + +static void node_declare_dynamic(const bNodeTree & /*node_tree*/, + const bNode &node, + NodeDeclaration &r_declaration) +{ + const NodeGeometryRepeatOutput &storage = node_storage(node); + socket_declarations_for_repeat_items(storage.items_span(), r_declaration); +} + +static void search_node_add_ops(GatherAddNodeSearchParams ¶ms) +{ + AddNodeItem item; + item.ui_name = IFACE_("Repeat Zone"); + item.description = TIP_("Add a new repeat input and output nodes to the node tree"); + item.add_fn = [](const bContext &C, bNodeTree &node_tree, float2 cursor) { + bNode *input = nodeAddNode(&C, &node_tree, "GeometryNodeRepeatInput"); + bNode *output = nodeAddNode(&C, &node_tree, "GeometryNodeRepeatOutput"); + static_cast(input->storage)->output_node_id = output->identifier; + + NodeRepeatItem &item = node_storage(*output).items[0]; + + update_node_declaration_and_sockets(node_tree, *input); + update_node_declaration_and_sockets(node_tree, *output); + + const std::string identifier = item.identifier_str(); + nodeAddLink(&node_tree, + input, + nodeFindSocket(input, SOCK_OUT, identifier.c_str()), + output, + nodeFindSocket(output, SOCK_IN, identifier.c_str())); + + input->locx = cursor.x / UI_SCALE_FAC - 150; + input->locy = cursor.y / UI_SCALE_FAC + 20; + output->locx = cursor.x / UI_SCALE_FAC + 150; + output->locy = cursor.y / UI_SCALE_FAC + 20; + + return Vector({input, output}); + }; + params.add_item(std::move(item)); +} + +static void node_init(bNodeTree * /*tree*/, bNode *node) +{ + NodeGeometryRepeatOutput *data = MEM_cnew(__func__); + + data->next_identifier = 0; + + data->items = MEM_cnew_array(1, __func__); + data->items[0].name = BLI_strdup(DATA_("Geometry")); + data->items[0].socket_type = SOCK_GEOMETRY; + data->items[0].identifier = data->next_identifier++; + data->items_num = 1; + + node->storage = data; +} + +static void node_free_storage(bNode *node) +{ + NodeGeometryRepeatOutput &storage = node_storage(*node); + for (NodeRepeatItem &item : storage.items_span()) { + MEM_SAFE_FREE(item.name); + } + MEM_SAFE_FREE(storage.items); + MEM_freeN(node->storage); +} + +static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node) +{ + const NodeGeometryRepeatOutput &src_storage = node_storage(*src_node); + NodeGeometryRepeatOutput *dst_storage = MEM_cnew(__func__); + + dst_storage->items = MEM_cnew_array(src_storage.items_num, __func__); + dst_storage->items_num = src_storage.items_num; + dst_storage->active_index = src_storage.active_index; + dst_storage->next_identifier = src_storage.next_identifier; + for (const int i : IndexRange(src_storage.items_num)) { + if (char *name = src_storage.items[i].name) { + dst_storage->items[i].identifier = src_storage.items[i].identifier; + dst_storage->items[i].name = BLI_strdup(name); + dst_storage->items[i].socket_type = src_storage.items[i].socket_type; + } + } + + dst_node->storage = dst_storage; +} + +static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link) +{ + NodeGeometryRepeatOutput &storage = node_storage(*node); + if (link->tonode == node) { + if (link->tosock->identifier == StringRef("__extend__")) { + if (const NodeRepeatItem *item = storage.add_item(link->fromsock->name, + eNodeSocketDatatype(link->fromsock->type))) + { + update_node_declaration_and_sockets(*ntree, *node); + link->tosock = nodeFindSocket(node, SOCK_IN, item->identifier_str().c_str()); + return true; + } + } + else { + return true; + } + } + if (link->fromnode == node) { + if (link->fromsock->identifier == StringRef("__extend__")) { + if (const NodeRepeatItem *item = storage.add_item(link->tosock->name, + eNodeSocketDatatype(link->tosock->type))) + { + update_node_declaration_and_sockets(*ntree, *node); + link->fromsock = nodeFindSocket(node, SOCK_OUT, item->identifier_str().c_str()); + return true; + } + } + else { + return true; + } + } + return false; +} + +} // namespace blender::nodes::node_geo_repeat_output_cc + +blender::Span NodeGeometryRepeatOutput::items_span() const +{ + return blender::Span(items, items_num); +} + +blender::MutableSpan NodeGeometryRepeatOutput::items_span() +{ + return blender::MutableSpan(items, items_num); +} + +bool NodeRepeatItem::supports_type(const eNodeSocketDatatype type) +{ + return ELEM(type, + SOCK_FLOAT, + SOCK_VECTOR, + SOCK_RGBA, + SOCK_BOOLEAN, + SOCK_ROTATION, + SOCK_INT, + SOCK_STRING, + SOCK_GEOMETRY, + SOCK_OBJECT, + SOCK_MATERIAL, + SOCK_IMAGE, + SOCK_COLLECTION); +} + +std::string NodeRepeatItem::identifier_str() const +{ + return "Item_" + std::to_string(this->identifier); +} + +NodeRepeatItem *NodeGeometryRepeatOutput::add_item(const char *name, + const eNodeSocketDatatype type) +{ + if (!NodeRepeatItem::supports_type(type)) { + return nullptr; + } + const int insert_index = this->items_num; + NodeRepeatItem *old_items = this->items; + + this->items = MEM_cnew_array(this->items_num + 1, __func__); + std::copy_n(old_items, insert_index, this->items); + NodeRepeatItem &new_item = this->items[insert_index]; + std::copy_n(old_items + insert_index + 1, + this->items_num - insert_index, + this->items + insert_index + 1); + + new_item.identifier = this->next_identifier++; + this->set_item_name(new_item, name); + new_item.socket_type = type; + + this->items_num++; + MEM_SAFE_FREE(old_items); + return &new_item; +} + +void NodeGeometryRepeatOutput::set_item_name(NodeRepeatItem &item, const char *name) +{ + char unique_name[MAX_NAME + 4]; + STRNCPY(unique_name, name); + + struct Args { + NodeGeometryRepeatOutput *storage; + const NodeRepeatItem *item; + } args = {this, &item}; + + const char *default_name = nodeStaticSocketLabel(item.socket_type, 0); + BLI_uniquename_cb( + [](void *arg, const char *name) { + const Args &args = *static_cast(arg); + for (const NodeRepeatItem &item : args.storage->items_span()) { + if (&item != args.item) { + if (STREQ(item.name, name)) { + return true; + } + } + } + return false; + }, + &args, + default_name, + '.', + unique_name, + ARRAY_SIZE(unique_name)); + + MEM_SAFE_FREE(item.name); + item.name = BLI_strdup(unique_name); +} + +void register_node_type_geo_repeat_output() +{ + namespace file_ns = blender::nodes::node_geo_repeat_output_cc; + + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_REPEAT_OUTPUT, "Repeat Output", NODE_CLASS_INTERFACE); + ntype.initfunc = file_ns::node_init; + ntype.declare_dynamic = file_ns::node_declare_dynamic; + ntype.gather_add_node_search_ops = file_ns::search_node_add_ops; + ntype.insert_link = file_ns::node_insert_link; + node_type_storage( + &ntype, "NodeGeometryRepeatOutput", file_ns::node_free_storage, file_ns::node_copy_storage); + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc b/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc index bda153b0207..c7bd9087db1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc @@ -70,12 +70,17 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction { params.set_output(0, fn::ValueOrField(delta_time)); } - const bke::sim::SimulationZoneID zone_id = get_simulation_zone_id(user_data, output_node_id_); + const std::optional zone_id = get_simulation_zone_id( + user_data, output_node_id_); + if (!zone_id) { + params.set_default_remaining_outputs(); + return; + } /* When caching is turned off and the old state doesn't need to persist, moving data * from the last state instead of copying it can avoid copies of geometry data arrays. */ if (auto *state = modifier_data.prev_simulation_state_mutable) { - if (bke::sim::SimulationZoneState *zone = state->get_zone_state(zone_id)) { + if (bke::sim::SimulationZoneState *zone = state->get_zone_state(*zone_id)) { this->output_simulation_state_move(params, user_data, *zone); return; } @@ -83,7 +88,7 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction { /* If there is a read-only state from the last frame, output that directly. */ if (const auto *state = modifier_data.prev_simulation_state) { - if (const bke::sim::SimulationZoneState *zone = state->get_zone_state(zone_id)) { + if (const bke::sim::SimulationZoneState *zone = state->get_zone_state(*zone_id)) { this->output_simulation_state_copy(params, user_data, *zone); return; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc b/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc index 439a5011ea9..3b15f0271d5 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc @@ -777,11 +777,16 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { EvalData &eval_data = *static_cast(context.storage); BLI_SCOPED_DEFER([&]() { eval_data.is_first_evaluation = false; }); - const bke::sim::SimulationZoneID zone_id = get_simulation_zone_id(user_data, node_.identifier); + const std::optional zone_id = get_simulation_zone_id( + user_data, node_.identifier); + if (!zone_id) { + params.set_default_remaining_outputs(); + return; + } const bke::sim::SimulationZoneState *current_zone_state = modifier_data.current_simulation_state ? - modifier_data.current_simulation_state->get_zone_state(zone_id) : + modifier_data.current_simulation_state->get_zone_state(*zone_id) : nullptr; if (eval_data.is_first_evaluation && current_zone_state != nullptr) { /* Common case when data is cached already. */ @@ -792,7 +797,7 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { if (modifier_data.current_simulation_state_for_write == nullptr) { const bke::sim::SimulationZoneState *prev_zone_state = modifier_data.prev_simulation_state ? - modifier_data.prev_simulation_state->get_zone_state(zone_id) : + modifier_data.prev_simulation_state->get_zone_state(*zone_id) : nullptr; if (prev_zone_state == nullptr) { /* There is no previous simulation state and we also don't create a new one, so just @@ -802,7 +807,7 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { } const bke::sim::SimulationZoneState *next_zone_state = modifier_data.next_simulation_state ? - modifier_data.next_simulation_state->get_zone_state(zone_id) : + modifier_data.next_simulation_state->get_zone_state(*zone_id) : nullptr; if (next_zone_state == nullptr) { /* Output the last cached simulation state. */ @@ -820,7 +825,7 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { } bke::sim::SimulationZoneState &new_zone_state = - modifier_data.current_simulation_state_for_write->get_zone_state_for_write(zone_id); + modifier_data.current_simulation_state_for_write->get_zone_state_for_write(*zone_id); if (eval_data.is_first_evaluation) { new_zone_state.item_by_identifier.clear(); } @@ -906,8 +911,8 @@ std::unique_ptr get_simulation_output_lazy_function( return std::make_unique(node, own_lf_graph_info); } -bke::sim::SimulationZoneID get_simulation_zone_id(const GeoNodesLFUserData &user_data, - const int output_node_id) +std::optional get_simulation_zone_id( + const GeoNodesLFUserData &user_data, const int output_node_id) { Vector node_ids; for (const ComputeContext *context = user_data.compute_context; context != nullptr; @@ -916,6 +921,10 @@ bke::sim::SimulationZoneID get_simulation_zone_id(const GeoNodesLFUserData &user if (const auto *node_context = dynamic_cast(context)) { node_ids.append(node_context->node_id()); } + else if (dynamic_cast(context) != nullptr) { + /* Simulation can't be used in a repeat zone. */ + return std::nullopt; + } } std::reverse(node_ids.begin(), node_ids.end()); node_ids.append(output_node_id); diff --git a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc index eec06911c35..737c58a6497 100644 --- a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc +++ b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc @@ -43,6 +43,7 @@ #include "BKE_type_conversions.hh" #include "FN_field_cpp_type.hh" +#include "FN_lazy_function_execute.hh" #include "FN_lazy_function_graph_executor.hh" #include "DEG_depsgraph_query.h" @@ -1421,6 +1422,300 @@ struct ZoneBuildInfo { Map attribute_set_input_by_caller_propagation_index; }; +/** + * Describes what the individual inputs and outputs of the #LazyFunction mean that's created for + * the repeat body. + */ +struct RepeatBodyIndices { + IndexRange main_inputs; + IndexRange main_outputs; + IndexRange border_link_inputs; + + IndexRange main_input_usages; + IndexRange main_output_usages; + IndexRange border_link_usages; + + /** + * Some anonymous attribute sets are input into the repeat body from the outside. These two maps + * indicate which repeat body input corresponds to attribute set. Attribute sets are identified + * by either a "field source index" or "caller propagation index". + */ + Map attribute_set_input_by_field_source_index; + Map attribute_set_input_by_caller_propagation_index; +}; + +class LazyFunctionForRepeatZone : public LazyFunction { + private: + const bNodeTreeZone &zone_; + const bNode &repeat_output_bnode_; + const ZoneBuildInfo &zone_info_; + const LazyFunction &body_fn_; + const RepeatBodyIndices &body_indices_; + + public: + LazyFunctionForRepeatZone(const bNodeTreeZone &zone, + ZoneBuildInfo &zone_info, + const LazyFunction &body_fn, + const RepeatBodyIndices &body_indices) + : zone_(zone), + repeat_output_bnode_(*zone.output_node), + zone_info_(zone_info), + body_fn_(body_fn), + body_indices_(body_indices) + { + debug_name_ = "Repeat Zone"; + + for (const bNodeSocket *socket : zone.input_node->input_sockets().drop_back(1)) { + inputs_.append_as(socket->name, *socket->typeinfo->geometry_nodes_cpp_type); + } + zone_info.main_input_indices = inputs_.index_range(); + + for (const bNodeLink *link : zone.border_links) { + inputs_.append_as(link->fromsock->name, *link->tosock->typeinfo->geometry_nodes_cpp_type); + } + zone_info.border_link_input_indices = inputs_.index_range().take_back( + zone.border_links.size()); + + for (const bNodeSocket *socket : zone.output_node->output_sockets().drop_back(1)) { + inputs_.append_as("Usage", CPPType::get()); + outputs_.append_as(socket->name, *socket->typeinfo->geometry_nodes_cpp_type); + } + zone_info.main_output_usage_indices = inputs_.index_range().take_back( + zone.output_node->output_sockets().drop_back(1).size()); + zone_info.main_output_indices = outputs_.index_range(); + + for ([[maybe_unused]] const bNodeSocket *socket : + zone.input_node->input_sockets().drop_back(1)) { + outputs_.append_as("Usage", CPPType::get()); + } + zone_info.main_input_usage_indices = outputs_.index_range().take_back( + zone.input_node->input_sockets().drop_back(1).size()); + for ([[maybe_unused]] const bNodeLink *link : zone.border_links) { + outputs_.append_as("Border Link Usage", CPPType::get()); + } + zone_info.border_link_input_usage_indices = outputs_.index_range().take_back( + zone.border_links.size()); + + for (const auto item : body_indices.attribute_set_input_by_field_source_index.items()) { + const int index = inputs_.append_and_get_index_as( + "Attribute Set", CPPType::get()); + zone_info.attribute_set_input_by_field_source_index.add_new(item.key, index); + } + for (const auto item : body_indices.attribute_set_input_by_caller_propagation_index.items()) { + const int index = inputs_.append_and_get_index_as( + "Attribute Set", CPPType::get()); + zone_info.attribute_set_input_by_caller_propagation_index.add_new(item.key, index); + } + } + + void execute_impl(lf::Params ¶ms, const lf::Context &context) const override + { + GeoNodesLFUserData &user_data = *static_cast(context.user_data); + + const NodeGeometryRepeatOutput &node_storage = *static_cast( + repeat_output_bnode_.storage); + + /* Number of iterations to evaluate. */ + const int iterations = std::max( + 0, params.get_input>(zone_info_.main_input_indices[0]).as_value()); + + const int repeat_items_num = node_storage.items_num; + /* Gather data types of the repeat items. */ + Array repeat_item_types(repeat_items_num); + for (const int i : body_indices_.main_inputs.index_range()) { + const int input_i = body_indices_.main_inputs[i]; + const CPPType &type = *body_fn_.inputs()[input_i].type; + repeat_item_types[i] = &type; + } + + LinearAllocator<> allocator; + Array repeat_item_values((iterations + 1) * repeat_items_num, nullptr); + /* Allocate memory for the looped values. */ + for (const int iteration : IndexRange(iterations)) { + MutableSpan item_values = repeat_item_values.as_mutable_span().slice( + (iteration + 1) * repeat_items_num, repeat_items_num); + for (const int item_i : IndexRange(repeat_items_num)) { + const CPPType &type = *repeat_item_types[item_i]; + void *buffer = allocator.allocate(type.size(), type.alignment()); + item_values[item_i] = buffer; + } + } + + /* Load the inputs of the first repeat iteration. */ + MutableSpan first_item_values = repeat_item_values.as_mutable_span().take_front( + repeat_items_num); + for (const int i : IndexRange(repeat_items_num)) { + /* +1 because of the iterations input. */ + const int input_index = zone_info_.main_input_indices[i + 1]; + void *value = params.try_get_input_data_ptr(input_index); + BLI_assert(value != nullptr); + first_item_values[i] = value; + } + + /* Load border link values. */ + const int border_links_num = zone_info_.border_link_input_indices.size(); + Array border_link_input_values(border_links_num, nullptr); + for (const int i : IndexRange(border_links_num)) { + const int input_index = zone_info_.border_link_input_indices[i]; + void *value = params.try_get_input_data_ptr(input_index); + BLI_assert(value != nullptr); + border_link_input_values[i] = value; + } + + /* Load attribute sets that are needed to propagate attributes correctly in the zone. */ + Map attribute_set_by_field_source_index; + Map attribute_set_by_caller_propagation_index; + for (const auto item : zone_info_.attribute_set_input_by_field_source_index.items()) { + bke::AnonymousAttributeSet &attribute_set = params.get_input( + item.value); + attribute_set_by_field_source_index.add_new(item.key, &attribute_set); + } + for (const auto item : zone_info_.attribute_set_input_by_caller_propagation_index.items()) { + bke::AnonymousAttributeSet &attribute_set = params.get_input( + item.value); + attribute_set_by_caller_propagation_index.add_new(item.key, &attribute_set); + } + + const int body_inputs_num = body_fn_.inputs().size(); + const int body_outputs_num = body_fn_.outputs().size(); + /* Evaluate the repeat zone eagerly, one iteration at a time. + * This can be made more lazy as a separate step. */ + for (const int iteration : IndexRange(iterations)) { + /* Prepare all data that has to be passed into the evaluation of the repeat zone body. */ + Array inputs(body_inputs_num); + Array outputs(body_outputs_num); + Array> input_usages(body_inputs_num); + Array output_usages(body_outputs_num, lf::ValueUsage::Used); + Array set_outputs(body_outputs_num, false); + + /* Prepare pointers to the main input and output values of the repeat zone, + * as well as their usages. */ + Array tmp_main_input_usages(repeat_items_num); + for (const int i : IndexRange(repeat_items_num)) { + const CPPType &type = *repeat_item_types[i]; + void *prev_value = repeat_item_values[iteration * repeat_items_num + i]; + void *next_value = repeat_item_values[(iteration + 1) * repeat_items_num + i]; + inputs[body_indices_.main_inputs[i]] = {type, prev_value}; + outputs[body_indices_.main_outputs[i]] = {type, next_value}; + outputs[body_indices_.main_input_usages[i]] = &tmp_main_input_usages[i]; + } + static bool static_true = true; + for (const int input_index : body_indices_.main_output_usages) { + /* All main outputs are used currently. */ + inputs[input_index] = &static_true; + } + /* Prepare border link values for the repeat body. */ + Array tmp_border_link_usages(border_links_num); + for (const int i : IndexRange(border_links_num)) { + const int input_index = body_indices_.border_link_inputs[i]; + const int usage_index = body_indices_.border_link_usages[i]; + const CPPType &type = *body_fn_.inputs()[input_index].type; + /* Need to copy because a lazy function is allowed to modify the input (e.g. move from + * it). */ + void *value_copy = allocator.allocate(type.size(), type.alignment()); + type.copy_construct(border_link_input_values[i], value_copy); + inputs[input_index] = {type, value_copy}; + outputs[usage_index] = &tmp_border_link_usages[i]; + } + + /* Prepare attribute sets that are passed into the repeat body. */ + for (const auto item : body_indices_.attribute_set_input_by_field_source_index.items()) { + bke::AnonymousAttributeSet &attribute_set = + *allocator + .construct( + *attribute_set_by_field_source_index.lookup(item.key)) + .release(); + inputs[item.value] = &attribute_set; + } + for (const auto item : body_indices_.attribute_set_input_by_caller_propagation_index.items()) + { + bke::AnonymousAttributeSet &attribute_set = + *allocator + .construct( + *attribute_set_by_caller_propagation_index.lookup(item.key)) + .release(); + inputs[item.value] = &attribute_set; + } + + /* Prepare evaluation context for the repeat body. */ + bke::RepeatZoneComputeContext body_compute_context{ + user_data.compute_context, repeat_output_bnode_, iteration}; + GeoNodesLFUserData body_user_data = user_data; + body_user_data.compute_context = &body_compute_context; + if (user_data.modifier_data->socket_log_contexts) { + body_user_data.log_socket_values = user_data.modifier_data->socket_log_contexts->contains( + body_compute_context.hash()); + } + GeoNodesLFLocalUserData body_local_user_data{body_user_data}; + void *body_storage = body_fn_.init_storage(allocator); + lf::Context body_context{body_storage, &body_user_data, &body_local_user_data}; + + lf::BasicParams body_params{ + body_fn_, inputs, outputs, input_usages, output_usages, set_outputs}; + /* Actually evaluate the repeat body. */ + body_fn_.execute(body_params, body_context); + + /* Destruct values that are not needed after the evaluation anymore. */ + body_fn_.destruct_storage(body_storage); + for (const int i : body_indices_.border_link_inputs) { + inputs[i].destruct(); + } + for (const int i : body_indices_.attribute_set_input_by_field_source_index.values()) { + inputs[i].destruct(); + } + for (const int i : body_indices_.attribute_set_input_by_caller_propagation_index.values()) { + inputs[i].destruct(); + } + } + + /* Set outputs of the repeat zone. */ + for (const int i : IndexRange(repeat_items_num)) { + void *computed_value = repeat_item_values[iterations * repeat_items_num + i]; + const int output_index = zone_info_.main_output_indices[i]; + void *r_value = params.get_output_data_ptr(output_index); + const CPPType &type = *repeat_item_types[i]; + type.move_construct(computed_value, r_value); + params.output_set(output_index); + } + for (const int i : zone_info_.main_input_usage_indices) { + params.set_output(i, true); + } + for (const int i : IndexRange(border_links_num)) { + params.set_output(zone_info_.border_link_input_usage_indices[i], true); + } + + /* Destruct remaining values. */ + for (const int iteration : IndexRange(iterations)) { + MutableSpan item_values = repeat_item_values.as_mutable_span().slice( + (iteration + 1) * repeat_items_num, repeat_items_num); + for (const int item_i : IndexRange(repeat_items_num)) { + const CPPType &type = *repeat_item_types[item_i]; + type.destruct(item_values[item_i]); + } + } + } + + std::string input_name(const int i) const override + { + if (zone_info_.main_output_usage_indices.contains(i)) { + const bNodeSocket &bsocket = zone_.output_node->output_socket( + i - zone_info_.main_output_usage_indices.first()); + return "Usage: " + StringRef(bsocket.name); + } + return inputs_[i].debug_name; + } + + std::string output_name(const int i) const override + { + if (zone_info_.main_input_usage_indices.contains(i)) { + const bNodeSocket &bsocket = zone_.input_node->input_socket( + i - zone_info_.main_input_usage_indices.first()); + return "Usage: " + StringRef(bsocket.name); + } + return outputs_[i].debug_name; + } +}; + /** * Utility class to build a lazy-function graph based on a geometry nodes tree. * This is mainly a separate class because it makes it easier to have variables that can be @@ -1446,7 +1741,7 @@ struct GeometryNodesLazyFunctionGraphBuilder { Map simulation_inputs_usage_nodes_; const bNodeTreeZones *tree_zones_; - Array zone_build_infos_; + MutableSpan zone_build_infos_; friend class UsedSocketVisualizeOptions; @@ -1492,14 +1787,27 @@ struct GeometryNodesLazyFunctionGraphBuilder { */ void build_zone_functions() { - zone_build_infos_.reinitialize(tree_zones_->zones.size()); + zone_build_infos_ = scope_.linear_allocator().construct_array( + tree_zones_->zones.size()); const Array zone_build_order = this->compute_zone_build_order(); for (const int zone_i : zone_build_order) { const bNodeTreeZone &zone = *tree_zones_->zones[zone_i]; - BLI_assert(zone.output_node->type == GEO_NODE_SIMULATION_OUTPUT); - this->build_simulation_zone_function(zone); + switch (zone.output_node->type) { + case GEO_NODE_SIMULATION_OUTPUT: { + this->build_simulation_zone_function(zone); + break; + } + case GEO_NODE_REPEAT_OUTPUT: { + this->build_repeat_zone_function(zone); + break; + } + default: { + BLI_assert_unreachable(); + break; + } + } } } @@ -1605,10 +1913,23 @@ struct GeometryNodesLazyFunctionGraphBuilder { Map lf_attribute_set_by_field_source_index; Map lf_attribute_set_by_caller_propagation_index; this->build_attribute_set_inputs_for_zone(graph_params, - zone_info, lf_attribute_set_by_field_source_index, - lf_attribute_set_by_caller_propagation_index, - lf_zone_inputs); + lf_attribute_set_by_caller_propagation_index); + for (const auto item : lf_attribute_set_by_field_source_index.items()) { + lf::OutputSocket &lf_attribute_set_socket = *item.value; + if (lf_attribute_set_socket.node().is_dummy()) { + const int zone_input_index = lf_zone_inputs.append_and_get_index(&lf_attribute_set_socket); + zone_info.attribute_set_input_by_field_source_index.add_new(item.key, zone_input_index); + } + } + for (const auto item : lf_attribute_set_by_caller_propagation_index.items()) { + lf::OutputSocket &lf_attribute_set_socket = *item.value; + if (lf_attribute_set_socket.node().is_dummy()) { + const int zone_input_index = lf_zone_inputs.append_and_get_index(&lf_attribute_set_socket); + zone_info.attribute_set_input_by_caller_propagation_index.add_new(item.key, + zone_input_index); + } + } this->link_attribute_set_inputs(lf_graph, graph_params, lf_attribute_set_by_field_source_index, @@ -1643,6 +1964,135 @@ struct GeometryNodesLazyFunctionGraphBuilder { // std::cout << "\n\n" << lf_graph.to_dot() << "\n\n"; } + /** + * Builds a #LazyFunction for a repeat zone. For that it first builds a lazy-function graph + * from all the nodes in the zone, and then wraps that in another lazy-function that implements + * the repeating behavior. + */ + void build_repeat_zone_function(const bNodeTreeZone &zone) + { + ZoneBuildInfo &zone_info = zone_build_infos_[zone.index]; + lf::Graph &lf_body_graph = scope_.construct(); + + BuildGraphParams graph_params{lf_body_graph}; + + Vector lf_body_inputs; + Vector lf_body_outputs; + RepeatBodyIndices &body_indices = scope_.construct(); + + lf::DummyNode &lf_main_input_node = this->build_dummy_node_for_sockets( + "Repeat Input", {}, zone.input_node->output_sockets().drop_back(1), lf_body_graph); + for (const int i : zone.input_node->output_sockets().drop_back(1).index_range()) { + const bNodeSocket &bsocket = zone.input_node->output_socket(i); + lf::OutputSocket &lf_socket = lf_main_input_node.output(i); + graph_params.lf_output_by_bsocket.add_new(&bsocket, &lf_socket); + } + lf_body_inputs.extend(lf_main_input_node.outputs()); + body_indices.main_inputs = lf_body_inputs.index_range(); + + lf::DummyNode &lf_main_output_node = this->build_dummy_node_for_sockets( + "Repeat Output", zone.output_node->input_sockets().drop_back(1), {}, lf_body_graph); + lf_body_outputs.extend(lf_main_output_node.inputs()); + body_indices.main_outputs = lf_body_outputs.index_range(); + + lf::Node &lf_main_input_usage_node = this->build_dummy_node_for_socket_usages( + "Input Usages", zone.input_node->output_sockets().drop_back(1), {}, lf_body_graph); + lf_body_outputs.extend(lf_main_input_usage_node.inputs()); + body_indices.main_input_usages = lf_body_outputs.index_range().take_back( + lf_main_input_usage_node.inputs().size()); + + lf::Node &lf_main_output_usage_node = this->build_dummy_node_for_socket_usages( + "Output Usages", {}, zone.output_node->input_sockets().drop_back(1), lf_body_graph); + lf_body_inputs.extend(lf_main_output_usage_node.outputs()); + body_indices.main_output_usages = lf_body_inputs.index_range().take_back( + lf_main_output_usage_node.outputs().size()); + + for (const int i : zone.output_node->input_sockets().drop_back(1).index_range()) { + const bNodeSocket &bsocket = zone.output_node->input_socket(i); + lf::InputSocket &lf_socket = lf_main_output_node.input(i); + lf::OutputSocket &lf_usage = lf_main_output_usage_node.output(i); + graph_params.lf_inputs_by_bsocket.add(&bsocket, &lf_socket); + graph_params.usage_by_bsocket.add(&bsocket, &lf_usage); + } + + lf::Node &lf_border_link_input_node = this->build_zone_border_links_input_node(zone, + lf_body_graph); + lf_body_inputs.extend(lf_border_link_input_node.outputs()); + body_indices.border_link_inputs = lf_body_inputs.index_range().take_back( + lf_border_link_input_node.outputs().size()); + + lf::Node &lf_border_link_usage_node = this->build_border_link_input_usage_node(zone, + lf_body_graph); + lf_body_outputs.extend(lf_border_link_usage_node.inputs()); + body_indices.border_link_usages = lf_body_outputs.index_range().take_back( + lf_border_link_usage_node.inputs().size()); + + this->insert_nodes_and_zones(zone.child_nodes, zone.child_zones, graph_params); + + this->build_output_socket_usages(*zone.input_node, graph_params); + for (const int i : zone.input_node->output_sockets().drop_back(1).index_range()) { + const bNodeSocket &bsocket = zone.input_node->output_socket(i); + lf::OutputSocket *lf_usage = graph_params.usage_by_bsocket.lookup_default(&bsocket, nullptr); + lf::InputSocket &lf_usage_output = lf_main_input_usage_node.input(i); + if (lf_usage) { + lf_body_graph.add_link(*lf_usage, lf_usage_output); + } + else { + static const bool static_false = false; + lf_usage_output.set_default_value(&static_false); + } + } + + for (const auto item : graph_params.lf_output_by_bsocket.items()) { + this->insert_links_from_socket(*item.key, *item.value, graph_params); + } + + this->link_border_link_inputs_and_usages( + zone, lf_border_link_input_node, lf_border_link_usage_node, graph_params); + + this->add_default_inputs(graph_params); + + Map lf_attribute_set_by_field_source_index; + Map lf_attribute_set_by_caller_propagation_index; + + this->build_attribute_set_inputs_for_zone(graph_params, + lf_attribute_set_by_field_source_index, + lf_attribute_set_by_caller_propagation_index); + for (const auto item : lf_attribute_set_by_field_source_index.items()) { + lf::OutputSocket &lf_attribute_set_socket = *item.value; + if (lf_attribute_set_socket.node().is_dummy()) { + const int body_input_index = lf_body_inputs.append_and_get_index(&lf_attribute_set_socket); + body_indices.attribute_set_input_by_field_source_index.add_new(item.key, body_input_index); + } + } + for (const auto item : lf_attribute_set_by_caller_propagation_index.items()) { + lf::OutputSocket &lf_attribute_set_socket = *item.value; + if (lf_attribute_set_socket.node().is_dummy()) { + const int body_input_index = lf_body_inputs.append_and_get_index(&lf_attribute_set_socket); + body_indices.attribute_set_input_by_caller_propagation_index.add_new(item.key, + body_input_index); + } + } + this->link_attribute_set_inputs(lf_body_graph, + graph_params, + lf_attribute_set_by_field_source_index, + lf_attribute_set_by_caller_propagation_index); + this->fix_link_cycles(lf_body_graph, graph_params.socket_usage_inputs); + + lf_body_graph.update_node_indices(); + + auto &logger = scope_.construct(*lf_graph_info_); + auto &side_effect_provider = scope_.construct(); + LazyFunction &body_graph_fn = scope_.construct( + lf_body_graph, lf_body_inputs, lf_body_outputs, &logger, &side_effect_provider); + + // std::cout << "\n\n" << lf_body_graph.to_dot() << "\n\n"; + + auto &fn = scope_.construct( + zone, zone_info, body_graph_fn, body_indices); + zone_info.lazy_function = &fn; + } + lf::DummyNode &build_zone_border_links_input_node(const bNodeTreeZone &zone, lf::Graph &lf_graph) { auto &debug_info = scope_.construct(); @@ -1669,10 +2119,8 @@ struct GeometryNodesLazyFunctionGraphBuilder { void build_attribute_set_inputs_for_zone( BuildGraphParams &graph_params, - ZoneBuildInfo &zone_info, Map &lf_attribute_set_by_field_source_index, - Map &lf_attribute_set_by_caller_propagation_index, - Vector &lf_zone_inputs) + Map &lf_attribute_set_by_caller_propagation_index) { const Vector all_required_field_sources = this->find_all_required_field_source_indices( graph_params.lf_attribute_set_input_by_output_geometry_bsocket, @@ -1730,10 +2178,6 @@ struct GeometryNodesLazyFunctionGraphBuilder { const int attribute_set_index = item.value; lf::OutputSocket &lf_attribute_set_socket = node.output(attribute_set_index); lf_attribute_set_by_field_source_index.add(field_source_index, &lf_attribute_set_socket); - - const int zone_input_index = lf_zone_inputs.append_and_get_index(&lf_attribute_set_socket); - zone_info.attribute_set_input_by_field_source_index.add(field_source_index, - zone_input_index); } for (const int i : all_required_caller_propagation_indices.index_range()) { const int caller_propagation_index = all_required_caller_propagation_indices[i]; @@ -1741,9 +2185,6 @@ struct GeometryNodesLazyFunctionGraphBuilder { input_by_field_source_index.size() + i); lf_attribute_set_by_caller_propagation_index.add_new(caller_propagation_index, &lf_attribute_set_socket); - const int zone_input_index = lf_zone_inputs.append_and_get_index(&lf_attribute_set_socket); - zone_info.attribute_set_input_by_caller_propagation_index.add(caller_propagation_index, - zone_input_index); } } } @@ -2151,6 +2592,7 @@ struct GeometryNodesLazyFunctionGraphBuilder { const int caller_propagation_index = item.key; const int child_zone_input_index = item.value; lf::InputSocket &lf_attribute_set_input = child_zone_node.input(child_zone_input_index); + BLI_assert(lf_attribute_set_input.type().is()); graph_params.lf_attribute_set_input_by_caller_propagation_index.add(caller_propagation_index, &lf_attribute_set_input); } @@ -2818,11 +3260,11 @@ struct GeometryNodesLazyFunctionGraphBuilder { Vector output_types; for (const bNodeSocket *bsocket : input_bsockets) { input_types.append(bsocket->typeinfo->geometry_nodes_cpp_type); - debug_info.input_names.append(bsocket->identifier); + debug_info.input_names.append(bsocket->name); } for (const bNodeSocket *bsocket : output_bsockets) { output_types.append(bsocket->typeinfo->geometry_nodes_cpp_type); - debug_info.output_names.append(bsocket->identifier); + debug_info.output_names.append(bsocket->name); } lf::DummyNode &node = lf_graph.add_dummy(input_types, output_types, &debug_info); return node; @@ -2840,10 +3282,10 @@ struct GeometryNodesLazyFunctionGraphBuilder { Vector input_types(input_bsockets.size(), &bool_cpp_type); Vector output_types(output_bsockets.size(), &bool_cpp_type); for (const bNodeSocket *bsocket : input_bsockets) { - debug_info.input_names.append(bsocket->identifier); + debug_info.input_names.append(bsocket->name); } for (const bNodeSocket *bsocket : output_bsockets) { - debug_info.output_names.append(bsocket->identifier); + debug_info.output_names.append(bsocket->name); } lf::DummyNode &node = lf_graph.add_dummy(input_types, output_types, &debug_info); return node; @@ -3349,6 +3791,12 @@ const GeometryNodesLazyFunctionGraphInfo *ensure_geometry_nodes_lazy_function_gr if (tree_zones == nullptr) { return nullptr; } + for (const std::unique_ptr &zone : tree_zones->zones) { + if (zone->input_node == nullptr || zone->output_node == nullptr) { + /* Simulations and repeats need input and output nodes. */ + return nullptr; + } + } if (const ID *id_orig = DEG_get_original_id(const_cast(&btree.id))) { if (id_orig->tag & LIB_TAG_MISSING) { return nullptr; diff --git a/source/blender/nodes/intern/geometry_nodes_log.cc b/source/blender/nodes/intern/geometry_nodes_log.cc index 1302ec52f3d..c405029eab8 100644 --- a/source/blender/nodes/intern/geometry_nodes_log.cc +++ b/source/blender/nodes/intern/geometry_nodes_log.cc @@ -527,7 +527,18 @@ static void find_tree_zone_hash_recursive( ComputeContextBuilder &compute_context_builder, Map &r_hash_by_zone) { - compute_context_builder.push(*zone.output_node); + switch (zone.output_node->type) { + case GEO_NODE_SIMULATION_OUTPUT: { + compute_context_builder.push(*zone.output_node); + break; + } + case GEO_NODE_REPEAT_OUTPUT: { + /* Only show data from the first iteration for now. */ + const int iteration = 0; + compute_context_builder.push(*zone.output_node, iteration); + break; + } + } r_hash_by_zone.add_new(&zone, compute_context_builder.hash()); for (const bNodeTreeZone *child_zone : zone.child_zones) { find_tree_zone_hash_recursive(*child_zone, compute_context_builder, r_hash_by_zone); @@ -560,7 +571,19 @@ Map GeoModifierLog:: const Vector zone_stack = tree_zones->get_zone_stack_for_node( group_node->identifier); for (const bNodeTreeZone *zone : zone_stack) { - compute_context_builder.push(*zone->output_node); + switch (zone->output_node->type) { + case GEO_NODE_SIMULATION_OUTPUT: { + compute_context_builder.push(*zone->output_node); + break; + } + case GEO_NODE_REPEAT_OUTPUT: { + /* Only show data from the first iteration for now. */ + const int repeat_iteration = 0; + compute_context_builder.push(*zone->output_node, + repeat_iteration); + break; + } + } } compute_context_builder.push(*group_node); } @@ -638,6 +661,12 @@ const ViewerNodeLog *GeoModifierLog::find_viewer_node_log_for_path(const ViewerP typed_elem.sim_output_node_id); break; } + case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: { + const auto &typed_elem = *reinterpret_cast(elem); + compute_context_builder.push( + typed_elem.repeat_output_node_id, typed_elem.iteration); + break; + } default: { BLI_assert_unreachable(); break; diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 39e79cca818..94cc79b82d8 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -708,6 +708,7 @@ set(geo_node_tests curves/interpolate_curves geometry instance + repeat_zone mesh_primitives mesh mesh/extrude