From 28cef56ad2866993a4bd7ef1a4bb42da69dffaa6 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 31 May 2024 16:23:31 +0200 Subject: [PATCH] Geometry Nodes: support capturing multiple attributes at once By capturing multiple attributes with one node, the user can make sure that those are evaluated together in the same context. This can be quite a bit more efficient compared to capturing multiple fields separately (also because we don't optimize grouping multiple capture nodes together yet). The change is fully backward compatible. Forward compatibility has been added for some cases. Especially, files created in older versions that are saved with this newer version will still work in the older version. Co-authored-by: Hans Goudey Pull Request: https://projects.blender.org/blender/blender/pulls/121665 --- .../blender/blenkernel/BKE_geometry_fields.hh | 58 +++- .../blenkernel/intern/geometry_fields.cc | 169 ++++++----- source/blender/blenkernel/intern/node.cc | 22 ++ .../blenloader/intern/versioning_300.cc | 4 +- .../blenloader/intern/versioning_400.cc | 26 +- source/blender/makesdna/DNA_node_types.h | 19 +- .../blender/makesdna/intern/dna_rename_defs.h | 1 + .../blender/makesrna/intern/rna_nodetree.cc | 128 +++++++-- source/blender/nodes/NOD_socket_items.hh | 24 +- source/blender/nodes/NOD_socket_items_ops.hh | 4 +- source/blender/nodes/NOD_static_types.h | 2 +- .../nodes/geometry/include/NOD_geo_bake.hh | 1 + .../include/NOD_geo_capture_attribute.hh | 92 ++++++ .../geometry/include/NOD_geo_index_switch.hh | 1 + .../geometry/include/NOD_geo_menu_switch.hh | 1 + .../nodes/geometry/include/NOD_geo_repeat.hh | 1 + .../geometry/include/NOD_geo_simulation.hh | 1 + .../nodes/node_geo_attribute_capture.cc | 269 ++++++++++++++---- 18 files changed, 650 insertions(+), 173 deletions(-) create mode 100644 source/blender/nodes/geometry/include/NOD_geo_capture_attribute.hh diff --git a/source/blender/blenkernel/BKE_geometry_fields.hh b/source/blender/blenkernel/BKE_geometry_fields.hh index acc40546629..df3ef47b07d 100644 --- a/source/blender/blenkernel/BKE_geometry_fields.hh +++ b/source/blender/blenkernel/BKE_geometry_fields.hh @@ -463,23 +463,51 @@ class EvaluateOnDomainInput final : public bke::GeometryFieldInput { const GeometryComponent & /*component*/) const override; }; -bool try_capture_field_on_geometry(MutableAttributeAccessor attributes, - const fn::FieldContext &field_context, - const AttributeIDRef &attribute_id, - AttrDomain domain, - const fn::Field &selection, - const fn::GField &field); +bool try_capture_fields_on_geometry(MutableAttributeAccessor attributes, + const fn::FieldContext &field_context, + Span attribute_ids, + AttrDomain domain, + const fn::Field &selection, + Span fields); -bool try_capture_field_on_geometry(GeometryComponent &component, - const AttributeIDRef &attribute_id, - AttrDomain domain, - const fn::GField &field); +inline bool try_capture_field_on_geometry(MutableAttributeAccessor attributes, + const fn::FieldContext &field_context, + const AttributeIDRef &attribute_id, + AttrDomain domain, + const fn::Field &selection, + const fn::GField &field) +{ + return try_capture_fields_on_geometry( + attributes, field_context, {attribute_id}, domain, selection, {field}); +} -bool try_capture_field_on_geometry(GeometryComponent &component, - const AttributeIDRef &attribute_id, - AttrDomain domain, - const fn::Field &selection, - const fn::GField &field); +bool try_capture_fields_on_geometry(GeometryComponent &component, + Span attribute_ids, + AttrDomain domain, + Span fields); + +inline bool try_capture_field_on_geometry(GeometryComponent &component, + const AttributeIDRef &attribute_id, + AttrDomain domain, + const fn::GField &field) +{ + return try_capture_fields_on_geometry(component, {attribute_id}, domain, {field}); +} + +bool try_capture_fields_on_geometry(GeometryComponent &component, + Span attribute_ids, + AttrDomain domain, + const fn::Field &selection, + Span fields); + +inline bool try_capture_field_on_geometry(GeometryComponent &component, + const AttributeIDRef &attribute_id, + AttrDomain domain, + const fn::Field &selection, + const fn::GField &field) +{ + return try_capture_fields_on_geometry(component, {attribute_id}, domain, selection, {field}); +} /** * Try to find the geometry domain that the field should be evaluated on. If it is not obvious diff --git a/source/blender/blenkernel/intern/geometry_fields.cc b/source/blender/blenkernel/intern/geometry_fields.cc index a43c03f72a8..da06f6c4501 100644 --- a/source/blender/blenkernel/intern/geometry_fields.cc +++ b/source/blender/blenkernel/intern/geometry_fields.cc @@ -821,80 +821,113 @@ static bool attribute_data_matches_varray(const GAttributeReader &attribute, con return varray_info.data == attribute_info.data; } -bool try_capture_field_on_geometry(MutableAttributeAccessor attributes, - const fn::FieldContext &field_context, - const AttributeIDRef &attribute_id, - const AttrDomain domain, - const fn::Field &selection, - const fn::GField &field) +bool try_capture_fields_on_geometry(MutableAttributeAccessor attributes, + const fn::FieldContext &field_context, + const Span attribute_ids, + const AttrDomain domain, + const fn::Field &selection, + const Span fields) { + BLI_assert(attribute_ids.size() == fields.size()); const int domain_size = attributes.domain_size(domain); - const CPPType &type = field.cpp_type(); - const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(type); - if (domain_size == 0) { - return attributes.add(attribute_id, domain, data_type, AttributeInitConstruct{}); - } - - const bke::AttributeValidator validator = attributes.lookup_validator(attribute_id); - - /* We are writing to an attribute that exists already with the correct domain and type. */ - if (const GAttributeReader dst = attributes.lookup(attribute_id)) { - if (dst.domain == domain && dst.varray.type() == field.cpp_type()) { - fn::FieldEvaluator evaluator{field_context, domain_size}; - evaluator.add(validator.validate_field_if_necessary(field)); - evaluator.set_selection(selection); - evaluator.evaluate(); - const GVArray &result = evaluator.get_evaluated(0); - if (attribute_data_matches_varray(dst, result)) { - return true; - } - - GSpanAttributeWriter dst_mut = attributes.lookup_for_write_span(attribute_id); - array_utils::copy(result, evaluator.get_evaluated_selection_as_mask(), dst_mut.span); - dst_mut.finish(); - return true; + bool all_added = true; + for (const int i : attribute_ids.index_range()) { + const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(fields[i].cpp_type()); + all_added &= attributes.add(attribute_ids[i], domain, data_type, AttributeInitConstruct{}); } + return all_added; } + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.set_selection(selection); + const bool selection_is_full = !selection.node().depends_on_input() && fn::evaluate_constant_field(selection); - if (!validator && selection_is_full) { - if (try_add_shared_field_attribute(attributes, attribute_id, domain, field)) { - return true; + struct StoreResult { + int input_index; + int evaluator_index; + }; + Vector results_to_store; + + struct AddResult { + int input_index; + int evaluator_index; + void *buffer; + }; + Vector results_to_add; + + for (const int input_index : attribute_ids.index_range()) { + const AttributeIDRef &id = attribute_ids[input_index]; + const AttributeValidator validator = attributes.lookup_validator(id); + const fn::GField field = validator.validate_field_if_necessary(fields[input_index]); + const CPPType &type = field.cpp_type(); + + /* We are writing to an attribute that exists already with the correct domain and type. */ + if (const GAttributeReader dst = attributes.lookup(id)) { + if (dst.domain == domain && dst.varray.type() == field.cpp_type()) { + const int evaluator_index = evaluator.add(field); + results_to_store.append({input_index, evaluator_index}); + continue; + } + } + + if (!validator && selection_is_full) { + if (try_add_shared_field_attribute(attributes, id, domain, field)) { + continue; + } + } + + /* Could avoid allocating a new buffer if: + * - The field does not depend on that attribute (we can't easily check for that yet). */ + void *buffer = MEM_mallocN_aligned(type.size() * domain_size, type.alignment(), __func__); + if (!selection_is_full) { + type.value_initialize_n(buffer, domain_size); + } + + GMutableSpan dst(type, buffer, domain_size); + const int evaluator_index = evaluator.add_with_destination(field, dst); + results_to_add.append({input_index, evaluator_index, buffer}); + } + + evaluator.evaluate(); + const IndexMask &mask = evaluator.get_evaluated_selection_as_mask(); + + for (const StoreResult &result : results_to_store) { + const AttributeIDRef &id = attribute_ids[result.input_index]; + const GVArray &result_data = evaluator.get_evaluated(result.evaluator_index); + const GAttributeReader dst = attributes.lookup(id); + if (!attribute_data_matches_varray(dst, result_data)) { + GSpanAttributeWriter dst_mut = attributes.lookup_for_write_span(id); + array_utils::copy(result_data, mask, dst_mut.span); + dst_mut.finish(); } } - /* Could avoid allocating a new buffer if: - * - The field does not depend on that attribute (we can't easily check for that yet). */ - void *buffer = MEM_mallocN_aligned(type.size() * domain_size, type.alignment(), __func__); - if (!selection_is_full) { - type.value_initialize_n(buffer, domain_size); - } - fn::FieldEvaluator evaluator{field_context, domain_size}; - evaluator.add_with_destination(validator.validate_field_if_necessary(field), - GMutableSpan{type, buffer, domain_size}); - evaluator.set_selection(selection); - evaluator.evaluate(); - - attributes.remove(attribute_id); - if (attributes.add(attribute_id, domain, data_type, bke::AttributeInitMoveArray(buffer))) { - return true; + bool success = true; + for (const AddResult &result : results_to_add) { + const AttributeIDRef &id = attribute_ids[result.input_index]; + attributes.remove(id); + const CPPType &type = fields[result.input_index].cpp_type(); + const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(type); + if (!attributes.add(id, domain, data_type, AttributeInitMoveArray(result.buffer))) { + /* If the name corresponds to a builtin attribute, removing the attribute might fail if + * it's required, adding the attribute might fail if the domain or type is incorrect. */ + type.destruct_n(result.buffer, domain_size); + MEM_freeN(result.buffer); + success = false; + } } - /* If the name corresponds to a builtin attribute, removing the attribute might fail if - * it's required, and adding the attribute might fail if the domain or type is incorrect. */ - type.destruct_n(buffer, domain_size); - MEM_freeN(buffer); - return false; + return success; } -bool try_capture_field_on_geometry(GeometryComponent &component, - const AttributeIDRef &attribute_id, - const AttrDomain domain, - const fn::Field &selection, - const fn::GField &field) +bool try_capture_fields_on_geometry(GeometryComponent &component, + const Span attribute_ids, + const AttrDomain domain, + const fn::Field &selection, + const Span fields) { const GeometryComponent::Type component_type = component.type(); if (component_type == GeometryComponent::Type::GreasePencil && @@ -913,13 +946,13 @@ bool try_capture_field_on_geometry(GeometryComponent &component, *grease_pencil->layer(layer_index))) { const GeometryFieldContext field_context{*grease_pencil, domain, layer_index}; - const bool success = try_capture_field_on_geometry( + const bool success = try_capture_fields_on_geometry( drawing->strokes_for_write().attributes_for_write(), field_context, - attribute_id, + attribute_ids, domain, selection, - field); + fields); if (success & !any_success) { any_success = true; } @@ -935,17 +968,17 @@ bool try_capture_field_on_geometry(GeometryComponent &component, MutableAttributeAccessor attributes = *component.attributes_for_write(); const GeometryFieldContext field_context{component, domain}; - return try_capture_field_on_geometry( - attributes, field_context, attribute_id, domain, selection, field); + return try_capture_fields_on_geometry( + attributes, field_context, attribute_ids, domain, selection, fields); } -bool try_capture_field_on_geometry(GeometryComponent &component, - const AttributeIDRef &attribute_id, - const AttrDomain domain, - const fn::GField &field) +bool try_capture_fields_on_geometry(GeometryComponent &component, + const Span attribute_ids, + const AttrDomain domain, + const Span fields) { const fn::Field selection = fn::make_constant_field(true); - return try_capture_field_on_geometry(component, attribute_id, domain, selection, field); + return try_capture_fields_on_geometry(component, attribute_ids, domain, selection, fields); } std::optional try_detect_field_domain(const GeometryComponent &component, diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index bae1e29fe20..a3681e047ff 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -84,6 +84,7 @@ #include "NOD_common.h" #include "NOD_composite.hh" #include "NOD_geo_bake.hh" +#include "NOD_geo_capture_attribute.hh" #include "NOD_geo_index_switch.hh" #include "NOD_geo_menu_switch.hh" #include "NOD_geo_repeat.hh" @@ -843,6 +844,23 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) } BLO_write_struct_by_name(writer, node->typeinfo->storagename, storage); } + else if (node->type == GEO_NODE_CAPTURE_ATTRIBUTE) { + auto &storage = *static_cast(node->storage); + /* Improve forward compatibility. */ + storage.data_type_legacy = CD_PROP_FLOAT; + for (const NodeGeometryAttributeCaptureItem &item : + Span{storage.capture_items, storage.capture_items_num}) + { + if (item.identifier == 0) { + /* The sockets of this item have the same identifiers that have been used by older + * Blender versions before the node supported capturing multiple attributes. */ + storage.data_type_legacy = item.data_type; + break; + } + } + BLO_write_struct(writer, NodeGeometryAttributeCapture, node->storage); + nodes::CaptureAttributeItemsAccessor::blend_write(writer, *node); + } else if (node->typeinfo != &NodeTypeUndefined) { BLO_write_struct_by_name(writer, node->typeinfo->storagename, node->storage); } @@ -1158,6 +1176,10 @@ void ntreeBlendReadData(BlendDataReader *reader, ID *owner_id, bNodeTree *ntree) nodes::MenuSwitchItemsAccessor::blend_read_data(reader, *node); break; } + case GEO_NODE_CAPTURE_ATTRIBUTE: { + nodes::CaptureAttributeItemsAccessor::blend_read_data(reader, *node); + break; + } default: break; diff --git a/source/blender/blenloader/intern/versioning_300.cc b/source/blender/blenloader/intern/versioning_300.cc index e2be86744e2..e0ca819919b 100644 --- a/source/blender/blenloader/intern/versioning_300.cc +++ b/source/blender/blenloader/intern/versioning_300.cc @@ -976,7 +976,7 @@ static void version_geometry_nodes_extrude_smooth_propagation(bNodeTree &ntree) bNode *capture_node = geometry_in_link->fromnode; const NodeGeometryAttributeCapture &capture_storage = *static_cast(capture_node->storage); - if (capture_storage.data_type != CD_PROP_BOOL || + if (capture_storage.data_type_legacy != CD_PROP_BOOL || bke::AttrDomain(capture_storage.domain) != bke::AttrDomain::Face) { return false; @@ -1020,7 +1020,7 @@ static void version_geometry_nodes_extrude_smooth_propagation(bNodeTree &ntree) new_nodes.append(&capture_node); auto *capture_node_storage = MEM_cnew(__func__); capture_node.storage = capture_node_storage; - capture_node_storage->data_type = CD_PROP_BOOL; + capture_node_storage->data_type_legacy = CD_PROP_BOOL; capture_node_storage->domain = int8_t(bke::AttrDomain::Face); bNodeSocket &capture_node_geo_in = version_node_add_socket( ntree, capture_node, SOCK_IN, "NodeSocketGeometry", "Geometry"); diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index c2704209d86..067568ec3f8 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -3674,7 +3674,6 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) scene->eevee.fast_gi_thickness_far = default_scene->eevee.fast_gi_thickness_far; } } - if (!MAIN_VERSION_FILE_ATLEAST(bmain, 402, 48)) { LISTBASE_FOREACH (Object *, ob, &bmain->objects) { if (!ob->pose) { @@ -3699,6 +3698,31 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) } } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 402, 50)) { + LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { + if (ntree->type != NTREE_GEOMETRY) { + continue; + } + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->type != GEO_NODE_CAPTURE_ATTRIBUTE) { + continue; + } + NodeGeometryAttributeCapture *storage = static_cast( + node->storage); + if (storage->next_identifier > 0) { + continue; + } + storage->capture_items_num = 1; + storage->capture_items = MEM_cnew_array( + storage->capture_items_num, __func__); + NodeGeometryAttributeCaptureItem &item = storage->capture_items[0]; + item.data_type = storage->data_type_legacy; + item.identifier = storage->next_identifier++; + item.name = BLI_strdup("Value"); + } + } + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check. diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 50329a86c0d..24e0f06ce73 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1791,11 +1791,28 @@ typedef struct NodeGeometryMeshToPoints { uint8_t mode; } NodeGeometryMeshToPoints; -typedef struct NodeGeometryAttributeCapture { +typedef struct NodeGeometryAttributeCaptureItem { /** #eCustomDataType. */ int8_t data_type; + char _pad[3]; + /** + * If the identifier is zero, the item supports forward-compatibility with older versions of + * Blender when it was only possible to capture a single attribute at a time. + */ + int identifier; + char *name; +} NodeGeometryAttributeCaptureItem; + +typedef struct NodeGeometryAttributeCapture { + /** #eCustomDataType. */ + int8_t data_type_legacy; /** #AttrDomain. */ int8_t domain; + char _pad[2]; + int next_identifier; + NodeGeometryAttributeCaptureItem *capture_items; + int capture_items_num; + int active_index; } NodeGeometryAttributeCapture; typedef struct NodeGeometryStoreNamedAttribute { diff --git a/source/blender/makesdna/intern/dna_rename_defs.h b/source/blender/makesdna/intern/dna_rename_defs.h index 784e93299aa..a47e93a92e8 100644 --- a/source/blender/makesdna/intern/dna_rename_defs.h +++ b/source/blender/makesdna/intern/dna_rename_defs.h @@ -145,6 +145,7 @@ DNA_STRUCT_RENAME_ELEM(MovieTrackingTrack, pat_min, pat_min_legacy) DNA_STRUCT_RENAME_ELEM(MovieTrackingTrack, search_max, search_max_legacy) DNA_STRUCT_RENAME_ELEM(MovieTrackingTrack, search_min, search_min_legacy) DNA_STRUCT_RENAME_ELEM(NodeCryptomatte, num_inputs, inputs_num) +DNA_STRUCT_RENAME_ELEM(NodeGeometryAttributeCapture, data_type, data_type_legacy) DNA_STRUCT_RENAME_ELEM(NodesModifierData, simulation_bake_directory, bake_directory) DNA_STRUCT_RENAME_ELEM(Object, col, color) DNA_STRUCT_RENAME_ELEM(Object, dup_group, instance_collection) diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index ab34750bd89..73d884bc7f2 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -558,6 +558,7 @@ static const EnumPropertyItem node_cryptomatte_layer_name_items[] = { # include "NOD_common.h" # include "NOD_composite.hh" # include "NOD_geo_bake.hh" +# include "NOD_geo_capture_attribute.hh" # include "NOD_geo_index_switch.hh" # include "NOD_geo_menu_switch.hh" # include "NOD_geo_repeat.hh" @@ -575,6 +576,7 @@ static const EnumPropertyItem node_cryptomatte_layer_name_items[] = { # include "WM_api.hh" using blender::nodes::BakeItemsAccessor; +using blender::nodes::CaptureAttributeItemsAccessor; using blender::nodes::IndexSwitchItemsAccessor; using blender::nodes::MenuSwitchItemsAccessor; using blender::nodes::RepeatItemsAccessor; @@ -3519,6 +3521,23 @@ static IndexSwitchItem *rna_NodeIndexSwitchItems_new(ID *id, bNode *node, Main * return new_item; } +static const EnumPropertyItem *rna_NodeGeometryCaptureAttributeItem_data_type_itemf( + bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) +{ + *r_free = true; + /* See #attribute_type_type_with_socket_fn. */ + return itemf_function_check(rna_enum_attribute_type_items, [](const EnumPropertyItem *item) { + return ELEM(item->value, + CD_PROP_FLOAT, + CD_PROP_FLOAT3, + CD_PROP_COLOR, + CD_PROP_BOOL, + CD_PROP_INT32, + CD_PROP_QUATERNION, + CD_PROP_FLOAT4X4); + }); +} + /* ******** Node Socket Types ******** */ static PointerRNA rna_NodeOutputFile_slot_layer_get(CollectionPropertyIterator *iter) @@ -8987,22 +9006,24 @@ static void def_geo_repeat_input(StructRNA *srna) def_common_zone_input(srna); } -static void rna_def_node_item_array_socket_item_common(StructRNA *srna, const char *accessor) +static void rna_def_node_item_array_socket_item_common(StructRNA *srna, + const char *accessor, + const bool add_socket_type) { 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); @@ -9012,13 +9033,15 @@ static void rna_def_node_item_array_socket_item_common(StructRNA *srna, const ch RNA_def_struct_name_property(srna, prop); RNA_def_property_update(prop, NC_NODE | NA_EDITED, item_update_func_ptr); - prop = RNA_def_property(srna, "socket_type", PROP_ENUM, PROP_NONE); - RNA_def_property_enum_items(prop, rna_enum_node_socket_data_type_items); - RNA_def_property_enum_funcs( - prop, nullptr, nullptr, allocator.copy_string(socket_type_itemf).c_str()); - 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, item_update_func_ptr); + if (add_socket_type) { + prop = RNA_def_property(srna, "socket_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_node_socket_data_type_items); + RNA_def_property_enum_funcs( + prop, nullptr, nullptr, allocator.copy_string(socket_type_itemf).c_str()); + 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, item_update_func_ptr); + } prop = RNA_def_property(srna, "color", PROP_FLOAT, PROP_COLOR_GAMMA); RNA_def_property_array(prop, 4); @@ -9037,11 +9060,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()); @@ -9101,7 +9124,7 @@ static void rna_def_simulation_state_item(BlenderRNA *brna) RNA_def_struct_ui_text(srna, "Simulation Item", ""); RNA_def_struct_sdna(srna, "NodeSimulationItem"); - rna_def_node_item_array_socket_item_common(srna, "SimulationItemsAccessor"); + rna_def_node_item_array_socket_item_common(srna, "SimulationItemsAccessor", true); prop = RNA_def_property(srna, "attribute_domain", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items); @@ -9166,7 +9189,7 @@ static void rna_def_repeat_item(BlenderRNA *brna) RNA_def_struct_ui_text(srna, "Repeat Item", ""); RNA_def_struct_sdna(srna, "NodeRepeatItem"); - rna_def_node_item_array_socket_item_common(srna, "RepeatItemsAccessor"); + rna_def_node_item_array_socket_item_common(srna, "RepeatItemsAccessor", true); } static void rna_def_geo_repeat_output_items(BlenderRNA *brna) @@ -9220,6 +9243,73 @@ static void def_geo_repeat_output(StructRNA *srna) RNA_def_property_update(prop, NC_NODE, "rna_Node_update"); } +static void rna_def_geo_capture_attribute_item(BlenderRNA *brna) +{ + StructRNA *srna = RNA_def_struct(brna, "NodeGeometryCaptureAttributeItem", nullptr); + RNA_def_struct_ui_text(srna, "Capture Attribute Item", ""); + RNA_def_struct_sdna(srna, "NodeGeometryAttributeCaptureItem"); + + rna_def_node_item_array_socket_item_common(srna, "CaptureAttributeItemsAccessor", false); + PropertyRNA *prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_attribute_type_items); + RNA_def_property_enum_funcs( + prop, nullptr, nullptr, "rna_NodeGeometryCaptureAttributeItem_data_type_itemf"); + RNA_def_property_ui_text(prop, "Data Type", ""); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update( + prop, NC_NODE | NA_EDITED, "rna_Node_ItemArray_item_update"); +} + +static void rna_def_geo_capture_attribute_items(BlenderRNA *brna) +{ + StructRNA *srna = RNA_def_struct(brna, "NodeGeometryCaptureAttributeItems", nullptr); + RNA_def_struct_ui_text(srna, "Items", "Collection of capture attribute items"); + RNA_def_struct_sdna(srna, "bNode"); + + rna_def_node_item_array_new_with_socket_and_name( + srna, "NodeGeometryCaptureAttributeItem", "CaptureAttributeItemsAccessor"); + rna_def_node_item_array_common_functions( + srna, "NodeGeometryCaptureAttributeItem", "CaptureAttributeItemsAccessor"); +} + +static void rna_def_geo_capture_attribute(StructRNA *srna) +{ + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeGeometryAttributeCapture", "storage"); + + prop = RNA_def_property(srna, "capture_items", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, nullptr, "capture_items", "capture_items_num"); + RNA_def_property_struct_type(prop, "NodeGeometryCaptureAttributeItem"); + RNA_def_property_ui_text(prop, "Items", ""); + RNA_def_property_srna(prop, "NodeGeometryCaptureAttributeItems"); + + 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_flag(prop, PROP_NO_DEG_UPDATE); + 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_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, "domain", PROP_ENUM, PROP_NONE); + RNA_def_property_ui_text(prop, "Domain", "Which domain to store the data in"); + RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items); + RNA_def_property_enum_funcs( + prop, nullptr, nullptr, "rna_GeometryNodeAttributeDomain_attribute_domain_itemf"); + RNA_def_property_update(prop, NC_NODE, "rna_Node_update"); +} + static void rna_def_geo_bake_item(BlenderRNA *brna) { PropertyRNA *prop; @@ -9227,7 +9317,7 @@ static void rna_def_geo_bake_item(BlenderRNA *brna) StructRNA *srna = RNA_def_struct(brna, "NodeGeometryBakeItem", nullptr); RNA_def_struct_ui_text(srna, "Bake Item", ""); - rna_def_node_item_array_socket_item_common(srna, "BakeItemsAccessor"); + rna_def_node_item_array_socket_item_common(srna, "BakeItemsAccessor", true); prop = RNA_def_property(srna, "attribute_domain", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items); @@ -10787,6 +10877,7 @@ void RNA_def_nodetree(BlenderRNA *brna) rna_def_index_switch_item(brna); rna_def_menu_switch_item(brna); rna_def_geo_bake_item(brna); + rna_def_geo_capture_attribute_item(brna); # define DefNode(Category, ID, DefFunc, EnumName, StructName, UIName, UIDesc) \ { \ @@ -10842,6 +10933,7 @@ void RNA_def_nodetree(BlenderRNA *brna) rna_def_geo_index_switch_items(brna); rna_def_geo_menu_switch_items(brna); rna_def_bake_items(brna); + rna_def_geo_capture_attribute_items(brna); rna_def_node_instance_hash(brna); } diff --git a/source/blender/nodes/NOD_socket_items.hh b/source/blender/nodes/NOD_socket_items.hh index b97f269ea2f..149bd525717 100644 --- a/source/blender/nodes/NOD_socket_items.hh +++ b/source/blender/nodes/NOD_socket_items.hh @@ -194,6 +194,23 @@ template inline typename Accessor::ItemT *add_item(bNode &nod return &new_item; } +template +inline std::string get_socket_identifier(const typename Accessor::ItemT &item, + const eNodeSocketInOut in_out) +{ + if constexpr (Accessor::has_single_identifier_str) { + return Accessor::socket_identifier_for_item(item); + } + else { + if (in_out == SOCK_IN) { + return Accessor::input_socket_identifier_for_item(item); + } + else { + return Accessor::output_socket_identifier_for_item(item); + } + } +} + /** * Check if the link connects to the `extend_socket`. If yes, create a new item for the linked * socket, update the node and then change the link to point to the new socket. @@ -238,13 +255,14 @@ template } update_node_declaration_and_sockets(ntree, extend_node); - const std::string item_identifier = Accessor::socket_identifier_for_item(*item); if (extend_socket.is_input()) { - bNodeSocket *new_socket = bke::nodeFindSocket(&extend_node, SOCK_IN, item_identifier); + const std::string item_identifier = get_socket_identifier(*item, SOCK_IN); + bNodeSocket *new_socket = bke::nodeFindSocket(&extend_node, SOCK_IN, item_identifier.c_str()); link.tosock = new_socket; } else { - bNodeSocket *new_socket = bke::nodeFindSocket(&extend_node, SOCK_OUT, item_identifier); + const std::string item_identifier = get_socket_identifier(*item, SOCK_OUT); + bNodeSocket *new_socket = bke::nodeFindSocket(&extend_node, SOCK_OUT, item_identifier.c_str()); link.fromsock = new_socket; } return true; diff --git a/source/blender/nodes/NOD_socket_items_ops.hh b/source/blender/nodes/NOD_socket_items_ops.hh index cd1862b87d2..70ee5bc0b5e 100644 --- a/source/blender/nodes/NOD_socket_items_ops.hh +++ b/source/blender/nodes/NOD_socket_items_ops.hh @@ -147,7 +147,9 @@ inline void add_item(wmOperatorType *ot, if constexpr (Accessor::has_type && Accessor::has_name) { socket_items::add_item_with_socket_type_and_name( node, - active_item ? eNodeSocketDatatype(active_item->socket_type) : SOCK_GEOMETRY, + active_item ? + Accessor::get_socket_type(*active_item) : + (Accessor::supports_socket_type(SOCK_GEOMETRY) ? SOCK_GEOMETRY : SOCK_FLOAT), /* Empty name so it is based on the type. */ active_item ? active_item->name : ""); } diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index afb7b9946e2..394f630e271 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -310,7 +310,7 @@ DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_STATISTIC, 0, "ATTRIBUTE_STATISTIC",Att DefNode(GeometryNode, GEO_NODE_BAKE, rna_def_geo_bake, "BAKE", Bake, "Bake", "Cache the incoming data so that it can be used without recomputation") DefNode(GeometryNode, GEO_NODE_BLUR_ATTRIBUTE, 0, "BLUR_ATTRIBUTE", BlurAttribute, "Blur Attribute", "Mix attribute values of neighboring elements") DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "Calculate the limits of a geometry's positions and generate a box mesh with those dimensions") -DefNode(GeometryNode, GEO_NODE_CAPTURE_ATTRIBUTE, 0, "CAPTURE_ATTRIBUTE", CaptureAttribute, "Capture Attribute", "Store the result of a field on a geometry and output the data as a node socket. Allows remembering or interpolating data as the geometry changes, such as positions before deformation") +DefNode(GeometryNode, GEO_NODE_CAPTURE_ATTRIBUTE, rna_def_geo_capture_attribute, "CAPTURE_ATTRIBUTE", CaptureAttribute, "Capture Attribute", "Store the result of a field on a geometry and output the data as a node socket. Allows remembering or interpolating data as the geometry changes, such as positions before deformation") DefNode(GeometryNode, GEO_NODE_COLLECTION_INFO, 0, "COLLECTION_INFO", CollectionInfo, "Collection Info", "Retrieve geometry instances from a collection") DefNode(GeometryNode, GEO_NODE_CONVEX_HULL, 0, "CONVEX_HULL", ConvexHull, "Convex Hull", "Create a mesh that encloses all points in the input geometry with the smallest number of points") DefNode(GeometryNode, GEO_NODE_CURVE_ENDPOINT_SELECTION, 0, "CURVE_ENDPOINT_SELECTION", CurveEndpointSelection, "Endpoint Selection", "Provide a selection for an arbitrary number of endpoints in each spline") diff --git a/source/blender/nodes/geometry/include/NOD_geo_bake.hh b/source/blender/nodes/geometry/include/NOD_geo_bake.hh index 76237b17401..817ede2fe92 100644 --- a/source/blender/nodes/geometry/include/NOD_geo_bake.hh +++ b/source/blender/nodes/geometry/include/NOD_geo_bake.hh @@ -33,6 +33,7 @@ struct BakeItemsAccessor { static constexpr const char *node_idname = "GeometryNodeBake"; static constexpr bool has_type = true; static constexpr bool has_name = true; + static constexpr bool has_single_identifier_str = true; static socket_items::SocketItemsRef get_items_from_node(bNode &node) { diff --git a/source/blender/nodes/geometry/include/NOD_geo_capture_attribute.hh b/source/blender/nodes/geometry/include/NOD_geo_capture_attribute.hh new file mode 100644 index 00000000000..ddaf1ee71c4 --- /dev/null +++ b/source/blender/nodes/geometry/include/NOD_geo_capture_attribute.hh @@ -0,0 +1,92 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "DNA_node_types.h" + +#include "NOD_socket_items.hh" + +#include "BKE_customdata.hh" + +namespace blender::nodes { + +struct CaptureAttributeItemsAccessor { + using ItemT = NodeGeometryAttributeCaptureItem; + static StructRNA *item_srna; + static int node_type; + static constexpr const char *node_idname = "GeometryNodeCaptureAttribute"; + static constexpr bool has_type = true; + static constexpr bool has_name = true; + static constexpr bool has_single_identifier_str = false; + + static socket_items::SocketItemsRef get_items_from_node( + bNode &node) + { + auto *storage = static_cast(node.storage); + return {&storage->capture_items, &storage->capture_items_num, &storage->active_index}; + } + + static void copy_item(const NodeGeometryAttributeCaptureItem &src, + NodeGeometryAttributeCaptureItem &dst) + { + dst = src; + dst.name = BLI_strdup_null(dst.name); + } + + static void destruct_item(NodeGeometryAttributeCaptureItem *item) + { + MEM_SAFE_FREE(item->name); + } + + static void blend_write(BlendWriter *writer, const bNode &node); + static void blend_read_data(BlendDataReader *reader, bNode &node); + + static eNodeSocketDatatype get_socket_type(const NodeGeometryAttributeCaptureItem &item) + { + return *bke::custom_data_type_to_socket_type(eCustomDataType(item.data_type)); + } + + static char **get_name(NodeGeometryAttributeCaptureItem &item) + { + return &item.name; + } + + static bool supports_socket_type(const eNodeSocketDatatype socket_type) + { + return bke::socket_type_to_custom_data_type(socket_type).has_value(); + } + + static void init_with_socket_type_and_name(bNode &node, + NodeGeometryAttributeCaptureItem &item, + const eNodeSocketDatatype socket_type, + const char *name) + { + auto *storage = static_cast(node.storage); + item.data_type = *bke::socket_type_to_custom_data_type(socket_type); + item.identifier = storage->next_identifier++; + socket_items::set_item_name_and_make_unique(node, item, name); + } + + static std::string input_socket_identifier_for_item(const NodeGeometryAttributeCaptureItem &item) + { + if (item.identifier == 0) { + /* This special case exists for compatibility. */ + return "Value"; + } + return "Value_" + std::to_string(item.identifier); + } + + static std::string output_socket_identifier_for_item( + const NodeGeometryAttributeCaptureItem &item) + { + if (item.identifier == 0) { + /* This special case exists for compatibility. */ + return "Attribute"; + } + return "Attribute_" + std::to_string(item.identifier); + } +}; + +} // namespace blender::nodes diff --git a/source/blender/nodes/geometry/include/NOD_geo_index_switch.hh b/source/blender/nodes/geometry/include/NOD_geo_index_switch.hh index bf7a014e8d7..0245a80a8b2 100644 --- a/source/blender/nodes/geometry/include/NOD_geo_index_switch.hh +++ b/source/blender/nodes/geometry/include/NOD_geo_index_switch.hh @@ -21,6 +21,7 @@ struct IndexSwitchItemsAccessor { static constexpr const char *node_idname = "GeometryNodeIndexSwitch"; static constexpr bool has_type = false; static constexpr bool has_name = false; + static constexpr bool has_single_identifier_str = true; static socket_items::SocketItemsRef get_items_from_node(bNode &node) { diff --git a/source/blender/nodes/geometry/include/NOD_geo_menu_switch.hh b/source/blender/nodes/geometry/include/NOD_geo_menu_switch.hh index d8c63074f01..11cc402b33b 100644 --- a/source/blender/nodes/geometry/include/NOD_geo_menu_switch.hh +++ b/source/blender/nodes/geometry/include/NOD_geo_menu_switch.hh @@ -22,6 +22,7 @@ struct MenuSwitchItemsAccessor { static constexpr const char *node_idname = "GeometryNodeMenuSwitch"; static constexpr bool has_type = false; static constexpr bool has_name = true; + static constexpr bool has_single_identifier_str = true; static socket_items::SocketItemsRef get_items_from_node(bNode &node) { diff --git a/source/blender/nodes/geometry/include/NOD_geo_repeat.hh b/source/blender/nodes/geometry/include/NOD_geo_repeat.hh index f633275ce62..a8fb5ec66b5 100644 --- a/source/blender/nodes/geometry/include/NOD_geo_repeat.hh +++ b/source/blender/nodes/geometry/include/NOD_geo_repeat.hh @@ -22,6 +22,7 @@ struct RepeatItemsAccessor { static constexpr const char *node_idname = "GeometryNodeRepeatOutput"; static constexpr bool has_type = true; static constexpr bool has_name = true; + static constexpr bool has_single_identifier_str = true; static socket_items::SocketItemsRef get_items_from_node(bNode &node) { diff --git a/source/blender/nodes/geometry/include/NOD_geo_simulation.hh b/source/blender/nodes/geometry/include/NOD_geo_simulation.hh index 661f2075919..9d0d3164ebc 100644 --- a/source/blender/nodes/geometry/include/NOD_geo_simulation.hh +++ b/source/blender/nodes/geometry/include/NOD_geo_simulation.hh @@ -21,6 +21,7 @@ struct SimulationItemsAccessor { static constexpr const char *node_idname = "GeometryNodeSimulationOutput"; static constexpr bool has_type = true; static constexpr bool has_name = true; + static constexpr bool has_single_identifier_str = true; static socket_items::SocketItemsRef get_items_from_node(bNode &node) { diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc index 9048a9fb4df..5771d67fd14 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc @@ -7,10 +7,16 @@ #include "UI_interface.hh" #include "UI_resources.hh" +#include "NOD_geo_capture_attribute.hh" +#include "NOD_socket_items_ops.hh" #include "NOD_socket_search_link.hh" #include "RNA_enum_types.hh" +#include "BLO_read_write.hh" + +#include "BKE_screen.hh" + #include "node_geometry_util.hh" namespace blender::nodes::node_geo_attribute_capture_cc { @@ -19,65 +25,150 @@ NODE_STORAGE_FUNCS(NodeGeometryAttributeCapture) static void node_declare(NodeDeclarationBuilder &b) { + const bNodeTree *tree = b.tree_or_null(); const bNode *node = b.node_or_null(); + b.use_custom_socket_order(); + b.allow_any_socket_order(); b.add_input("Geometry"); + b.add_output("Geometry").propagate_all().align_with_previous(); if (node != nullptr) { - const eCustomDataType data_type = eCustomDataType(node_storage(*node).data_type); - b.add_input(data_type, "Value").field_on_all(); - } - - b.add_output("Geometry").propagate_all(); - if (node != nullptr) { - const eCustomDataType data_type = eCustomDataType(node_storage(*node).data_type); - b.add_output(data_type, "Attribute").field_on_all(); + const NodeGeometryAttributeCapture &storage = node_storage(*node); + for (const NodeGeometryAttributeCaptureItem &item : + Span(storage.capture_items, storage.capture_items_num)) + { + const eCustomDataType data_type = eCustomDataType(item.data_type); + const std::string input_identifier = + CaptureAttributeItemsAccessor::input_socket_identifier_for_item(item); + const std::string output_identifier = + CaptureAttributeItemsAccessor::output_socket_identifier_for_item(item); + b.add_input(data_type, item.name, input_identifier) + .field_on_all() + .socket_name_ptr(&tree->id, CaptureAttributeItemsAccessor::item_srna, &item, "name"); + b.add_output(data_type, item.name, output_identifier).field_on_all().align_with_previous(); + } } + b.add_input("", "__extend__"); + b.add_output("", "__extend__").align_with_previous(); } static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); - uiItemR(layout, ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE); uiItemR(layout, ptr, "domain", UI_ITEM_NONE, "", ICON_NONE); } static void node_init(bNodeTree * /*tree*/, bNode *node) { NodeGeometryAttributeCapture *data = MEM_cnew(__func__); - data->data_type = CD_PROP_FLOAT; data->domain = int8_t(AttrDomain::Point); - node->storage = data; } -static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) +static void draw_item(uiList * /*ui_list*/, + const bContext *C, + uiLayout *layout, + PointerRNA * /*idataptr*/, + PointerRNA *itemptr, + int /*icon*/, + PointerRNA * /*active_dataptr*/, + const char * /*active_propname*/, + int /*index*/, + int /*flt_flag*/) { - const NodeDeclaration &declaration = *params.node_type().static_declaration; - search_link_ops_for_declarations(params, declaration.inputs); - search_link_ops_for_declarations(params, declaration.outputs); + uiLayout *row = uiLayoutRow(layout, true); + float4 color; + RNA_float_get_array(itemptr, "color", color); + uiTemplateNodeSocket(row, const_cast(C), color); + uiLayoutSetEmboss(row, UI_EMBOSS_NONE); + uiItemR(row, itemptr, "name", UI_ITEM_NONE, "", ICON_NONE); +} - const blender::bke::bNodeType &node_type = params.node_type(); - const std::optional type = bke::socket_type_to_custom_data_type( - eNodeSocketDatatype(params.other_socket().type)); - if (type && *type != CD_PROP_STRING) { - if (params.in_out() == SOCK_OUT) { - params.add_item(IFACE_("Attribute"), [node_type, type](LinkSearchOpParams ¶ms) { - bNode &node = params.add_node(node_type); - node_storage(node).data_type = *type; - params.update_and_connect_available_socket(node, "Attribute"); - }); - } - else { - params.add_item(IFACE_("Value"), [node_type, type](LinkSearchOpParams ¶ms) { - bNode &node = params.add_node(node_type); - node_storage(node).data_type = *type; - params.update_and_connect_available_socket(node, "Value"); - }); +static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *ptr) +{ + static const uiListType *items_list = []() { + uiListType *list = MEM_new(__func__); + STRNCPY(list->idname, "NODE_UL_capture_items_list"); + list->draw_item = draw_item; + WM_uilisttype_add(list); + return list; + }(); + + uiItemR(layout, ptr, "domain", UI_ITEM_NONE, "", ICON_NONE); + + if (uiLayout *panel = uiLayoutPanel( + C, layout, "capture_attribute_items", false, TIP_("Capture Items"))) + { + uiLayout *row = uiLayoutRow(panel, false); + uiTemplateList(row, + C, + items_list->idname, + "", + ptr, + "capture_items", + ptr, + "active_index", + nullptr, + 3, + 5, + UILST_LAYOUT_DEFAULT, + 0, + UI_TEMPLATE_LIST_FLAG_NONE); + { + uiLayout *ops_col = uiLayoutColumn(row, false); + { + uiLayout *add_remove_col = uiLayoutColumn(ops_col, true); + uiItemO(add_remove_col, "", ICON_ADD, "node.capture_attribute_item_add"); + uiItemO(add_remove_col, "", ICON_REMOVE, "node.capture_attribute_item_remove"); + } + { + uiLayout *up_down_col = uiLayoutColumn(ops_col, true); + uiItemEnumO( + up_down_col, "node.capture_attribute_item_move", "", ICON_TRIA_UP, "direction", 0); + uiItemEnumO( + up_down_col, "node.capture_attribute_item_move", "", ICON_TRIA_DOWN, "direction", 1); + } + bNode &node = *static_cast(ptr->data); + auto &storage = node_storage(node); + if (storage.active_index >= 0 && storage.active_index < storage.capture_items_num) { + NodeGeometryAttributeCaptureItem &active_item = + storage.capture_items[storage.active_index]; + PointerRNA item_ptr = RNA_pointer_create( + ptr->owner_id, CaptureAttributeItemsAccessor::item_srna, &active_item); + uiLayoutSetPropSep(panel, true); + uiLayoutSetPropDecorate(panel, false); + uiItemR(panel, &item_ptr, "data_type", UI_ITEM_NONE, nullptr, ICON_NONE); + } } } } +static void NODE_OT_capture_attribute_item_add(wmOperatorType *ot) +{ + socket_items::ops::add_item( + ot, "Add Capture Attribute Item", __func__, "Add capture attribute item"); +} + +static void NODE_OT_capture_attribute_item_remove(wmOperatorType *ot) +{ + socket_items::ops::remove_active_item( + ot, "Remove Capture Attribute Item", __func__, "Remove active capture attribute item"); +} + +static void NODE_OT_capture_attribute_item_move(wmOperatorType *ot) +{ + socket_items::ops::move_active_item( + ot, "Move Capture Attribute Item", __func__, "Move active capture attribute item"); +} + +static void node_operators() +{ + WM_operatortype_append(NODE_OT_capture_attribute_item_add); + WM_operatortype_append(NODE_OT_capture_attribute_item_remove); + WM_operatortype_append(NODE_OT_capture_attribute_item_move); +} + static void clean_unused_attributes(const AnonymousAttributePropagationInfo &propagation_info, const Set &skip, GeometryComponent &component) @@ -122,22 +213,45 @@ static void node_geo_exec(GeoNodeExecParams params) const NodeGeometryAttributeCapture &storage = node_storage(params.node()); const AttrDomain domain = AttrDomain(storage.domain); - AnonymousAttributeIDPtr attribute_id = params.get_output_anonymous_attribute_id_if_needed( - "Attribute"); - if (!attribute_id) { + Vector used_items; + Vector fields; + Vector attribute_id_ptrs; + Set used_attribute_ids_set; + for (const NodeGeometryAttributeCaptureItem &item : + Span{storage.capture_items, storage.capture_items_num}) + { + const std::string input_identifier = + CaptureAttributeItemsAccessor::input_socket_identifier_for_item(item); + const std::string output_identifier = + CaptureAttributeItemsAccessor::output_socket_identifier_for_item(item); + AnonymousAttributeIDPtr attribute_id = params.get_output_anonymous_attribute_id_if_needed( + output_identifier); + if (!attribute_id) { + continue; + } + used_attribute_ids_set.add(*attribute_id); + fields.append(params.extract_input(input_identifier)); + attribute_id_ptrs.append(std::move(attribute_id)); + used_items.append(&item); + } + + if (fields.is_empty()) { params.set_output("Geometry", geometry_set); params.set_default_remaining_outputs(); return; } - const GField field = params.extract_input("Value"); + Array attribute_ids(attribute_id_ptrs.size()); + for (const int i : attribute_id_ptrs.index_range()) { + attribute_ids[i] = *attribute_id_ptrs[i]; + } const auto capture_on = [&](GeometryComponent &component) { - bke::try_capture_field_on_geometry(component, *attribute_id, domain, field); + bke::try_capture_fields_on_geometry(component, attribute_ids, domain, fields); /* Changing of the anonymous attributes may require removing attributes that are no longer * needed. */ clean_unused_attributes( - params.get_output_propagation_info("Geometry"), {*attribute_id}, component); + params.get_output_propagation_info("Geometry"), used_attribute_ids_set, component); }; /* Run on the instances component separately to only affect the top level of instances. */ @@ -164,26 +278,26 @@ static void node_geo_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -static void node_rna(StructRNA *srna) +static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link) { - RNA_def_node_enum(srna, - "data_type", - "Data Type", - "Type of data stored in attribute", - rna_enum_attribute_type_items, - NOD_storage_enum_accessors(data_type), - CD_PROP_FLOAT, - enums::attribute_type_type_with_socket_fn); + return socket_items::try_add_item_via_any_extend_socket( + *ntree, *node, *node, *link); +} - RNA_def_node_enum(srna, - "domain", - "Domain", - "Which domain to store the data in", - rna_enum_attribute_domain_items, - NOD_storage_enum_accessors(domain), - int8_t(AttrDomain::Point), - enums::domain_experimental_grease_pencil_version3_fn, - true); +static void node_free_storage(bNode *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 NodeGeometryAttributeCapture &src_storage = node_storage(*src_node); + NodeGeometryAttributeCapture *dst_storage = MEM_new(__func__, + src_storage); + dst_node->storage = dst_storage; + + socket_items::copy_array(*src_node, *dst_node); } static void node_register() @@ -192,19 +306,48 @@ static void node_register() geo_node_type_base( &ntype, GEO_NODE_CAPTURE_ATTRIBUTE, "Capture Attribute", NODE_CLASS_ATTRIBUTE); - blender::bke::node_type_storage(&ntype, - "NodeGeometryAttributeCapture", - node_free_standard_storage, - node_copy_standard_storage); + blender::bke::node_type_storage( + &ntype, "NodeGeometryAttributeCapture", node_free_storage, node_copy_storage); ntype.initfunc = node_init; ntype.declare = node_declare; ntype.geometry_node_execute = node_geo_exec; + ntype.insert_link = node_insert_link; ntype.draw_buttons = node_layout; - ntype.gather_link_search_ops = node_gather_link_searches; + ntype.draw_buttons_ex = node_layout_ex; + ntype.register_operators = node_operators; blender::bke::nodeRegisterType(&ntype); - - node_rna(ntype.rna_ext.srna); } NOD_REGISTER_NODE(node_register) } // namespace blender::nodes::node_geo_attribute_capture_cc + +namespace blender::nodes { + +StructRNA *CaptureAttributeItemsAccessor::item_srna = &RNA_NodeGeometryCaptureAttributeItem; +int CaptureAttributeItemsAccessor::node_type = GEO_NODE_CAPTURE_ATTRIBUTE; + +void CaptureAttributeItemsAccessor::blend_write(BlendWriter *writer, const bNode &node) +{ + const auto &storage = *static_cast(node.storage); + BLO_write_struct_array( + writer, NodeGeometryAttributeCaptureItem, storage.capture_items_num, storage.capture_items); + for (const NodeGeometryAttributeCaptureItem &item : + Span(storage.capture_items, storage.capture_items_num)) + { + BLO_write_string(writer, item.name); + } +} + +void CaptureAttributeItemsAccessor::blend_read_data(BlendDataReader *reader, bNode &node) +{ + auto &storage = *static_cast(node.storage); + BLO_read_struct_array( + reader, NodeGeometryAttributeCaptureItem, storage.capture_items_num, &storage.capture_items); + for (const NodeGeometryAttributeCaptureItem &item : + Span(storage.capture_items, storage.capture_items_num)) + { + BLO_read_string(reader, &item.name); + } +} + +} // namespace blender::nodes