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:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 : "");
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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 ¶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<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 ¶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<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
|
||||
|
||||
Reference in New Issue
Block a user