From 1ec9ac201652be6031379e39e98e7dd9dc4a2375 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 8 Apr 2021 12:19:09 -0500 Subject: Geometry Nodes: Support instances in attribute search Previously only attributes of "real" geometry were displayed in attribute search. This commit adds code to look through attributes on instances and add those to the search drop-down too. This required implementing the same sort of recursive traversal as the realize instances code. The situation is a bit different though, this can return early and doesn't need to keep track of transforms. I added a limit so that it doesn't look through the attributes of too many instanced geometry sets. I think this is important, since this isn't a trivial operation and it could potentially happen for every node in a large node tree. Currently the limit is set at 8 geometry sets, which I expect will be enough, since the set of attributes is mostly not very unique anyway. Fixes T86282 Diffrential Revision: https://developer.blender.org/D10919 --- source/blender/blenkernel/BKE_geometry_set.hh | 2 +- .../blenkernel/BKE_geometry_set_instances.hh | 4 + .../blender/blenkernel/intern/attribute_access.cc | 14 ++- .../blenkernel/intern/geometry_set_instances.cc | 116 +++++++++++++++++++++ source/blender/modifiers/intern/MOD_nodes.cc | 28 ++--- 5 files changed, 145 insertions(+), 19 deletions(-) (limited to 'source/blender') diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index 2ce8ce5749f..d94b2e7902b 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -186,7 +186,7 @@ class GeometryComponent { const CustomDataType data_type); blender::Set attribute_names() const; - void attribute_foreach(const AttributeForeachCallback callback) const; + bool attribute_foreach(const AttributeForeachCallback callback) const; virtual bool is_empty() const; diff --git a/source/blender/blenkernel/BKE_geometry_set_instances.hh b/source/blender/blenkernel/BKE_geometry_set_instances.hh index c0d9c3f74ec..25876296a47 100644 --- a/source/blender/blenkernel/BKE_geometry_set_instances.hh +++ b/source/blender/blenkernel/BKE_geometry_set_instances.hh @@ -39,6 +39,10 @@ struct GeometryInstanceGroup { Vector transforms; }; +void geometry_set_instances_attribute_foreach(const GeometrySet &geometry_set, + const AttributeForeachCallback callback, + const int limit); + void geometry_set_gather_instances(const GeometrySet &geometry_set, Vector &r_instance_groups); diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index 52f89ca302b..5bd3b990a63 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -822,12 +822,16 @@ Set GeometryComponent::attribute_names() const return attributes; } -void GeometryComponent::attribute_foreach(const AttributeForeachCallback callback) const +/** + * \return False if the callback explicitly returned false at any point, otherwise true, + * meaning the callback made it all the way through. + */ +bool GeometryComponent::attribute_foreach(const AttributeForeachCallback callback) const { using namespace blender::bke; const ComponentAttributeProviders *providers = this->get_attribute_providers(); if (providers == nullptr) { - return; + return true; } /* Keep track handled attribute names to make sure that we do not return the same name twice. */ @@ -838,7 +842,7 @@ void GeometryComponent::attribute_foreach(const AttributeForeachCallback callbac if (provider->exists(*this)) { AttributeMetaData meta_data{provider->domain(), provider->data_type()}; if (!callback(provider->name(), meta_data)) { - return; + return false; } handled_attribute_names.add_new(provider->name()); } @@ -852,9 +856,11 @@ void GeometryComponent::attribute_foreach(const AttributeForeachCallback callbac return true; }); if (!continue_loop) { - return; + return false; } } + + return true; } bool GeometryComponent::attribute_exists(const blender::StringRef attribute_name) const diff --git a/source/blender/blenkernel/intern/geometry_set_instances.cc b/source/blender/blenkernel/intern/geometry_set_instances.cc index 1a45eac8cab..baeed4fc3bc 100644 --- a/source/blender/blenkernel/intern/geometry_set_instances.cc +++ b/source/blender/blenkernel/intern/geometry_set_instances.cc @@ -162,6 +162,122 @@ void geometry_set_gather_instances(const GeometrySet &geometry_set, geometry_set_collect_recursive(geometry_set, unit_transform, r_instance_groups); } +static bool collection_instance_attribute_foreach(const Collection &collection, + const AttributeForeachCallback callback, + const int limit, + int &count); + +static bool instances_attribute_foreach_recursive(const GeometrySet &geometry_set, + const AttributeForeachCallback callback, + const int limit, + int &count); + +static bool object_instance_attribute_foreach(const Object &object, + const AttributeForeachCallback callback, + const int limit, + int &count) +{ + GeometrySet instance_geometry_set = object_get_geometry_set_for_read(object); + if (!instances_attribute_foreach_recursive(instance_geometry_set, callback, limit, count)) { + return false; + } + + if (object.type == OB_EMPTY) { + const Collection *collection_instance = object.instance_collection; + if (collection_instance != nullptr) { + if (!collection_instance_attribute_foreach(*collection_instance, callback, limit, count)) { + return false; + } + } + } + return true; +} + +static bool collection_instance_attribute_foreach(const Collection &collection, + const AttributeForeachCallback callback, + const int limit, + int &count) +{ + LISTBASE_FOREACH (const CollectionObject *, collection_object, &collection.gobject) { + BLI_assert(collection_object->ob != nullptr); + const Object &object = *collection_object->ob; + if (!object_instance_attribute_foreach(object, callback, limit, count)) { + return false; + } + } + LISTBASE_FOREACH (const CollectionChild *, collection_child, &collection.children) { + BLI_assert(collection_child->collection != nullptr); + const Collection &collection = *collection_child->collection; + if (!collection_instance_attribute_foreach(collection, callback, limit, count)) { + return false; + } + } + return true; +} + +/** + * \return True if the recursive iteration should continue, false if the limit is reached or the + * callback has returned false indicating it should stop. + */ +static bool instances_attribute_foreach_recursive(const GeometrySet &geometry_set, + const AttributeForeachCallback callback, + const int limit, + int &count) +{ + for (const GeometryComponent *component : geometry_set.get_components_for_read()) { + if (!component->attribute_foreach(callback)) { + return false; + } + } + + /* Now that this this geometry set is visited, increase the count and check with the limit. */ + if (limit > 0 && count++ > limit) { + return false; + } + + const InstancesComponent *instances_component = + geometry_set.get_component_for_read(); + if (instances_component == nullptr) { + return true; + } + + for (const InstancedData &data : instances_component->instanced_data()) { + if (data.type == INSTANCE_DATA_TYPE_OBJECT) { + BLI_assert(data.data.object != nullptr); + const Object &object = *data.data.object; + if (!object_instance_attribute_foreach(object, callback, limit, count)) { + return false; + } + } + else if (data.type == INSTANCE_DATA_TYPE_COLLECTION) { + BLI_assert(data.data.collection != nullptr); + const Collection &collection = *data.data.collection; + if (!collection_instance_attribute_foreach(collection, callback, limit, count)) { + return false; + } + } + } + + return true; +} + +/** + * Call the callback on all of this geometry set's components, including geometry sets from + * instances and recursive instances. This is necessary to access available attributes without + * making all of the set's geometry real. + * + * \param limit: The total number of geometry sets to visit before returning early. This is used + * to avoid looking through too many geometry sets recursively, as an explicit tradeoff in favor + * of performance at the cost of visiting every unique attribute. + */ +void geometry_set_instances_attribute_foreach(const GeometrySet &geometry_set, + const AttributeForeachCallback callback, + const int limit) +{ + int count = 0; + instances_attribute_foreach_recursive(geometry_set, callback, limit, count); +} + void geometry_set_gather_instances_attribute_info(Span set_groups, Span component_types, const Set &ignored_attributes, diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index c7d822e5418..7a2f8640202 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -47,6 +47,7 @@ #include "DNA_windowmanager_types.h" #include "BKE_customdata.h" +#include "BKE_geometry_set_instances.hh" #include "BKE_global.h" #include "BKE_idprop.h" #include "BKE_lib_query.h" @@ -489,20 +490,19 @@ class GeometryNodesEvaluator { const NodeTreeEvaluationContext context(*self_object_, *modifier_); const GeometrySet &geometry_set = params.get_input(socket_ref->identifier()); - const Vector components = geometry_set.get_components_for_read(); - - for (const GeometryComponent *component : components) { - component->attribute_foreach( - [&](StringRefNull attribute_name, const AttributeMetaData &meta_data) { - BKE_nodetree_attribute_hint_add(*btree_original, - context, - *node->bnode(), - attribute_name, - meta_data.domain, - meta_data.data_type); - return true; - }); - } + + blender::bke::geometry_set_instances_attribute_foreach( + geometry_set, + [&](StringRefNull attribute_name, const AttributeMetaData &meta_data) { + BKE_nodetree_attribute_hint_add(*btree_original, + context, + *node->bnode(), + attribute_name, + meta_data.domain, + meta_data.data_type); + return true; + }, + 8); } } -- cgit v1.2.3