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