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 <hans@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/121665
This commit is contained in:
Jacques Lucke
2024-05-31 16:23:31 +02:00
parent d568abea62
commit 28cef56ad2
18 changed files with 650 additions and 173 deletions

View File

@@ -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<bool> &selection,
const fn::GField &field);
bool try_capture_fields_on_geometry(MutableAttributeAccessor attributes,
const fn::FieldContext &field_context,
Span<AttributeIDRef> attribute_ids,
AttrDomain domain,
const fn::Field<bool> &selection,
Span<fn::GField> 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<bool> &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<bool> &selection,
const fn::GField &field);
bool try_capture_fields_on_geometry(GeometryComponent &component,
Span<AttributeIDRef> attribute_ids,
AttrDomain domain,
Span<fn::GField> 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<AttributeIDRef> attribute_ids,
AttrDomain domain,
const fn::Field<bool> &selection,
Span<fn::GField> fields);
inline bool try_capture_field_on_geometry(GeometryComponent &component,
const AttributeIDRef &attribute_id,
AttrDomain domain,
const fn::Field<bool> &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

View File

@@ -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<bool> &selection,
const fn::GField &field)
bool try_capture_fields_on_geometry(MutableAttributeAccessor attributes,
const fn::FieldContext &field_context,
const Span<AttributeIDRef> attribute_ids,
const AttrDomain domain,
const fn::Field<bool> &selection,
const Span<fn::GField> 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<StoreResult> results_to_store;
struct AddResult {
int input_index;
int evaluator_index;
void *buffer;
};
Vector<AddResult> 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<bool> &selection,
const fn::GField &field)
bool try_capture_fields_on_geometry(GeometryComponent &component,
const Span<AttributeIDRef> attribute_ids,
const AttrDomain domain,
const fn::Field<bool> &selection,
const Span<fn::GField> 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<AttributeIDRef> attribute_ids,
const AttrDomain domain,
const Span<fn::GField> fields)
{
const fn::Field<bool> selection = fn::make_constant_field<bool>(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<AttrDomain> try_detect_field_domain(const GeometryComponent &component,

View File

@@ -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<NodeGeometryAttributeCapture *>(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;

View File

@@ -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<const NodeGeometryAttributeCapture *>(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<NodeGeometryAttributeCapture>(__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");

View File

@@ -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<NodeGeometryAttributeCapture *>(
node->storage);
if (storage->next_identifier > 0) {
continue;
}
storage->capture_items_num = 1;
storage->capture_items = MEM_cnew_array<NodeGeometryAttributeCaptureItem>(
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.

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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<CaptureAttributeItemsAccessor>");
}
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<CaptureAttributeItemsAccessor>",
"rna_Node_ItemArray_active_set<CaptureAttributeItemsAccessor>",
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);
}

View File

@@ -194,6 +194,23 @@ template<typename Accessor> inline typename Accessor::ItemT *add_item(bNode &nod
return &new_item;
}
template<typename Accessor>
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<typename Accessor>
}
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<Accessor>(*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<Accessor>(*item, SOCK_OUT);
bNodeSocket *new_socket = bke::nodeFindSocket(&extend_node, SOCK_OUT, item_identifier.c_str());
link.fromsock = new_socket;
}
return true;

View File

@@ -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<Accessor>(
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 : "");
}

View File

@@ -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")

View File

@@ -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<NodeGeometryBakeItem> get_items_from_node(bNode &node)
{

View File

@@ -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<NodeGeometryAttributeCaptureItem> get_items_from_node(
bNode &node)
{
auto *storage = static_cast<NodeGeometryAttributeCapture *>(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<NodeGeometryAttributeCapture *>(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<CaptureAttributeItemsAccessor>(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

View File

@@ -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<IndexSwitchItem> get_items_from_node(bNode &node)
{

View File

@@ -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<NodeEnumItem> get_items_from_node(bNode &node)
{

View File

@@ -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<NodeRepeatItem> get_items_from_node(bNode &node)
{

View File

@@ -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<NodeSimulationItem> get_items_from_node(bNode &node)
{

View File

@@ -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<decl::Geometry>("Geometry");
b.add_output<decl::Geometry>("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<decl::Geometry>("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<decl::Extend>("", "__extend__");
b.add_output<decl::Extend>("", "__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<NodeGeometryAttributeCapture>(__func__);
data->data_type = CD_PROP_FLOAT;
data->domain = int8_t(AttrDomain::Point);
node->storage = data;
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
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<bContext *>(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<eCustomDataType> 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 &params) {
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 &params) {
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<uiListType>(__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<bNode *>(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<CaptureAttributeItemsAccessor>(
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<CaptureAttributeItemsAccessor>(
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<CaptureAttributeItemsAccessor>(
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<AttributeIDRef> &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<const NodeGeometryAttributeCaptureItem *> used_items;
Vector<GField> fields;
Vector<AnonymousAttributeIDPtr> attribute_id_ptrs;
Set<AttributeIDRef> 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<GField>(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<GField>("Value");
Array<AttributeIDRef> 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<CaptureAttributeItemsAccessor>(
*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<CaptureAttributeItemsAccessor>(*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<NodeGeometryAttributeCapture>(__func__,
src_storage);
dst_node->storage = dst_storage;
socket_items::copy_array<CaptureAttributeItemsAccessor>(*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<const NodeGeometryAttributeCapture *>(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<NodeGeometryAttributeCapture *>(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