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