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
This commit is contained in:
Hans Goudey 2021-04-08 12:19:09 -05:00
parent fd414b4906
commit 1ec9ac2016
Notes: blender-bot 2023-02-14 02:08:37 +01:00
Referenced by issue #86282, Attribute search does not show attributes from instances
5 changed files with 144 additions and 18 deletions

View File

@ -186,7 +186,7 @@ class GeometryComponent {
const CustomDataType data_type);
blender::Set<std::string> attribute_names() const;
void attribute_foreach(const AttributeForeachCallback callback) const;
bool attribute_foreach(const AttributeForeachCallback callback) const;
virtual bool is_empty() const;

View File

@ -39,6 +39,10 @@ struct GeometryInstanceGroup {
Vector<float4x4> 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<GeometryInstanceGroup> &r_instance_groups);

View File

@ -822,12 +822,16 @@ Set<std::string> 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

View File

@ -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<InstancesComponent>();
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<GeometryInstanceGroup> set_groups,
Span<GeometryComponentType> component_types,
const Set<std::string> &ignored_attributes,

View File

@ -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<GeometrySet>(socket_ref->identifier());
const Vector<const GeometryComponent *> 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);
}
}