diff --git a/release/datafiles/userdef/userdef_default_theme.c b/release/datafiles/userdef/userdef_default_theme.c index 0edc3fcf40c..21a195931bc 100644 --- a/release/datafiles/userdef/userdef_default_theme.c +++ b/release/datafiles/userdef/userdef_default_theme.c @@ -880,6 +880,7 @@ const bTheme U_theme_default = { .nodeclass_attribute = RGBA(0x001566ff), .node_zone_simulation = RGBA(0x66416233), .node_zone_repeat = RGBA(0x76512f33), + .node_zone_foreach = RGBA(0x2f517633), .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 2657bec9b27..28de0f3645f 100644 --- a/scripts/startup/bl_operators/geometry_nodes.py +++ b/scripts/startup/bl_operators/geometry_nodes.py @@ -458,6 +458,63 @@ class RepeatZoneItemMoveOperator(RepeatZoneOperator, ZoneMoveItemOperator, Opera bl_options = {'REGISTER', 'UNDO'} +class ForEachZoneOperator(ZoneOperator): + input_node_type = 'GeometryNodeForEachInput' + output_node_type = 'GeometryNodeForEachOutput' + + +class ForEachInputOperator(ForEachZoneOperator): + items_name = "input_items" + active_index_name = "input_active_index" + + +class ForEachInputItemAddOperator(ForEachInputOperator, ZoneItemAddOperator, Operator): + """Add an item to the for-each input node""" + bl_idname = "node.foreach_zone_input_item_add" + bl_label = "Add For-Each Input" + bl_options = {'REGISTER', 'UNDO'} + + default_socket_type = 'FLOAT' + +class ForEachInputItemRemoveOperator(ForEachInputOperator, ZoneItemRemoveOperator, Operator): + """Remove an item from the for-each input node""" + bl_idname = "node.foreach_zone_input_item_remove" + bl_label = "Remove For-Each Input" + bl_options = {'REGISTER', 'UNDO'} + + +class ForEachInputItemMoveOperator(ForEachInputOperator, ZoneMoveItemOperator, Operator): + """Move an item up or down in the list""" + bl_idname = "node.foreach_zone_input_item_move" + bl_label = "Move For-Each Input" + bl_options = {'REGISTER', 'UNDO'} + + +class ForEachOutputOperator(ForEachZoneOperator): + items_name = "output_items" + active_index_name = "output_active_index" + + +class ForEachOutputItemAddOperator(ForEachOutputOperator, ZoneItemAddOperator, Operator): + """Add an item to the for-each output node""" + bl_idname = "node.foreach_zone_output_item_add" + bl_label = "Add For-Each Output" + bl_options = {'REGISTER', 'UNDO'} + +class ForEachOutputItemRemoveOperator(ForEachOutputOperator, ZoneItemRemoveOperator, Operator): + """Remove an item from the for-each output node""" + bl_idname = "node.foreach_zone_output_item_remove" + bl_label = "Remove For-Each Output" + bl_options = {'REGISTER', 'UNDO'} + + +class ForEachOutputItemMoveOperator(ForEachOutputOperator, ZoneMoveItemOperator, Operator): + """Move an item up or down in the list""" + bl_idname = "node.foreach_zone_output_item_move" + bl_label = "Move For-Each Output" + bl_options = {'REGISTER', 'UNDO'} + + classes = ( NewGeometryNodesModifier, NewGeometryNodeTreeAssign, @@ -469,4 +526,10 @@ classes = ( RepeatZoneItemAddOperator, RepeatZoneItemRemoveOperator, RepeatZoneItemMoveOperator, + ForEachInputItemAddOperator, + ForEachInputItemRemoveOperator, + ForEachInputItemMoveOperator, + ForEachOutputItemAddOperator, + ForEachOutputItemRemoveOperator, + ForEachOutputItemMoveOperator, ) diff --git a/scripts/startup/bl_operators/node.py b/scripts/startup/bl_operators/node.py index 542744f47bb..14f88cda77d 100644 --- a/scripts/startup/bl_operators/node.py +++ b/scripts/startup/bl_operators/node.py @@ -182,9 +182,12 @@ class NodeAddZoneOperator(NodeAddOperator): # Connect geometry sockets by default. # Get the sockets by their types, because the name is not guaranteed due to i18n. - from_socket = next(s for s in input_node.outputs if s.type == 'GEOMETRY') - to_socket = next(s for s in output_node.inputs if s.type == 'GEOMETRY') - tree.links.new(to_socket, from_socket) + try: + from_socket = next(s for s in input_node.outputs if s.type == 'GEOMETRY') + to_socket = next(s for s in output_node.inputs if s.type == 'GEOMETRY') + tree.links.new(to_socket, from_socket) + except: + pass return {'FINISHED'} @@ -209,6 +212,15 @@ class NODE_OT_add_repeat_zone(NodeAddZoneOperator, Operator): output_node_type = "GeometryNodeRepeatOutput" +class NODE_OT_add_foreach_zone(NodeAddZoneOperator, Operator): + """Add a for-each zone that allows executing nodes in parallel a dynamic number of times""" + bl_idname = "node.add_foreach_zone" + bl_label = "Add For-Each Zone" + bl_options = {'REGISTER', 'UNDO'} + + input_node_type = "GeometryNodeForEachInput" + output_node_type = "GeometryNodeForEachOutput" + class NODE_OT_collapse_hide_unused_toggle(Operator): """Toggle collapsed nodes and hide unused sockets""" bl_idname = "node.collapse_hide_unused_toggle" @@ -389,6 +401,7 @@ classes = ( NODE_OT_add_node, NODE_OT_add_simulation_zone, NODE_OT_add_repeat_zone, + NODE_OT_add_foreach_zone, NODE_OT_collapse_hide_unused_toggle, NODE_OT_interface_item_new, NODE_OT_interface_item_duplicate, diff --git a/scripts/startup/bl_ui/node_add_menu.py b/scripts/startup/bl_ui/node_add_menu.py index ae23abd92fb..9dd28073b8b 100644 --- a/scripts/startup/bl_ui/node_add_menu.py +++ b/scripts/startup/bl_ui/node_add_menu.py @@ -74,6 +74,11 @@ def add_repeat_zone(layout, label): props.use_transform = True return props +def add_foreach_zone(layout, label): + props = layout.operator("node.add_foreach_zone", text=label, text_ctxt=i18n_contexts.default) + props.use_transform = True + return props + class NODE_MT_category_layout(Menu): bl_idname = "NODE_MT_category_layout" diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index aacd43d5875..97c2d5798ec 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -542,6 +542,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_foreach_zone(layout, label="For-Each Zone") 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 5779e28d7fa..39bbc61a3e4 100644 --- a/scripts/startup/bl_ui/space_node.py +++ b/scripts/startup/bl_ui/space_node.py @@ -1143,6 +1143,114 @@ class NODE_PT_repeat_zone_items(Panel): layout.prop(output_node, "inspection_index") +class NODE_UL_foreach_zone_input_items(bpy.types.UIList): + def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index): + draw_socket_item_in_list(self, layout, item, icon) + + +class NODE_UL_foreach_zone_output_items(bpy.types.UIList): + def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index): + draw_socket_item_in_list(self, layout, item, icon) + + +class NODE_PT_foreach_zone_items(Panel): + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_category = "Node" + bl_label = "For-Each" + + input_node_type = 'GeometryNodeForEachInput' + output_node_type = 'GeometryNodeForEachOutput' + + @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) + self.draw_input_items(layout, output_node) + self.draw_output_items(layout, output_node) + + def draw_input_items(self, layout, output_node): + split = layout.row() + split.template_list( + "NODE_UL_foreach_zone_input_items", + "", + output_node, + "input_items", + output_node, + "input_active_index") + + ops_col = split.column() + + add_remove_col = ops_col.column(align=True) + add_remove_col.operator("node.foreach_zone_input_item_add", icon='ADD', text="") + add_remove_col.operator("node.foreach_zone_input_item_remove", icon='REMOVE', text="") + + ops_col.separator() + + up_down_col = ops_col.column(align=True) + props = up_down_col.operator("node.foreach_zone_input_item_move", icon='TRIA_UP', text="") + props.direction = 'UP' + props = up_down_col.operator("node.foreach_zone_input_item_move", icon='TRIA_DOWN', text="") + props.direction = 'DOWN' + + active_item = output_node.input_active_item + if active_item is not None: + layout.use_property_split = True + layout.use_property_decorate = False + layout.prop(active_item, "socket_type") + + def draw_output_items(self, layout, output_node): + split = layout.row() + split.template_list( + "NODE_UL_foreach_zone_output_items", + "", + output_node, + "output_items", + output_node, + "output_active_index") + + ops_col = split.column() + + add_remove_col = ops_col.column(align=True) + add_remove_col.operator("node.foreach_zone_output_item_add", icon='ADD', text="") + add_remove_col.operator("node.foreach_zone_output_item_remove", icon='REMOVE', text="") + + ops_col.separator() + + up_down_col = ops_col.column(align=True) + props = up_down_col.operator("node.foreach_zone_output_item_move", icon='TRIA_UP', text="") + props.direction = 'UP' + props = up_down_col.operator("node.foreach_zone_output_item_move", icon='TRIA_DOWN', text="") + props.direction = 'DOWN' + + active_item = output_node.output_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' @@ -1212,6 +1320,9 @@ classes = ( NODE_PT_simulation_zone_items, NODE_UL_repeat_zone_items, NODE_PT_repeat_zone_items, + NODE_UL_foreach_zone_input_items, + NODE_UL_foreach_zone_output_items, + NODE_PT_foreach_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 f027e22e053..dc2a1cc52ae 100644 --- a/scripts/startup/bl_ui/space_spreadsheet.py +++ b/scripts/startup/bl_ui/space_spreadsheet.py @@ -93,6 +93,8 @@ class SPREADSHEET_HT_header(bpy.types.Header): layout.label(text="Simulation Zone") elif ctx.type == 'REPEAT_ZONE': layout.label(text="Repeat Zone") + elif ctx.type == 'FOREACH_ZONE': + layout.label(text='For-Each 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 f8b0260e6fc..c0a5332c524 100644 --- a/source/blender/blenkernel/BKE_compute_contexts.hh +++ b/source/blender/blenkernel/BKE_compute_contexts.hh @@ -108,4 +108,29 @@ class RepeatZoneComputeContext : public ComputeContext { void print_current_in_line(std::ostream &stream) const override; }; +class ForEachZoneComputeContext : public ComputeContext { + private: + static constexpr const char *s_static_type = "FOREACH_ZONE"; + + int32_t output_node_id_; + int index_; + + public: + ForEachZoneComputeContext(const ComputeContext *parent, int32_t output_node_id, int index); + ForEachZoneComputeContext(const ComputeContext *parent, const bNode &node, int index); + + int32_t output_node_id() const + { + return output_node_id_; + } + + int index() const + { + return index_; + } + + 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 ee79fd573af..eccba4c93a7 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1309,6 +1309,8 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i #define GEO_NODE_TOOL_SET_FACE_SET 2113 #define GEO_NODE_POINTS_TO_CURVES 2114 #define GEO_NODE_INPUT_EDGE_SMOOTH 2115 +#define GEO_NODE_FOR_EACH_INPUT 2116 +#define GEO_NODE_FOR_EACH_OUTPUT 2117 /** \} */ diff --git a/source/blender/blenkernel/BKE_viewer_path.h b/source/blender/blenkernel/BKE_viewer_path.h index aa78593c81e..5be366a75a5 100644 --- a/source/blender/blenkernel/BKE_viewer_path.h +++ b/source/blender/blenkernel/BKE_viewer_path.h @@ -37,6 +37,7 @@ extern "C" { enum ViewerPathEqualFlag { VIEWER_PATH_EQUAL_FLAG_IGNORE_REPEAT_ITERATION = (1 << 0), + VIEWER_PATH_EQUAL_FLAG_IGNORE_FOREACH_INDEX = (1 << 1), }; void BKE_viewer_path_init(ViewerPath *viewer_path); @@ -57,6 +58,7 @@ 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); +ForEachZoneViewerPathElem *BKE_viewer_path_elem_new_foreach_zone(void); ViewerPathElem *BKE_viewer_path_elem_copy(const ViewerPathElem *src); bool BKE_viewer_path_elem_equal(const ViewerPathElem *a, const ViewerPathElem *b, diff --git a/source/blender/blenkernel/intern/compute_contexts.cc b/source/blender/blenkernel/intern/compute_contexts.cc index ac47233b535..9796df74e38 100644 --- a/source/blender/blenkernel/intern/compute_contexts.cc +++ b/source/blender/blenkernel/intern/compute_contexts.cc @@ -119,4 +119,33 @@ void RepeatZoneComputeContext::print_current_in_line(std::ostream &stream) const stream << "Repeat Zone ID: " << output_node_id_; } +ForEachZoneComputeContext::ForEachZoneComputeContext(const ComputeContext *parent, + const int32_t output_node_id, + const int index) + : ComputeContext(s_static_type, parent), output_node_id_(output_node_id), index_(index) +{ + /* 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), &index_, sizeof(int)); + hash_.mix_in(buffer, buffer_size); +} + +ForEachZoneComputeContext::ForEachZoneComputeContext(const ComputeContext *parent, + const bNode &node, + const int index) + : ForEachZoneComputeContext(parent, node.identifier, index) +{ +} + +void ForEachZoneComputeContext::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 18d3bbfbe75..9312e53180a 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -788,6 +788,10 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) if (node->type == GEO_NODE_REPEAT_OUTPUT) { blender::nodes::RepeatItemsAccessor::blend_write(writer, *node); } + if (node->type == GEO_NODE_FOR_EACH_OUTPUT) { + blender::nodes::ForEachInputItemsAccessor::blend_write(writer, *node); + blender::nodes::ForEachOutputItemsAccessor::blend_write(writer, *node); + } } LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { @@ -985,6 +989,11 @@ void ntreeBlendReadData(BlendDataReader *reader, ID *owner_id, bNodeTree *ntree) blender::nodes::RepeatItemsAccessor::blend_read_data(reader, *node); break; } + case GEO_NODE_FOR_EACH_OUTPUT: { + blender::nodes::ForEachInputItemsAccessor::blend_read_data(reader, *node); + blender::nodes::ForEachOutputItemsAccessor::blend_read_data(reader, *node); + break; + } default: break; diff --git a/source/blender/blenkernel/intern/node_tree_update.cc b/source/blender/blenkernel/intern/node_tree_update.cc index 0af746ce420..a7bfcf2b36e 100644 --- a/source/blender/blenkernel/intern/node_tree_update.cc +++ b/source/blender/blenkernel/intern/node_tree_update.cc @@ -984,15 +984,29 @@ class NodeTreeMainUpdater { } } else { - bool all_available_inputs_computed = true; + Vector src_sockets; for (const bNodeSocket *input_socket : node.input_sockets()) { if (input_socket->is_available()) { - if (!hash_by_socket_id[input_socket->index_in_tree()].has_value()) { - sockets_to_check.push(input_socket); - all_available_inputs_computed = false; + src_sockets.append(input_socket); + } + } + if (all_zone_output_node_types().contains(node.type)) { + const bNodeZoneType &zone_type = *zone_type_by_node_type(node.type); + if (const bNode *zone_input_node = zone_type.get_corresponding_input(tree, node)) { + for (const bNodeSocket *input_socket : zone_input_node->input_sockets()) { + if (input_socket->is_available()) { + src_sockets.append(input_socket); + } } } } + bool all_available_inputs_computed = true; + for (const bNodeSocket *socket : src_sockets) { + if (!hash_by_socket_id[socket->index_in_tree()].has_value()) { + sockets_to_check.push(socket); + all_available_inputs_computed = false; + } + } if (!all_available_inputs_computed) { continue; } @@ -1015,10 +1029,10 @@ class NodeTreeMainUpdater { } else { socket_hash = get_socket_ptr_hash(socket); - for (const bNodeSocket *input_socket : node.input_sockets()) { - if (input_socket->is_available()) { - const uint32_t input_socket_hash = *hash_by_socket_id[input_socket->index_in_tree()]; - socket_hash = noise::hash(socket_hash, input_socket_hash); + for (const bNodeSocket *src_socket : src_sockets) { + if (src_socket->is_available()) { + const uint32_t src_socket_hash = *hash_by_socket_id[src_socket->index_in_tree()]; + socket_hash = noise::hash(socket_hash, src_socket_hash); } } @@ -1096,6 +1110,21 @@ class NodeTreeMainUpdater { } } } + if (all_zone_output_node_types().contains(node.type)) { + const bNodeZoneType &zone_type = *zone_type_by_node_type(node.type); + if (const bNode *zone_input_node = zone_type.get_corresponding_input(tree, node)) { + if (zone_input_node->runtime->changed_flag != NTREE_CHANGED_NOTHING) { + return true; + } + for (const bNodeSocket *input_socket : zone_input_node->input_sockets()) { + bool &pushed = pushed_by_socket_id[input_socket->index_in_tree()]; + if (!pushed) { + sockets_to_check.push(input_socket); + pushed = true; + } + } + } + } /* The Normal node has a special case, because the value stored in the first output socket * is used as input in the node. */ if (node.type == SH_NODE_NORMAL && socket.index() == 1) { diff --git a/source/blender/blenkernel/intern/viewer_path.cc b/source/blender/blenkernel/intern/viewer_path.cc index 9fbf8304370..84ed578ffc3 100644 --- a/source/blender/blenkernel/intern/viewer_path.cc +++ b/source/blender/blenkernel/intern/viewer_path.cc @@ -95,6 +95,11 @@ void BKE_viewer_path_blend_write(BlendWriter *writer, const ViewerPath *viewer_p BLO_write_struct(writer, RepeatZoneViewerPathElem, typed_elem); break; } + case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE: { + const auto *typed_elem = reinterpret_cast(elem); + BLO_write_struct(writer, ForEachZoneViewerPathElem, typed_elem); + break; + } } BLO_write_string(writer, elem->ui_name); } @@ -110,6 +115,7 @@ void BKE_viewer_path_blend_read_data(BlendDataReader *reader, ViewerPath *viewer 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_FOREACH_ZONE: case VIEWER_PATH_ELEM_TYPE_ID: { break; } @@ -135,7 +141,8 @@ void BKE_viewer_path_foreach_id(LibraryForeachIDData *data, ViewerPath *viewer_p 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_REPEAT_ZONE: + case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE: { break; } } @@ -155,7 +162,8 @@ void BKE_viewer_path_id_remap(ViewerPath *viewer_path, const IDRemapper *mapping 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_REPEAT_ZONE: + case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE: { break; } } @@ -190,6 +198,9 @@ ViewerPathElem *BKE_viewer_path_elem_new(const ViewerPathElemType type) case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: { return &make_elem(type)->base; } + case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE: { + return &make_elem(type)->base; + } } BLI_assert_unreachable(); return nullptr; @@ -230,6 +241,12 @@ RepeatZoneViewerPathElem *BKE_viewer_path_elem_new_repeat_zone() BKE_viewer_path_elem_new(VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE)); } +ForEachZoneViewerPathElem *BKE_viewer_path_elem_new_foreach_zone() +{ + return reinterpret_cast( + BKE_viewer_path_elem_new(VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE)); +} + ViewerPathElem *BKE_viewer_path_elem_copy(const ViewerPathElem *src) { ViewerPathElem *dst = BKE_viewer_path_elem_new(ViewerPathElemType(src->type)); @@ -276,6 +293,12 @@ ViewerPathElem *BKE_viewer_path_elem_copy(const ViewerPathElem *src) new_elem->iteration = old_elem->iteration; break; } + case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE: { + const auto *old_elem = reinterpret_cast(src); + auto *new_elem = reinterpret_cast(dst); + new_elem->foreach_output_node_id = old_elem->foreach_output_node_id; + new_elem->index = old_elem->index; + } } return dst; } @@ -320,6 +343,13 @@ bool BKE_viewer_path_elem_equal(const ViewerPathElem *a, ((flag & VIEWER_PATH_EQUAL_FLAG_IGNORE_REPEAT_ITERATION) != 0 || a_elem->iteration == b_elem->iteration); } + case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE: { + const auto *a_elem = reinterpret_cast(a); + const auto *b_elem = reinterpret_cast(b); + return a_elem->foreach_output_node_id == b_elem->foreach_output_node_id && + ((flag & VIEWER_PATH_EQUAL_FLAG_IGNORE_FOREACH_INDEX) != 0 || + a_elem->index == b_elem->index); + } } return false; } @@ -331,7 +361,8 @@ void BKE_viewer_path_elem_free(ViewerPathElem *elem) 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_REPEAT_ZONE: + case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE: { break; } case VIEWER_PATH_ELEM_TYPE_MODIFIER: { diff --git a/source/blender/blenloader/intern/versioning_userdef.cc b/source/blender/blenloader/intern/versioning_userdef.cc index 7e785184212..5471453b378 100644 --- a/source/blender/blenloader/intern/versioning_userdef.cc +++ b/source/blender/blenloader/intern/versioning_userdef.cc @@ -140,6 +140,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_foreach); } #undef FROM_DEFAULT_V4_UCHAR diff --git a/source/blender/editors/include/UI_resources.hh b/source/blender/editors/include/UI_resources.hh index 19950c546da..ba130d6d947 100644 --- a/source/blender/editors/include/UI_resources.hh +++ b/source/blender/editors/include/UI_resources.hh @@ -190,6 +190,7 @@ enum ThemeColorID { TH_NODE_ZONE_SIMULATION, TH_NODE_ZONE_REPEAT, + TH_NODE_ZONE_FOR_EACH, TH_SIMULATED_FRAMES, TH_CONSOLE_OUTPUT, diff --git a/source/blender/editors/interface/resources.cc b/source/blender/editors/interface/resources.cc index d9c4b0e25b1..2f9795cb45e 100644 --- a/source/blender/editors/interface/resources.cc +++ b/source/blender/editors/interface/resources.cc @@ -666,6 +666,9 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid) case TH_NODE_ZONE_REPEAT: cp = ts->node_zone_repeat; break; + case TH_NODE_ZONE_FOR_EACH: + cp = ts->node_zone_foreach; + break; case TH_SIMULATED_FRAMES: cp = ts->simulated_frames; break; diff --git a/source/blender/editors/util/ed_viewer_path.cc b/source/blender/editors/util/ed_viewer_path.cc index a0edcd5d984..8605937e4ce 100644 --- a/source/blender/editors/util/ed_viewer_path.cc +++ b/source/blender/editors/util/ed_viewer_path.cc @@ -44,6 +44,12 @@ static ViewerPathElem *viewer_path_elem_for_zone(const bNodeTreeZone &zone) node_elem->iteration = storage.inspection_index; return &node_elem->base; } + case GEO_NODE_FOR_EACH_OUTPUT: { + ForEachZoneViewerPathElem *node_elem = BKE_viewer_path_elem_new_foreach_zone(); + node_elem->foreach_output_node_id = zone.output_node->identifier; + node_elem->index = 0; + return &node_elem->base; + } } BLI_assert_unreachable(); return nullptr; @@ -253,7 +259,8 @@ std::optional parse_geometry_nodes_viewer( if (!ELEM(elem->type, VIEWER_PATH_ELEM_TYPE_GROUP_NODE, VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE, - VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE)) + VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE, + VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE)) { return std::nullopt; } @@ -320,6 +327,19 @@ bool exists_geometry_nodes_viewer(const ViewerPathForGeometryNodesViewer &parsed zone = next_zone; break; } + case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE: { + const auto &typed_elem = *reinterpret_cast(path_elem); + const bNodeTreeZone *next_zone = tree_zones->get_zone_by_node( + typed_elem.foreach_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); @@ -484,6 +504,12 @@ bNode *find_geometry_nodes_viewer(const ViewerPath &viewer_path, SpaceNode &snod elem.iteration); return true; } + case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE: { + const auto &elem = reinterpret_cast(elem_generic); + compute_context_builder.push(elem.foreach_output_node_id, + elem.index); + return true; + } } return false; } diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 4afcae885c0..275bc2f11a6 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1836,6 +1836,73 @@ typedef struct NodeGeometryRepeatOutput { #endif } NodeGeometryRepeatOutput; +typedef struct NodeForEachInputItem { + 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; +} NodeForEachInputItem; + +typedef struct NodeForEachOutputItem { + 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; +} NodeForEachOutputItem; + +typedef struct NodeGeometryForEachInput { + /** bNode.identifier of the corresponding output node. */ + int output_node_id; +} NodeGeometryForEachInput; + +typedef struct NodeGeometryForEachOutput { + NodeForEachInputItem *input_items; + int input_items_num; + int input_active_index; + int input_next_identifier; + char _pad1[4]; + + NodeForEachOutputItem *output_items; + int output_items_num; + int output_active_index; + int output_next_identifier; + + /** #GeometryNodeForEachMode. */ + uint8_t mode; + /** #eAttrDomain. */ + uint8_t domain; + char _pad2[2]; + +#ifdef __cplusplus + blender::Span input_items_span() const + { + return {this->input_items, this->input_items_num}; + } + blender::MutableSpan input_items_span() + { + return {this->input_items, this->input_items_num}; + } + blender::Span output_items_span() const + { + return {this->output_items, this->output_items_num}; + } + blender::MutableSpan output_items_span() + { + return {this->output_items, this->output_items_num}; + } +#endif +} NodeGeometryForEachOutput; + typedef struct NodeGeometryDistributePointsInVolume { /** #GeometryNodePointDistributeVolumeMode. */ uint8_t mode; @@ -2742,3 +2809,9 @@ typedef enum NodeCombSepColorMode { NODE_COMBSEP_COLOR_HSV = 1, NODE_COMBSEP_COLOR_HSL = 2, } NodeCombSepColorMode; + +typedef enum GeometryNodeForEachMode { + GEO_NODE_FOR_EACH_MODE_INDEX = 0, + GEO_NODE_FOR_EACH_MODE_GEOMETRY_ELEMENT = 1, + GEO_NODE_FOR_EACH_MODE_INSTANCE = 2, +} GeometryNodeForEachMode; diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 416e6168132..1c9c9e0f423 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -349,7 +349,7 @@ typedef struct ThemeSpace { unsigned char node_zone_simulation[4]; unsigned char node_zone_repeat[4]; - unsigned char _pad9[4]; + unsigned char node_zone_foreach[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 0c942962bb8..9e4111f18f6 100644 --- a/source/blender/makesdna/DNA_viewer_path_types.h +++ b/source/blender/makesdna/DNA_viewer_path_types.h @@ -16,6 +16,7 @@ typedef enum ViewerPathElemType { VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE = 3, VIEWER_PATH_ELEM_TYPE_VIEWER_NODE = 4, VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE = 5, + VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE = 6, } ViewerPathElemType; typedef struct ViewerPathElem { @@ -56,6 +57,13 @@ typedef struct RepeatZoneViewerPathElem { int iteration; } RepeatZoneViewerPathElem; +typedef struct ForEachZoneViewerPathElem { + ViewerPathElem base; + + int foreach_output_node_id; + int index; +} ForEachZoneViewerPathElem; + typedef struct ViewerNodeViewerPathElem { ViewerPathElem base; diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 37a87e6e0ca..6e48cf2504d 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -608,6 +608,8 @@ static const EnumPropertyItem node_cryptomatte_layer_name_items[] = { # include "DNA_scene_types.h" # include "WM_api.hh" +using blender::nodes::ForEachInputItemsAccessor; +using blender::nodes::ForEachOutputItemsAccessor; using blender::nodes::RepeatItemsAccessor; using blender::nodes::SimulationItemsAccessor; @@ -3267,14 +3269,20 @@ static void rna_Node_ItemArray_item_update(Main *bmain, Scene * /*scene*/, Point template static const EnumPropertyItem *rna_Node_ItemArray_socket_type_itemf(bContext * /*C*/, - PointerRNA * /*ptr*/, + PointerRNA *ptr, PropertyRNA * /*prop*/, bool *r_free) { + using ItemT = typename Accessor::ItemT; + bNodeTree &ntree = *reinterpret_cast(ptr->owner_id); + ItemT &item = *static_cast(ptr->data); + const bNode *node = blender::nodes::socket_items::find_node_by_item(ntree, item); + BLI_assert(node != nullptr); + *r_free = true; return itemf_function_check( - rna_enum_node_socket_data_type_items, [](const EnumPropertyItem *item) { - return Accessor::supports_socket_type(eNodeSocketDatatype(item->value)); + rna_enum_node_socket_data_type_items, [&](const EnumPropertyItem *item) { + return Accessor::supports_socket_type(*node, eNodeSocketDatatype(item->value)); }); } @@ -3303,7 +3311,7 @@ typename Accessor::ItemT *rna_Node_ItemArray_new_with_socket_and_name( ID *id, bNode *node, Main *bmain, ReportList *reports, int socket_type, const char *name) { using ItemT = typename Accessor::ItemT; - if (!Accessor::supports_socket_type(eNodeSocketDatatype(socket_type))) { + if (!Accessor::supports_socket_type(*node, eNodeSocketDatatype(socket_type))) { BKE_report(reports, RPT_ERROR, "Unable to create item with this socket type"); return nullptr; } @@ -3318,6 +3326,18 @@ typename Accessor::ItemT *rna_Node_ItemArray_new_with_socket_and_name( return new_item; } +static void rna_ForEachOutputNode_mode_update(Main *bmain, Scene * /*scene*/, PointerRNA *ptr) +{ + bNodeTree &ntree = *reinterpret_cast(ptr->owner_id); + bNode &node = *static_cast(ptr->data); + + blender::nodes::socket_items::remove_unsupported_socket_types(node); + blender::nodes::socket_items::remove_unsupported_socket_types(node); + + BKE_ntree_update_tag_node_property(&ntree, &node); + ED_node_tree_propagate_change(nullptr, bmain, &ntree); +} + /* ******** Node Socket Types ******** */ static PointerRNA rna_NodeOutputFile_slot_layer_get(CollectionPropertyIterator *iter) @@ -8725,22 +8745,29 @@ static void def_geo_repeat_input(StructRNA *srna) def_common_zone_input(srna); } +static void def_geo_foreach_input(StructRNA *srna) +{ + RNA_def_struct_sdna_from(srna, "NodeGeometryForEachInput", "storage"); + + def_common_zone_input(srna); +} + static void rna_def_node_item_array_socket_item_common(StructRNA *srna, const char *accessor) { static blender::LinearAllocator<> allocator; PropertyRNA *prop; - char name_set_func[64]; + char name_set_func[128]; SNPRINTF(name_set_func, "rna_Node_ItemArray_item_name_set<%s>", accessor); - char item_update_func[64]; + char item_update_func[128]; SNPRINTF(item_update_func, "rna_Node_ItemArray_item_update<%s>", accessor); const char *item_update_func_ptr = allocator.copy_string(item_update_func).c_str(); - char socket_type_itemf[64]; + char socket_type_itemf[128]; SNPRINTF(socket_type_itemf, "rna_Node_ItemArray_socket_type_itemf<%s>", accessor); - char color_get_func[64]; + char color_get_func[128]; SNPRINTF(color_get_func, "rna_Node_ItemArray_item_color_get<%s>", accessor); prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); @@ -8775,11 +8802,11 @@ static void rna_def_node_item_array_common_functions(StructRNA *srna, PropertyRNA *parm; FunctionRNA *func; - char remove_call[64]; + char remove_call[128]; SNPRINTF(remove_call, "rna_Node_ItemArray_remove<%s>", accessor_name); - char clear_call[64]; + char clear_call[128]; SNPRINTF(clear_call, "rna_Node_ItemArray_clear<%s>", accessor_name); - char move_call[64]; + char move_call[128]; SNPRINTF(move_call, "rna_Node_ItemArray_move<%s>", accessor_name); func = RNA_def_function(srna, "remove", allocator.copy_string(remove_call).c_str()); @@ -8956,6 +8983,127 @@ static void def_geo_repeat_output(StructRNA *srna) RNA_def_property_update(prop, NC_NODE, "rna_Node_update"); } +static void rna_def_foreach_input_item(BlenderRNA *brna) +{ + StructRNA *srna = RNA_def_struct(brna, "ForEachInputItem", nullptr); + RNA_def_struct_ui_text(srna, "For-Each Input Item", ""); + RNA_def_struct_sdna(srna, "NodeForEachInputItem"); + + rna_def_node_item_array_socket_item_common(srna, "ForEachInputItemsAccessor"); +} + +static void rna_def_foreach_output_item(BlenderRNA *brna) +{ + StructRNA *srna = RNA_def_struct(brna, "ForEachOutputItem", nullptr); + RNA_def_struct_ui_text(srna, "For-Each Output Item", ""); + RNA_def_struct_sdna(srna, "NodeForEachOutputItem"); + + rna_def_node_item_array_socket_item_common(srna, "ForEachOutputItemsAccessor"); +} + +static void rna_def_geo_foreach_input_items(BlenderRNA *brna) +{ + StructRNA *srna = RNA_def_struct(brna, "NodeGeometryForEachInputItems", nullptr); + RNA_def_struct_sdna(srna, "bNode"); + RNA_def_struct_ui_text(srna, "Input Items", "Collection of for-each input items"); + + rna_def_node_item_array_new_with_socket_and_name( + srna, "ForEachInputItem", "ForEachInputItemsAccessor"); + rna_def_node_item_array_common_functions(srna, "ForEachInputItem", "ForEachInputItemsAccessor"); +} + +static void rna_def_geo_foreach_output_items(BlenderRNA *brna) +{ + StructRNA *srna = RNA_def_struct(brna, "NodeGeometryForEachOutputItems", nullptr); + RNA_def_struct_sdna(srna, "bNode"); + RNA_def_struct_ui_text(srna, "Output Items", "Collection of for-each output items"); + + rna_def_node_item_array_new_with_socket_and_name( + srna, "ForEachOutputItem", "ForEachOutputItemsAccessor"); + rna_def_node_item_array_common_functions( + srna, "ForEachOutputItem", "ForEachOutputItemsAccessor"); +} + +static void def_geo_foreach_output(StructRNA *srna) +{ + PropertyRNA *prop; + + static const EnumPropertyItem mode_items[] = { + {GEO_NODE_FOR_EACH_MODE_INDEX, "INDEX", 0, "Index", "Do something for each index"}, + {GEO_NODE_FOR_EACH_MODE_GEOMETRY_ELEMENT, + "GEOMETRY_ELEMENT", + 0, + "Geometry Element", + "Do something for each element in a geometry on a specific domain"}, + {GEO_NODE_FOR_EACH_MODE_INSTANCE, + "INSTANCE", + 0, + "Instance", + "Do something for each unique top level instance"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + RNA_def_struct_sdna_from(srna, "NodeGeometryForEachOutput", "storage"); + + prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, mode_items); + RNA_def_property_ui_text(prop, "Mode", ""); + RNA_def_property_update(prop, NC_NODE, "rna_ForEachOutputNode_mode_update"); + + prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items); + RNA_def_property_ui_text(prop, "Domain", ""); + RNA_def_property_update(prop, NC_NODE, "rna_Node_update"); + + prop = RNA_def_property(srna, "input_items", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, nullptr, "input_items", "input_items_num"); + RNA_def_property_struct_type(prop, "ForEachInputItem"); + RNA_def_property_ui_text(prop, "Input Items", ""); + RNA_def_property_srna(prop, "NodeGeometryForEachInputItems"); + + prop = RNA_def_property(srna, "input_active_index", PROP_INT, PROP_UNSIGNED); + RNA_def_property_int_sdna(prop, nullptr, "input_active_index"); + RNA_def_property_ui_text(prop, "Active Input Item Index", "Index of the active item"); + RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, NC_NODE, nullptr); + + prop = RNA_def_property(srna, "input_active_item", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "ForEachInputItem"); + RNA_def_property_pointer_funcs(prop, + "rna_Node_ItemArray_active_get", + "rna_Node_ItemArray_active_set", + nullptr, + nullptr); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_NO_DEG_UPDATE); + RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item"); + RNA_def_property_update(prop, NC_NODE, nullptr); + + prop = RNA_def_property(srna, "output_items", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, nullptr, "output_items", "output_items_num"); + RNA_def_property_struct_type(prop, "ForEachOutputItem"); + RNA_def_property_ui_text(prop, "OutputItems", ""); + RNA_def_property_srna(prop, "NodeGeometryForEachOutputItems"); + + prop = RNA_def_property(srna, "output_active_index", PROP_INT, PROP_UNSIGNED); + RNA_def_property_int_sdna(prop, nullptr, "output_active_index"); + RNA_def_property_ui_text(prop, "Active Output Item Index", "Index of the active item"); + RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, NC_NODE, nullptr); + + prop = RNA_def_property(srna, "output_active_item", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "ForEachOutputItem"); + RNA_def_property_pointer_funcs(prop, + "rna_Node_ItemArray_active_get", + "rna_Node_ItemArray_active_set", + nullptr, + nullptr); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_NO_DEG_UPDATE); + 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; @@ -10278,6 +10426,8 @@ void RNA_def_nodetree(BlenderRNA *brna) rna_def_simulation_state_item(brna); rna_def_repeat_item(brna); + rna_def_foreach_input_item(brna); + rna_def_foreach_output_item(brna); # define DefNode(Category, ID, DefFunc, EnumName, StructName, UIName, UIDesc) \ { \ @@ -10330,6 +10480,8 @@ void RNA_def_nodetree(BlenderRNA *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_geo_foreach_input_items(brna); + rna_def_geo_foreach_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 cb22f9bcbfc..efe8a738d8e 100644 --- a/source/blender/makesrna/intern/rna_space.cc +++ b/source/blender/makesrna/intern/rna_space.cc @@ -3348,6 +3348,8 @@ static StructRNA *rna_viewer_path_elem_refine(PointerRNA *ptr) return &RNA_ViewerNodeViewerPathElem; case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: return &RNA_RepeatZoneViewerPathElem; + case VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE: + return &RNA_ForEachZoneViewerPathElem; } BLI_assert_unreachable(); return nullptr; @@ -8090,6 +8092,7 @@ static const EnumPropertyItem viewer_path_elem_type_items[] = { {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", ""}, + {VIEWER_PATH_ELEM_TYPE_FOREACH_ZONE, "FOREACH_ZONE", ICON_NONE, "For-Each Zone", ""}, {0, nullptr, 0, nullptr, nullptr}, }; @@ -8168,6 +8171,17 @@ static void rna_def_repeat_zone_viewer_path_elem(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Repeat Output Node ID", ""); } +static void rna_def_foreach_zone_viewer_path_elem(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "ForEachZoneViewerPathElem", "ViewerPathElem"); + + prop = RNA_def_property(srna, "foreach_output_node_id", PROP_INT, PROP_NONE); + RNA_def_property_ui_text(prop, "For-Each Output Node ID", ""); +} + static void rna_def_viewer_node_viewer_path_elem(BlenderRNA *brna) { StructRNA *srna; @@ -8190,6 +8204,7 @@ static void rna_def_viewer_path(BlenderRNA *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_foreach_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 7fd2cdd1970..e3f3a3b9afc 100644 --- a/source/blender/makesrna/intern/rna_userdef.cc +++ b/source/blender/makesrna/intern/rna_userdef.cc @@ -3208,6 +3208,12 @@ static void rna_def_userdef_theme_space_node(BlenderRNA *brna) RNA_def_property_array(prop, 4); RNA_def_property_ui_text(prop, "Repeat Zone", ""); RNA_def_property_update(prop, 0, "rna_userdef_theme_update"); + + prop = RNA_def_property(srna, "foreach_zone", PROP_FLOAT, PROP_COLOR_GAMMA); + RNA_def_property_float_sdna(prop, nullptr, "node_zone_foreach"); + RNA_def_property_array(prop, 4); + RNA_def_property_ui_text(prop, "For-Each 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 5161c58a4a4..5563de44e7b 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -606,6 +606,28 @@ static void try_add_side_effect_node(const ComputeContext &final_compute_context compute_context->iteration()); current_zone = repeat_zone; } + else if (const auto *compute_context = dynamic_cast( + compute_context_generic)) + { + const bke::bNodeTreeZone *foreach_zone = current_zones->get_zone_by_node( + compute_context->output_node_id()); + if (foreach_zone == nullptr) { + return; + } + if (foreach_zone->parent_zone != current_zone) { + return; + } + const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default( + foreach_zone, nullptr); + if (lf_zone_node == nullptr) { + return; + } + local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_zone_node); + local_side_effect_nodes.indices_by_foreach_zone.add( + {parent_compute_context_hash, compute_context->output_node_id()}, + compute_context->index()); + current_zone = foreach_zone; + } else if (const auto *compute_context = dynamic_cast( compute_context_generic)) { diff --git a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh index 05a08df3c60..23c42b84ff9 100644 --- a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh +++ b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh @@ -135,6 +135,7 @@ struct GeoNodesSideEffectNodes { * repeat output node. */ MultiValueMap, int> iterations_by_repeat_zone; + MultiValueMap, int> indices_by_foreach_zone; }; /** diff --git a/source/blender/nodes/NOD_socket_items.hh b/source/blender/nodes/NOD_socket_items.hh index a2ecb7a228f..caa46e34ea2 100644 --- a/source/blender/nodes/NOD_socket_items.hh +++ b/source/blender/nodes/NOD_socket_items.hh @@ -137,6 +137,56 @@ inline void move_item(T *items, const int items_num, const int from_index, const } } +/** + * Low level utility to remove all socket items for which the predicate is true. + */ +template +inline void remove_if(T **items, + int *items_num, + int *active_index, + void (*destruct_item)(T *), + const FunctionRef predicate) +{ + static_assert(std::is_trivial_v); + T *old_items = *items; + const int old_items_num = *items_num; + Vector indices_to_keep; + for (const int i : IndexRange(old_items_num)) { + T &item = old_items[i]; + if (predicate(item)) { + destruct_item(&item); + } + else { + indices_to_keep.append(i); + } + } + const int new_items_num = indices_to_keep.size(); + if (old_items_num == new_items_num) { + return; + } + T *new_items = MEM_cnew_array(new_items_num, __func__); + for (const int i : IndexRange(new_items_num)) { + new_items[i] = old_items[indices_to_keep[i]]; + } + MEM_SAFE_FREE(old_items); + + *items = new_items; + *items_num = new_items_num; + *active_index = std::max(0, std::min(*active_index, new_items_num - 1)); +} + +template inline void remove_unsupported_socket_types(bNode &node) +{ + using ItemT = typename Accessor::ItemT; + SocketItemsRef ref = Accessor::get_items_from_node(node); + remove_if( + ref.items, ref.items_num, ref.active_index, Accessor::destruct_item, [&](const ItemT &item) { + const eNodeSocketDatatype socket_type = eNodeSocketDatatype( + *Accessor::get_socket_type(const_cast(item))); + return !Accessor::supports_socket_type(node, socket_type); + }); +} + /** * Destruct all the items and the free the array itself. */ @@ -217,7 +267,7 @@ inline typename Accessor::ItemT *add_item_with_socket_and_name( bNode &node, const eNodeSocketDatatype socket_type, const char *name) { using ItemT = typename Accessor::ItemT; - BLI_assert(Accessor::supports_socket_type(socket_type)); + BLI_assert(Accessor::supports_socket_type(node, socket_type)); SocketItemsRef array = Accessor::get_items_from_node(node); @@ -263,7 +313,7 @@ template return false; } const eNodeSocketDatatype socket_type = eNodeSocketDatatype(src_socket->type); - if (!Accessor::supports_socket_type(socket_type)) { + if (!Accessor::supports_socket_type(storage_node, socket_type)) { return false; } const ItemT *item = add_item_with_socket_and_name( diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 6d1bfb805e5..d614389f8dc 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -331,6 +331,8 @@ DefNode(GeometryNode, GEO_NODE_EXTRUDE_MESH, 0, "EXTRUDE_MESH", ExtrudeMesh, "Ex DefNode(GeometryNode, GEO_NODE_FILL_CURVE, 0, "FILL_CURVE", FillCurve, "Fill Curve", "Generate a mesh on the XY plane with faces on the inside of input curves") DefNode(GeometryNode, GEO_NODE_FILLET_CURVE, 0, "FILLET_CURVE", FilletCurve, "Fillet Curve", "Round corners by generating circular arcs on each control point") DefNode(GeometryNode, GEO_NODE_FLIP_FACES, 0, "FLIP_FACES", FlipFaces, "Flip Faces", "Reverse the order of the vertices and edges of selected faces, flipping their normal direction") +DefNode(GeometryNode, GEO_NODE_FOR_EACH_INPUT, def_geo_foreach_input, "FOREACH_INPUT", ForEachInput, "For-Each Input", "") +DefNode(GeometryNode, GEO_NODE_FOR_EACH_OUTPUT, def_geo_foreach_output, "FOREACH_OUTPUT", ForEachOutput, "For-Each Output", "") DefNode(GeometryNode, GEO_NODE_GEOMETRY_TO_INSTANCE, 0, "GEOMETRY_TO_INSTANCE", GeometryToInstance, "Geometry to Instance", "Convert each input geometry into an instance, which can be much faster than the Join Geometry node when the inputs are large") DefNode(GeometryNode, GEO_NODE_IMAGE_INFO, 0, "IMAGE_INFO", ImageInfo, "Image Info", "Retrieve information about an image") DefNode(GeometryNode, GEO_NODE_IMAGE_TEXTURE, def_geo_image_texture, "IMAGE_TEXTURE", ImageTexture, "Image Texture", "Sample values from an image texture") diff --git a/source/blender/nodes/NOD_zone_socket_items.hh b/source/blender/nodes/NOD_zone_socket_items.hh index 72e4703fe9d..15a9f3b3d57 100644 --- a/source/blender/nodes/NOD_zone_socket_items.hh +++ b/source/blender/nodes/NOD_zone_socket_items.hh @@ -47,7 +47,7 @@ struct SimulationItemsAccessor { { return &item.name; } - static bool supports_socket_type(const eNodeSocketDatatype socket_type) + static bool supports_socket_type(const bNode & /*node*/, const eNodeSocketDatatype socket_type) { return ELEM(socket_type, SOCK_FLOAT, @@ -109,7 +109,7 @@ struct RepeatItemsAccessor { { return &item.name; } - static bool supports_socket_type(const eNodeSocketDatatype socket_type) + static bool supports_socket_type(const bNode & /*node*/, const eNodeSocketDatatype socket_type) { return ELEM(socket_type, SOCK_FLOAT, @@ -141,4 +141,123 @@ struct RepeatItemsAccessor { } }; +struct ForEachInputItemsAccessor { + using ItemT = NodeForEachInputItem; + static StructRNA *item_srna; + /* This refers to the node that stores the item array. */ + static int node_type; + static constexpr const char *node_idname = "GeometryNodeForEachOutput"; + + static socket_items::SocketItemsRef get_items_from_node(bNode &node) + { + auto *storage = static_cast(node.storage); + return {&storage->input_items, &storage->input_items_num, &storage->input_active_index}; + } + static void copy_item(const NodeForEachInputItem &src, NodeForEachInputItem &dst) + { + dst = src; + dst.name = BLI_strdup_null(dst.name); + } + static void destruct_item(NodeForEachInputItem *item) + { + MEM_SAFE_FREE(item->name); + } + static void blend_write(BlendWriter *, const bNode &node); + static void blend_read_data(BlendDataReader *reader, bNode &node); + static short *get_socket_type(NodeForEachInputItem &item) + { + return &item.socket_type; + } + static char **get_name(NodeForEachInputItem &item) + { + return &item.name; + } + static bool supports_socket_type(const bNode &node, const eNodeSocketDatatype socket_type) + { + auto *storage = static_cast(node.storage); + switch (GeometryNodeForEachMode(storage->mode)) { + case GEO_NODE_FOR_EACH_MODE_INDEX: + case GEO_NODE_FOR_EACH_MODE_GEOMETRY_ELEMENT: { + return socket_type_supports_fields(socket_type); + } + case GEO_NODE_FOR_EACH_MODE_INSTANCE: { + return false; + } + } + return false; + } + static void init_with_socket_type_and_name(bNode &node, + NodeForEachInputItem &item, + const eNodeSocketDatatype socket_type, + const char *name) + { + auto *storage = static_cast(node.storage); + item.socket_type = socket_type; + item.identifier = storage->input_next_identifier++; + socket_items::set_item_name_and_make_unique(node, item, name); + } + static std::string socket_identifier_for_item(const NodeForEachInputItem &item) + { + return "Item_" + std::to_string(item.identifier); + } +}; + +struct ForEachOutputItemsAccessor { + using ItemT = NodeForEachOutputItem; + static StructRNA *item_srna; + static int node_type; + static constexpr const char *node_idname = "GeometryNodeForEachOutput"; + + static socket_items::SocketItemsRef get_items_from_node(bNode &node) + { + auto *storage = static_cast(node.storage); + return {&storage->output_items, &storage->output_items_num, &storage->output_active_index}; + } + static void copy_item(const NodeForEachOutputItem &src, NodeForEachOutputItem &dst) + { + dst = src; + dst.name = BLI_strdup_null(dst.name); + } + static void destruct_item(NodeForEachOutputItem *item) + { + MEM_SAFE_FREE(item->name); + } + static void blend_write(BlendWriter *, const bNode &node); + static void blend_read_data(BlendDataReader *reader, bNode &node); + static short *get_socket_type(NodeForEachOutputItem &item) + { + return &item.socket_type; + } + static char **get_name(NodeForEachOutputItem &item) + { + return &item.name; + } + static bool supports_socket_type(const bNode &node, const eNodeSocketDatatype socket_type) + { + auto *storage = static_cast(node.storage); + switch (GeometryNodeForEachMode(storage->mode)) { + case GEO_NODE_FOR_EACH_MODE_INDEX: + case GEO_NODE_FOR_EACH_MODE_GEOMETRY_ELEMENT: + case GEO_NODE_FOR_EACH_MODE_INSTANCE: { + return socket_type == SOCK_GEOMETRY || socket_type_supports_fields(socket_type); + } + } + return false; + } + static void init_with_socket_type_and_name(bNode &node, + NodeForEachOutputItem &item, + const eNodeSocketDatatype socket_type, + const char *name) + { + auto *storage = static_cast(node.storage); + item.socket_type = socket_type; + item.identifier = storage->output_next_identifier++; + socket_items::set_item_name_and_make_unique(node, item, name); + } + static std::string socket_identifier_for_item(const NodeForEachOutputItem &item) + { + return "Item_" + std::to_string(item.identifier); + } +}; + } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 0da46d2dda2..73d32ceff93 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -77,6 +77,8 @@ set(SRC nodes/node_geo_evaluate_on_domain.cc nodes/node_geo_extrude_mesh.cc nodes/node_geo_flip_faces.cc + nodes/node_geo_foreach_input.cc + nodes/node_geo_foreach_output.cc nodes/node_geo_geometry_to_instance.cc nodes/node_geo_image.cc nodes/node_geo_image_info.cc diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index 9787376e7e9..2f2c3450e68 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -20,7 +20,6 @@ #endif struct BVHTreeFromMesh; -struct GeometrySet; namespace blender::nodes { class GatherAddNodeSearchParams; class GatherLinkSearchOpParams; diff --git a/source/blender/nodes/geometry/nodes/node_geo_foreach_input.cc b/source/blender/nodes/geometry/nodes/node_geo_foreach_input.cc new file mode 100644 index 00000000000..eb7a4693f34 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_foreach_input.cc @@ -0,0 +1,138 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_compute_contexts.hh" +#include "BKE_scene.h" + +#include "DEG_depsgraph_query.hh" + +#include "UI_interface.hh" +#include "UI_resources.hh" + +#include "NOD_geometry.hh" +#include "NOD_socket.hh" +#include "NOD_zone_socket_items.hh" + +#include "RNA_access.hh" +#include "RNA_prototypes.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_foreach_input_cc { + +NODE_STORAGE_FUNCS(NodeGeometryForEachInput); + +static void node_declare_dynamic(const bNodeTree &tree, + const bNode &node, + NodeDeclarationBuilder &b) +{ + const NodeGeometryForEachInput &input_storage = node_storage(node); + const bNode *output_node = tree.node_by_id(input_storage.output_node_id); + if (output_node == nullptr) { + return; + } + const auto &output_storage = *static_cast( + output_node->storage); + const GeometryNodeForEachMode mode = GeometryNodeForEachMode(output_storage.mode); + { + /* Add standard inputs. */ + switch (mode) { + case GEO_NODE_FOR_EACH_MODE_INDEX: { + b.add_input("Amount").min(0).default_value(1); + b.add_output("Index"); + break; + } + case GEO_NODE_FOR_EACH_MODE_GEOMETRY_ELEMENT: { + b.add_input("Geometry"); + b.add_input("Selection").default_value(true).hide_value(true).supports_field(); + b.add_output("Index"); + if (output_storage.domain != ATTR_DOMAIN_CORNER) { + b.add_output("Element"); + } + break; + } + case GEO_NODE_FOR_EACH_MODE_INSTANCE: { + b.add_input("Instances"); + b.add_output("Geometry"); + break; + } + } + } + if (mode == GEO_NODE_FOR_EACH_MODE_INSTANCE) { + /* Other inputs are not allowed in this mode. */ + return; + } + /* Add dynamic sockets. */ + for (const int i : IndexRange(output_storage.input_items_num)) { + const NodeForEachInputItem &item = output_storage.input_items[i]; + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type); + const StringRef name = item.name; + const std::string identifier = ForEachInputItemsAccessor::socket_identifier_for_item(item); + auto &input_decl = b.add_input(socket_type, name, identifier); + b.add_output(socket_type, name, identifier); + if (socket_type_supports_fields(socket_type)) { + input_decl.supports_field(); + } + } + b.add_input("", "__extend__"); + b.add_output("", "__extend__"); +} + +static void node_init(bNodeTree * /*tree*/, bNode *node) +{ + NodeGeometryForEachInput *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 *tree, bNode *node, bNodeLink *link) +{ + const NodeGeometryForEachInput &input_storage = node_storage(*node); + bNode *output_node = tree->node_by_id(input_storage.output_node_id); + if (output_node == nullptr) { + return true; + } + return socket_items::try_add_item_via_any_extend_socket( + *tree, *node, *output_node, *link); +} + +static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + bNodeTree &tree = *reinterpret_cast(ptr->owner_id); + bNode &input_node = *static_cast(ptr->data); + const NodeGeometryForEachInput &input_storage = node_storage(input_node); + bNode *output_node = tree.node_by_id(input_storage.output_node_id); + if (output_node == nullptr) { + return; + } + const auto &output_storage = *static_cast( + output_node->storage); + PointerRNA output_node_ptr = RNA_pointer_create(ptr->owner_id, &RNA_Node, output_node); + + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiLayout *col = uiLayoutColumn(layout, false); + uiItemR(col, &output_node_ptr, "mode", UI_ITEM_NONE, "", ICON_NONE); + if (output_storage.mode == GEO_NODE_FOR_EACH_MODE_GEOMETRY_ELEMENT) { + uiItemR(col, &output_node_ptr, "domain", UI_ITEM_NONE, "Domain", ICON_NONE); + } +} + +static void node_register() +{ + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_FOR_EACH_INPUT, "For-Each Input", NODE_CLASS_INTERFACE); + ntype.initfunc = node_init; + ntype.declare_dynamic = node_declare_dynamic; + ntype.gather_link_search_ops = nullptr; + ntype.insert_link = node_insert_link; + ntype.draw_buttons = node_layout; + node_type_storage( + &ntype, "NodeGeometryForEachInput", node_free_standard_storage, node_copy_standard_storage); + nodeRegisterType(&ntype); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_foreach_input_cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_foreach_output.cc b/source/blender/nodes/geometry/nodes/node_geo_foreach_output.cc new file mode 100644 index 00000000000..935f80b587d --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_foreach_output.cc @@ -0,0 +1,97 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_compute_contexts.hh" +#include "BKE_scene.h" + +#include "DEG_depsgraph_query.hh" + +#include "UI_interface.hh" +#include "UI_resources.hh" + +#include "NOD_geometry.hh" +#include "NOD_socket.hh" +#include "NOD_zone_socket_items.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_foreach_output_cc { + +NODE_STORAGE_FUNCS(NodeGeometryForEachOutput); + +static void node_declare_dynamic(const bNodeTree & /*node_tree*/, + const bNode &node, + NodeDeclarationBuilder &b) +{ + const NodeGeometryForEachOutput &storage = node_storage(node); + for (const int i : IndexRange(storage.output_items_num)) { + const NodeForEachOutputItem &item = storage.output_items[i]; + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type); + const std::string identifier = ForEachOutputItemsAccessor::socket_identifier_for_item(item); + const StringRef name = item.name; + + auto &input_decl = b.add_input(socket_type, name, identifier); + auto &output_decl = b.add_output(socket_type, name, identifier); + + if (socket_type_supports_fields(socket_type)) { + input_decl.supports_field(); + output_decl.field_on_all(); + } + } + b.add_input("", "__extend__"); + b.add_output("", "__extend__"); +} + +static void node_init(bNodeTree * /*tree*/, bNode *node) +{ + NodeGeometryForEachOutput *data = MEM_cnew(__func__); + + data->output_items = MEM_cnew_array(1, __func__); + data->output_items_num = 1; + + NodeForEachOutputItem &item = data->output_items[0]; + item.name = BLI_strdup(DATA_("Geometry")); + item.socket_type = SOCK_GEOMETRY; + item.identifier = data->output_next_identifier++; + + node->storage = data; +} + +static void node_free_storage(bNode *node) +{ + socket_items::destruct_array(*node); + socket_items::destruct_array(*node); + MEM_freeN(node->storage); +} + +static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node) +{ + const NodeGeometryForEachOutput &src_storage = node_storage(*src_node); + auto *dst_storage = MEM_new(__func__, src_storage); + dst_node->storage = dst_storage; + + socket_items::copy_array(*src_node, *dst_node); + socket_items::copy_array(*src_node, *dst_node); +} + +static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link) +{ + return socket_items::try_add_item_via_any_extend_socket( + *ntree, *node, *node, *link); +} + +static void node_register() +{ + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_FOR_EACH_OUTPUT, "For-Each Output", NODE_CLASS_INTERFACE); + ntype.initfunc = node_init; + ntype.declare_dynamic = node_declare_dynamic; + ntype.gather_link_search_ops = nullptr; + ntype.insert_link = node_insert_link; + node_type_storage(&ntype, "NodeGeometryForEachOutput", node_free_storage, node_copy_storage); + nodeRegisterType(&ntype); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_foreach_output_cc diff --git a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc index c62fa2b1ad0..25f22be6a1f 100644 --- a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc +++ b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc @@ -37,6 +37,8 @@ #include "DNA_ID.h" +#include "GEO_join_geometries.hh" + #include "BKE_compute_contexts.hh" #include "BKE_geometry_set.hh" #include "BKE_node_tree_anonymous_attributes.hh" @@ -1483,6 +1485,67 @@ struct RepeatEvalStorage { Vector output_index_map; }; +static void build_interface_for_zone_function(const bNodeTreeZone &zone, + const ZoneBodyFunction &body_fn, + ZoneBuildInfo &r_zone_info, + Vector &r_inputs, + Vector &r_outputs) +{ + for (const bNodeSocket *socket : zone.input_node->input_sockets()) { + if (socket->typeinfo->geometry_nodes_cpp_type == nullptr) { + /* Skip "empty" sockets. */ + continue; + } + r_inputs.append_as( + socket->name, *socket->typeinfo->geometry_nodes_cpp_type, lf::ValueUsage::Maybe); + } + r_zone_info.indices.inputs.main = r_inputs.index_range(); + + for (const bNodeLink *link : zone.border_links) { + r_inputs.append_as(link->fromsock->name, + *link->tosock->typeinfo->geometry_nodes_cpp_type, + lf::ValueUsage::Maybe); + } + r_zone_info.indices.inputs.border_links = r_inputs.index_range().take_back( + zone.border_links.size()); + + for (const bNodeSocket *socket : zone.output_node->output_sockets()) { + if (socket->typeinfo->geometry_nodes_cpp_type == nullptr) { + continue; + } + r_inputs.append_as("Usage", CPPType::get(), lf::ValueUsage::Maybe); + r_outputs.append_as(socket->name, *socket->typeinfo->geometry_nodes_cpp_type); + } + r_zone_info.indices.outputs.main = r_outputs.index_range(); + r_zone_info.indices.inputs.output_usages = r_inputs.index_range().take_back( + r_zone_info.indices.outputs.main.size()); + + for ([[maybe_unused]] const bNodeSocket *socket : zone.input_node->input_sockets()) { + if (socket->typeinfo->geometry_nodes_cpp_type == nullptr) { + continue; + } + r_outputs.append_as("Usage", CPPType::get()); + } + r_zone_info.indices.outputs.input_usages = r_outputs.index_range().take_back( + r_zone_info.indices.inputs.main.size()); + for ([[maybe_unused]] const bNodeLink *link : zone.border_links) { + r_outputs.append_as("Border Link Usage", CPPType::get()); + } + r_zone_info.indices.outputs.border_link_usages = r_outputs.index_range().take_back( + zone.border_links.size()); + + for (const auto item : body_fn.indices.inputs.attributes_by_field_source_index.items()) { + const int index = r_inputs.append_and_get_index_as( + "Attribute Set", CPPType::get(), lf::ValueUsage::Maybe); + r_zone_info.indices.inputs.attributes_by_field_source_index.add_new(item.key, index); + } + for (const auto item : body_fn.indices.inputs.attributes_by_caller_propagation_index.items()) { + const int index = r_inputs.append_and_get_index_as( + "Attribute Set", CPPType::get(), lf::ValueUsage::Maybe); + r_zone_info.indices.inputs.attributes_by_caller_propagation_index.add_new(item.key, index); + } +} + class LazyFunctionForRepeatZone : public LazyFunction { private: const bNodeTreeZone &zone_; @@ -1500,53 +1563,10 @@ class LazyFunctionForRepeatZone : public LazyFunction { body_fn_(body_fn) { debug_name_ = "Repeat Zone"; + build_interface_for_zone_function(zone, body_fn, zone_info, inputs_, outputs_); - inputs_.append_as("Iterations", CPPType::get>(), lf::ValueUsage::Used); - for (const bNodeSocket *socket : zone.input_node->input_sockets().drop_front(1).drop_back(1)) { - inputs_.append_as( - socket->name, *socket->typeinfo->geometry_nodes_cpp_type, lf::ValueUsage::Maybe); - } - zone_info.indices.inputs.main = inputs_.index_range(); - - for (const bNodeLink *link : zone.border_links) { - inputs_.append_as(link->fromsock->name, - *link->tosock->typeinfo->geometry_nodes_cpp_type, - lf::ValueUsage::Maybe); - } - zone_info.indices.inputs.border_links = 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(), lf::ValueUsage::Maybe); - outputs_.append_as(socket->name, *socket->typeinfo->geometry_nodes_cpp_type); - } - zone_info.indices.inputs.output_usages = inputs_.index_range().take_back( - zone.output_node->output_sockets().drop_back(1).size()); - zone_info.indices.outputs.main = 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.indices.outputs.input_usages = 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.indices.outputs.border_link_usages = outputs_.index_range().take_back( - zone.border_links.size()); - - for (const auto item : body_fn_.indices.inputs.attributes_by_field_source_index.items()) { - const int index = inputs_.append_and_get_index_as( - "Attribute Set", CPPType::get(), lf::ValueUsage::Maybe); - zone_info.indices.inputs.attributes_by_field_source_index.add_new(item.key, index); - } - for (const auto item : body_fn_.indices.inputs.attributes_by_caller_propagation_index.items()) - { - const int index = inputs_.append_and_get_index_as( - "Attribute Set", CPPType::get(), lf::ValueUsage::Maybe); - zone_info.indices.inputs.attributes_by_caller_propagation_index.add_new(item.key, index); - } + /* Iterations input is always used. */ + inputs_[zone_info.indices.inputs.main[0]].usage = lf::ValueUsage::Used; } void *init_storage(LinearAllocator<> &allocator) const override @@ -1806,6 +1826,241 @@ class LazyFunctionForRepeatZone : public LazyFunction { } }; +class LazyFunctionForForEachSplit : public lf::LazyFunction { + private: + const int amount_; + + public: + LazyFunctionForForEachSplit(const int amount) : amount_(amount) + { + debug_name_ = "For-Each Split"; + const CPPType &type = CPPType::get>(); + outputs_.resize(amount, lf::Output("Index", type)); + } + + void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override + { + for (const int i : IndexRange(amount_)) { + params.set_output(i, ValueOrField(i)); + } + } +}; + +class LazyFunctionForForEachReduce : public lf::LazyFunction { + private: + const int amount_; + + public: + LazyFunctionForForEachReduce(const int amount) : amount_(amount) + { + debug_name_ = "For-Each Reduce"; + const CPPType &geo_cpp_type = CPPType::get(); + for ([[maybe_unused]] const int i : IndexRange(amount)) { + inputs_.append_as("Geometry", geo_cpp_type); + } + outputs_.append_as("Geometry", geo_cpp_type); + } + + void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override + { + Vector geometries(amount_); + for (const int i : IndexRange(amount_)) { + geometries[i] = params.extract_input(i); + } + GeometrySet joined_geometries = geometry::join_geometries(geometries, {}); + params.set_output(0, std::move(joined_geometries)); + } +}; + +struct ForEachEvalStorage { + LinearAllocator<> allocator; + lf::Graph graph; + std::optional graph_executor; + std::optional split_fn; + std::optional reduce_fn; + std::optional or_fn; + void *graph_executor_storage = nullptr; + Vector input_index_map; + Vector output_index_map; + bool multi_threading_enabled = false; +}; + +class LazyFunctionForForeachZone : public LazyFunction { + private: + const bNodeTreeZone &zone_; + const ZoneBuildInfo &zone_info_; + const ZoneBodyFunction &body_fn_; + + public: + LazyFunctionForForeachZone(const bNodeTreeZone &zone, + ZoneBuildInfo &zone_info, + const ZoneBodyFunction &body_fn) + : zone_(zone), zone_info_(zone_info), body_fn_(body_fn) + { + debug_name_ = "For-Each Zone"; + build_interface_for_zone_function(zone, body_fn, zone_info, inputs_, outputs_); + /* The Amount input is always used. */ + inputs_[zone_info.indices.inputs.main[0]].usage = lf::ValueUsage::Used; + } + + void *init_storage(LinearAllocator<> &allocator) const override + { + return allocator.construct().release(); + } + void destruct_storage(void *storage) const override + { + ForEachEvalStorage *s = static_cast(storage); + if (s->graph_executor_storage) { + s->graph_executor->destruct_storage(s->graph_executor_storage); + } + std::destroy_at(s); + } + + void execute_impl(lf::Params ¶ms, const lf::Context &context) const override + { + auto &user_data = *static_cast(context.user_data); + auto &local_user_data = *static_cast(context.local_user_data); + + const auto &node_storage = *static_cast( + zone_.output_node->storage); + ForEachEvalStorage &eval_storage = *static_cast(context.storage); + + const int iteration_usage_index = zone_info_.indices.outputs.input_usages[0]; + if (!params.output_was_set(iteration_usage_index)) { + params.set_output(iteration_usage_index, true); + } + + if (!eval_storage.graph_executor) { + this->initialize_execution_graph( + params, eval_storage, node_storage, user_data, local_user_data); + } + + lf::RemappedParams eval_graph_params{*eval_storage.graph_executor, + params, + eval_storage.input_index_map, + eval_storage.output_index_map, + eval_storage.multi_threading_enabled}; + lf::Context eval_graph_context{ + eval_storage.graph_executor_storage, context.user_data, context.local_user_data}; + eval_storage.graph_executor->execute(eval_graph_params, eval_graph_context); + } + + void initialize_execution_graph(lf::Params ¶ms, + ForEachEvalStorage &eval_storage, + const NodeGeometryForEachOutput &node_storage, + GeoNodesLFUserData &user_data, + GeoNodesLFLocalUserData &local_user_data) const + { + UNUSED_VARS(params, eval_storage, node_storage, user_data, local_user_data); + + const int amount = params.get_input>(0).as_value(); + const int main_outputs_num = node_storage.output_items_num; + const int border_links_num = zone_.border_links.size(); + + lf::Graph &lf_graph = eval_storage.graph; + + for (const int i : inputs_.index_range()) { + const lf::Input &input = inputs_[i]; + lf_graph.add_input(*input.type, input.debug_name); + } + for (const int i : outputs_.index_range()) { + const lf::Output &output = outputs_[i]; + lf_graph.add_output(*output.type, output.debug_name); + } + + VectorSet lf_body_nodes; + for ([[maybe_unused]] const int i : IndexRange(amount)) { + lf::FunctionNode &lf_node = lf_graph.add_function(*body_fn_.function); + lf_body_nodes.add_new(&lf_node); + } + + eval_storage.split_fn.emplace(amount); + lf::FunctionNode &lf_split_node = lf_graph.add_function(*eval_storage.split_fn); + + eval_storage.reduce_fn.emplace(amount); + lf::FunctionNode &lf_reduce_node = lf_graph.add_function(*eval_storage.reduce_fn); + + for (const int node_i : IndexRange(amount)) { + lf::FunctionNode &lf_body_node = *lf_body_nodes[node_i]; + lf_graph.add_link(lf_split_node.output(node_i), + lf_body_node.input(body_fn_.indices.inputs.main[0])); + lf_graph.add_link(lf_body_node.output(body_fn_.indices.outputs.main[0]), + lf_reduce_node.input(node_i)); + + for (const int main_output_i : IndexRange(main_outputs_num)) { + lf_graph.add_link( + *lf_graph.graph_inputs()[zone_info_.indices.inputs.output_usages[main_output_i]], + lf_body_node.input(body_fn_.indices.inputs.output_usages[main_output_i])); + } + for (const int border_link_i : IndexRange(border_links_num)) { + lf_graph.add_link( + *lf_graph.graph_inputs()[zone_info_.indices.inputs.border_links[border_link_i]], + lf_body_node.input(body_fn_.indices.inputs.border_links[border_link_i])); + } + } + + static bool static_true = true; + for (const int i : zone_info_.indices.outputs.border_link_usages) { + lf_graph.graph_outputs()[i]->set_default_value(&static_true); + } + for (const int i : zone_info_.indices.outputs.input_usages) { + lf_graph.graph_outputs()[i]->set_default_value(&static_true); + } + + lf_graph.add_link(lf_reduce_node.output(0), *lf_graph.graph_outputs()[0]); + + eval_storage.input_index_map.reinitialize(inputs_.size()); + array_utils::fill_index_range(eval_storage.input_index_map); + eval_storage.output_index_map.reinitialize(outputs_.size()); + array_utils::fill_index_range(eval_storage.output_index_map); + + eval_storage.input_index_map.remove(zone_info_.indices.inputs.main[0]); + eval_storage.output_index_map.remove(zone_info_.indices.outputs.input_usages[0]); + + Vector lf_graph_inputs; + Vector lf_graph_outputs; + for (const int i : eval_storage.input_index_map) { + lf_graph_inputs.append(lf_graph.graph_inputs()[i]); + } + for (const int i : eval_storage.output_index_map) { + lf_graph_outputs.append(lf_graph.graph_outputs()[i]); + } + + lf_graph.update_node_indices(); + + eval_storage.graph_executor.emplace(lf_graph, + std::move(lf_graph_inputs), + std::move(lf_graph_outputs), + nullptr, + nullptr, + nullptr); + eval_storage.graph_executor_storage = eval_storage.graph_executor->init_storage( + eval_storage.allocator); + + std::cout << "\n\n" << lf_graph.to_dot() << "\n\n"; + } + + std::string input_name(const int i) const override + { + if (zone_info_.indices.inputs.output_usages.contains(i)) { + const bNodeSocket &bsocket = zone_.output_node->output_socket( + i - zone_info_.indices.inputs.output_usages.first()); + return "Usage: " + StringRef(bsocket.name); + } + return inputs_[i].debug_name; + } + + std::string output_name(const int i) const override + { + if (zone_info_.indices.outputs.input_usages.contains(i)) { + const bNodeSocket &bsocket = zone_.input_node->input_socket( + i - zone_info_.indices.outputs.input_usages.first()); + return "Usage: " + StringRef(bsocket.name); + } + return outputs_[i].debug_name; + } +}; + /** * Logs intermediate values from the lazy-function graph evaluation into #GeoModifierLog based on * the mapping between the lazy-function graph and the corresponding #bNodeTree. @@ -2063,6 +2318,10 @@ struct GeometryNodesLazyFunctionBuilder { this->build_repeat_zone_function(zone); break; } + case GEO_NODE_FOR_EACH_OUTPUT: { + this->build_foreach_zone_function(zone); + break; + } default: { BLI_assert_unreachable(); break; @@ -2257,6 +2516,14 @@ struct GeometryNodesLazyFunctionBuilder { zone_info.lazy_function = &zone_fn; } + void build_foreach_zone_function(const bNodeTreeZone &zone) + { + ZoneBuildInfo &zone_info = zone_build_infos_[zone.index]; + ZoneBodyFunction &body_fn = this->build_zone_body_function(zone); + auto &zone_fn = scope_.construct(zone, zone_info, body_fn); + zone_info.lazy_function = &zone_fn; + } + /** * Build a lazy-function for the "body" of a zone, i.e. for all the nodes within the zone. */ @@ -2273,7 +2540,11 @@ struct GeometryNodesLazyFunctionBuilder { Vector lf_main_inputs; Vector lf_main_input_usages; - for (const bNodeSocket *bsocket : zone.input_node->output_sockets().drop_back(1)) { + for (const bNodeSocket *bsocket : zone.input_node->output_sockets()) { + if (bsocket->typeinfo->geometry_nodes_cpp_type == nullptr) { + /* Skip empty sockets. */ + continue; + } lf::GraphInputSocket &lf_input = lf_body_graph.add_input( *bsocket->typeinfo->geometry_nodes_cpp_type, bsocket->name); lf::GraphOutputSocket &lf_input_usage = lf_body_graph.add_output( @@ -2293,7 +2564,11 @@ struct GeometryNodesLazyFunctionBuilder { Vector lf_main_outputs; Vector lf_main_output_usages; - for (const bNodeSocket *bsocket : zone.output_node->input_sockets().drop_back(1)) { + for (const bNodeSocket *bsocket : zone.output_node->input_sockets()) { + if (bsocket->typeinfo->geometry_nodes_cpp_type == nullptr) { + /* Skip empty sockets. */ + continue; + } lf::GraphOutputSocket &lf_output = lf_body_graph.add_output( *bsocket->typeinfo->geometry_nodes_cpp_type, bsocket->name); lf::GraphInputSocket &lf_output_usage = lf_body_graph.add_input( @@ -2326,10 +2601,14 @@ struct GeometryNodesLazyFunctionBuilder { 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::GraphOutputSocket &lf_usage_output = *lf_main_input_usages[i]; + int lf_input_index = 0; + for (const bNodeSocket *bsocket : zone.input_node->output_sockets()) { + if (bsocket->typeinfo->geometry_nodes_cpp_type == nullptr) { + /* Skip empty socket. */ + continue; + } + lf::OutputSocket *lf_usage = graph_params.usage_by_bsocket.lookup_default(bsocket, nullptr); + lf::GraphOutputSocket &lf_usage_output = *lf_main_input_usages[lf_input_index]; if (lf_usage) { lf_body_graph.add_link(*lf_usage, lf_usage_output); } @@ -2337,6 +2616,7 @@ struct GeometryNodesLazyFunctionBuilder { static const bool static_false = false; lf_usage_output.set_default_value(&static_false); } + lf_input_index++; } for (const auto item : graph_params.lf_output_by_bsocket.items()) { @@ -2886,6 +3166,7 @@ struct GeometryNodesLazyFunctionBuilder { *child_zone_info.lazy_function); mapping_->zone_node_map.add_new(&child_zone, &child_zone_node); + /* TODO: Handle "empty" sockets that are not at the end. */ for (const int i : child_zone_info.indices.inputs.main.index_range()) { const bNodeSocket &bsocket = child_zone.input_node->input_socket(i); lf::InputSocket &lf_input_socket = child_zone_node.input( diff --git a/source/blender/nodes/intern/node_register.cc b/source/blender/nodes/intern/node_register.cc index 406ffc34b89..1451ece077b 100644 --- a/source/blender/nodes/intern/node_register.cc +++ b/source/blender/nodes/intern/node_register.cc @@ -88,12 +88,32 @@ class RepeatZoneType : public blender::bke::bNodeZoneType { } }; +class ForEachZoneType : public blender::bke::bNodeZoneType { + public: + ForEachZoneType() + { + this->input_idname = "GeometryNodeForEachInput"; + this->output_idname = "GeometryNodeForEachOutput"; + this->input_type = GEO_NODE_FOR_EACH_INPUT; + this->output_type = GEO_NODE_FOR_EACH_OUTPUT; + this->theme_id = TH_NODE_ZONE_FOR_EACH; + } + + const int &get_corresponding_output_id(const bNode &input_bnode) const override + { + BLI_assert(input_bnode.type == this->input_type); + return static_cast(input_bnode.storage)->output_node_id; + } +}; + static void register_zone_types() { static SimulationZoneType simulation_zone_type; static RepeatZoneType repeat_zone_type; + static ForEachZoneType foreach_zone_type; blender::bke::register_node_zone_type(simulation_zone_type); blender::bke::register_node_zone_type(repeat_zone_type); + blender::bke::register_node_zone_type(foreach_zone_type); } void register_nodes() diff --git a/source/blender/nodes/intern/node_zone_socket_items.cc b/source/blender/nodes/intern/node_zone_socket_items.cc index 03c6b104ca1..9df8726e8b0 100644 --- a/source/blender/nodes/intern/node_zone_socket_items.cc +++ b/source/blender/nodes/intern/node_zone_socket_items.cc @@ -56,4 +56,48 @@ void RepeatItemsAccessor::blend_read_data(BlendDataReader *reader, bNode &node) } } +StructRNA *ForEachInputItemsAccessor::item_srna = &RNA_ForEachInputItem; +int ForEachInputItemsAccessor::node_type = GEO_NODE_FOR_EACH_OUTPUT; + +void ForEachInputItemsAccessor::blend_write(BlendWriter *writer, const bNode &node) +{ + const auto &storage = *static_cast(node.storage); + BLO_write_struct_array( + writer, NodeForEachInputItem, storage.input_items_num, storage.input_items); + for (const NodeForEachInputItem &item : Span(storage.input_items, storage.input_items_num)) { + BLO_write_string(writer, item.name); + } +} + +void ForEachInputItemsAccessor::blend_read_data(BlendDataReader *reader, bNode &node) +{ + auto &storage = *static_cast(node.storage); + BLO_read_data_address(reader, &storage.input_items); + for (const NodeForEachInputItem &item : Span(storage.input_items, storage.input_items_num)) { + BLO_read_data_address(reader, &item.name); + } +} + +StructRNA *ForEachOutputItemsAccessor::item_srna = &RNA_ForEachOutputItem; +int ForEachOutputItemsAccessor::node_type = GEO_NODE_REPEAT_OUTPUT; + +void ForEachOutputItemsAccessor::blend_write(BlendWriter *writer, const bNode &node) +{ + const auto &storage = *static_cast(node.storage); + BLO_write_struct_array( + writer, NodeForEachOutputItem, storage.output_items_num, storage.output_items); + for (const NodeForEachOutputItem &item : Span(storage.output_items, storage.output_items_num)) { + BLO_write_string(writer, item.name); + } +} + +void ForEachOutputItemsAccessor::blend_read_data(BlendDataReader *reader, bNode &node) +{ + auto &storage = *static_cast(node.storage); + BLO_read_data_address(reader, &storage.output_items); + for (const NodeForEachOutputItem &item : Span(storage.output_items, storage.output_items_num)) { + BLO_read_data_address(reader, &item.name); + } +} + } // namespace blender::nodes