Files
test/source/blender/blenkernel/intern/geometry_set.cc
Jacques Lucke 24dc9a21b1 Geometry Nodes: support attaching gizmos to input values
This adds support for attaching gizmos for input values. The goal is to make it
easier for users to set input values intuitively in the 3D viewport.

We went through multiple different possible designs until we settled on the one
implemented here. We picked it for it's flexibility and ease of use when using
geometry node assets. The core principle in the design is that **gizmos are
attached to existing input values instead of being the input value themselves**.
This actually fits the existing concept of gizmos in Blender well, but may be a
bit unintutitive in a node setup at first. The attachment is done using links in
the node editor.

The most basic usage of the node is to link a Value node to the new Linear Gizmo
node. This attaches the gizmo to the input value and allows you to change it
from the 3D view. The attachment is indicated by the gizmo icon in the sockets
which are controlled by a gizmo as well as the back-link (notice the double
link) when the gizmo is active.

The core principle makes it straight forward to control the same node setup from
the 3D view with gizmos, or by manually changing input values, or by driving the
input values procedurally.

If the input value is controlled indirectly by other inputs, it's often possible
to **automatically propagate** the gizmo to the actual input.

Backpropagation does not work for all nodes, although more nodes can be
supported over time.

This patch adds the first three gizmo nodes which cover common use cases:
* **Linear Gizmo**: Creates a gizmo that controls a float or integer value using
  a linear movement of e.g. an arrow in the 3D viewport.
* **Dial Gizmo**: Creates a circular gizmo in the 3D viewport that can be
  rotated to change the attached angle input.
* **Transform Gizmo**: Creates a simple gizmo for location, rotation and scale.

In the future, more built-in gizmos and potentially the ability for custom
gizmos could be added.

All gizmo nodes have a **Transform** geometry output. Using it is optional but
it is recommended when the gizmo is used to control inputs that affect a
geometry. When it is used, Blender will automatically transform the gizmos
together with the geometry that they control. To achieve this, the output should
be merged with the generated geometry using the *Join Geometry* node. The data
contained in *Transform* output is not visible geometry, but just internal
information that helps Blender to give a better user experience when using
gizmos.

The gizmo nodes have a multi-input socket. This allows **controlling multiple
values** with the same gizmo.

Only a small set of **gizmo shapes** is supported initially. It might be
extended in the future but one goal is to give the gizmos used by different node
group assets a familiar look and feel. A similar constraint exists for
**colors**. Currently, one can choose from a fixed set of colors which can be
modified in the theme settings.

The set of **visible gizmos** is determined by a multiple factors because it's
not really feasible to show all possible gizmos at all times. To see any of the
geometry nodes gizmos, the "Active Modifier" option has to be enabled in the
"Viewport Gizmos" popover. Then all gizmos are drawn for which at least one of
the following is true:
* The gizmo controls an input of the active modifier of the active object.
* The gizmo controls a value in a selected node in an open node editor.
* The gizmo controls a pinned value in an open node editor. Pinning works by
  clicking the gizmo icon next to the value.

Pull Request: https://projects.blender.org/blender/blender/pulls/112677
2024-07-10 16:18:47 +02:00

808 lines
25 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_bounds.hh"
#include "BLI_map.hh"
#include "BLI_task.hh"
#include "BLT_translation.hh"
#include "BKE_attribute.hh"
#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
#include "BKE_geometry_set_instances.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_instances.hh"
#include "BKE_lib_id.hh"
#include "BKE_mesh.hh"
#include "BKE_mesh_wrapper.hh"
#include "BKE_modifier.hh"
#include "BKE_object_types.hh"
#include "BKE_pointcloud.hh"
#include "BKE_volume.hh"
#include "DNA_collection_types.h"
#include "DNA_object_types.h"
#include "DNA_pointcloud_types.h"
#include "BLI_rand.hh"
#include "MEM_guardedalloc.h"
namespace blender::bke {
/* -------------------------------------------------------------------- */
/** \name Geometry Component
* \{ */
GeometryComponent::GeometryComponent(Type type) : type_(type) {}
GeometryComponentPtr GeometryComponent::create(Type component_type)
{
switch (component_type) {
case Type::Mesh:
return GeometryComponentPtr(new MeshComponent());
case Type::PointCloud:
return GeometryComponentPtr(new PointCloudComponent());
case Type::Instance:
return GeometryComponentPtr(new InstancesComponent());
case Type::Volume:
return GeometryComponentPtr(new VolumeComponent());
case Type::Curve:
return GeometryComponentPtr(new CurveComponent());
case Type::Edit:
return GeometryComponentPtr(new GeometryComponentEditData());
case Type::GreasePencil:
return GeometryComponentPtr(new GreasePencilComponent());
}
BLI_assert_unreachable();
return {};
}
int GeometryComponent::attribute_domain_size(const AttrDomain domain) const
{
if (this->is_empty()) {
return 0;
}
const std::optional<AttributeAccessor> attributes = this->attributes();
if (attributes.has_value()) {
return attributes->domain_size(domain);
}
return 0;
}
std::optional<AttributeAccessor> GeometryComponent::attributes() const
{
return std::nullopt;
};
std::optional<MutableAttributeAccessor> GeometryComponent::attributes_for_write()
{
return std::nullopt;
}
GeometryComponent::Type GeometryComponent::type() const
{
return type_;
}
bool GeometryComponent::is_empty() const
{
return false;
}
void GeometryComponent::delete_self()
{
delete this;
}
void GeometryComponent::delete_data_only()
{
this->clear();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Geometry Set
* \{ */
GeometrySet::GeometrySet() = default;
GeometrySet::GeometrySet(const GeometrySet &other) = default;
GeometrySet::GeometrySet(GeometrySet &&other) = default;
GeometrySet::~GeometrySet() = default;
GeometrySet &GeometrySet::operator=(const GeometrySet &other) = default;
GeometrySet &GeometrySet::operator=(GeometrySet &&other) = default;
GeometryComponent &GeometrySet::get_component_for_write(GeometryComponent::Type component_type)
{
GeometryComponentPtr &component_ptr = components_[size_t(component_type)];
if (!component_ptr) {
/* If the component did not exist before, create a new one. */
component_ptr = GeometryComponent::create(component_type);
}
else if (component_ptr->is_mutable()) {
/* If the referenced component is already mutable, return it directly. */
component_ptr->tag_ensured_mutable();
}
else {
/* If the referenced component is shared, make a copy. The copy is not shared and is
* therefore mutable. */
component_ptr = component_ptr->copy();
}
return const_cast<GeometryComponent &>(*component_ptr);
}
GeometryComponent *GeometrySet::get_component_ptr(GeometryComponent::Type type)
{
if (this->has(type)) {
return &this->get_component_for_write(type);
}
return nullptr;
}
const GeometryComponent *GeometrySet::get_component(GeometryComponent::Type component_type) const
{
return components_[size_t(component_type)].get();
}
bool GeometrySet::has(const GeometryComponent::Type component_type) const
{
const GeometryComponentPtr &component = components_[size_t(component_type)];
return component.has_value() && !component->is_empty();
}
void GeometrySet::remove(const GeometryComponent::Type component_type)
{
components_[size_t(component_type)].reset();
}
void GeometrySet::keep_only(const Span<GeometryComponent::Type> component_types)
{
for (GeometryComponentPtr &component_ptr : components_) {
if (component_ptr) {
if (!component_types.contains(component_ptr->type())) {
component_ptr.reset();
}
}
}
}
void GeometrySet::keep_only_during_modify(const Span<GeometryComponent::Type> component_types)
{
Vector<GeometryComponent::Type> extended_types = component_types;
extended_types.append_non_duplicates(GeometryComponent::Type::Instance);
extended_types.append_non_duplicates(GeometryComponent::Type::Edit);
this->keep_only(extended_types);
}
void GeometrySet::remove_geometry_during_modify()
{
this->keep_only_during_modify({});
}
void GeometrySet::add(const GeometryComponent &component)
{
BLI_assert(!components_[size_t(component.type())]);
component.add_user();
components_[size_t(component.type())] = GeometryComponentPtr(
const_cast<GeometryComponent *>(&component));
}
Vector<const GeometryComponent *> GeometrySet::get_components() const
{
Vector<const GeometryComponent *> components;
for (const GeometryComponentPtr &component_ptr : components_) {
if (component_ptr) {
components.append(component_ptr.get());
}
}
return components;
}
std::optional<Bounds<float3>> GeometrySet::compute_boundbox_without_instances() const
{
std::optional<Bounds<float3>> bounds;
if (const PointCloud *pointcloud = this->get_pointcloud()) {
bounds = bounds::merge(bounds, pointcloud->bounds_min_max());
}
if (const Mesh *mesh = this->get_mesh()) {
bounds = bounds::merge(bounds, mesh->bounds_min_max());
}
if (const Volume *volume = this->get_volume()) {
bounds = bounds::merge(bounds, BKE_volume_min_max(volume));
}
if (const Curves *curves_id = this->get_curves()) {
bounds = bounds::merge(bounds, curves_id->geometry.wrap().bounds_min_max());
}
if (const GreasePencil *grease_pencil = this->get_grease_pencil()) {
bounds = bounds::merge(bounds, grease_pencil->bounds_min_max_eval());
}
return bounds;
}
std::ostream &operator<<(std::ostream &stream, const GeometrySet &geometry_set)
{
Vector<std::string> parts;
if (const Mesh *mesh = geometry_set.get_mesh()) {
parts.append(std::to_string(mesh->verts_num) + " verts");
parts.append(std::to_string(mesh->edges_num) + " edges");
parts.append(std::to_string(mesh->faces_num) + " faces");
parts.append(std::to_string(mesh->corners_num) + " corners");
}
if (const Curves *curves = geometry_set.get_curves()) {
parts.append(std::to_string(curves->geometry.point_num) + " control points");
parts.append(std::to_string(curves->geometry.curve_num) + " curves");
}
if (const GreasePencil *grease_pencil = geometry_set.get_grease_pencil()) {
parts.append(std::to_string(grease_pencil->layers().size()) + " grease pencil layers");
}
if (const PointCloud *point_cloud = geometry_set.get_pointcloud()) {
parts.append(std::to_string(point_cloud->totpoint) + " points");
}
if (const Volume *volume = geometry_set.get_volume()) {
parts.append(std::to_string(BKE_volume_num_grids(volume)) + " volume grids");
}
if (geometry_set.has_instances()) {
parts.append(std::to_string(geometry_set.get_instances()->instances_num()) + " instances");
}
if (geometry_set.get_curve_edit_hints()) {
parts.append("curve edit hints");
}
stream << "<GeometrySet: ";
for (const int i : parts.index_range()) {
stream << parts[i];
if (i < parts.size() - 1) {
stream << ", ";
}
}
stream << ">";
return stream;
}
void GeometrySet::clear()
{
for (GeometryComponentPtr &component_ptr : components_) {
component_ptr.reset();
}
}
void GeometrySet::ensure_owns_direct_data()
{
for (GeometryComponentPtr &component_ptr : components_) {
if (!component_ptr) {
continue;
}
if (component_ptr->owns_direct_data()) {
continue;
}
GeometryComponent &component_for_write = this->get_component_for_write(component_ptr->type());
component_for_write.ensure_owns_direct_data();
}
}
void GeometrySet::ensure_owns_all_data()
{
if (Instances *instances = this->get_instances_for_write()) {
instances->ensure_geometry_instances();
}
this->ensure_owns_direct_data();
}
bool GeometrySet::owns_direct_data() const
{
for (const GeometryComponentPtr &component_ptr : components_) {
if (component_ptr) {
if (!component_ptr->owns_direct_data()) {
return false;
}
}
}
return true;
}
const Mesh *GeometrySet::get_mesh() const
{
const MeshComponent *component = this->get_component<MeshComponent>();
return (component == nullptr) ? nullptr : component->get();
}
bool GeometrySet::has_mesh() const
{
const MeshComponent *component = this->get_component<MeshComponent>();
return component != nullptr && component->has_mesh();
}
const PointCloud *GeometrySet::get_pointcloud() const
{
const PointCloudComponent *component = this->get_component<PointCloudComponent>();
return (component == nullptr) ? nullptr : component->get();
}
const Volume *GeometrySet::get_volume() const
{
const VolumeComponent *component = this->get_component<VolumeComponent>();
return (component == nullptr) ? nullptr : component->get();
}
const Curves *GeometrySet::get_curves() const
{
const CurveComponent *component = this->get_component<CurveComponent>();
return (component == nullptr) ? nullptr : component->get();
}
const Instances *GeometrySet::get_instances() const
{
const InstancesComponent *component = this->get_component<InstancesComponent>();
return (component == nullptr) ? nullptr : component->get();
}
const CurvesEditHints *GeometrySet::get_curve_edit_hints() const
{
const GeometryComponentEditData *component = this->get_component<GeometryComponentEditData>();
return (component == nullptr) ? nullptr : component->curves_edit_hints_.get();
}
const GizmoEditHints *GeometrySet::get_gizmo_edit_hints() const
{
const GeometryComponentEditData *component = this->get_component<GeometryComponentEditData>();
return (component == nullptr) ? nullptr : component->gizmo_edit_hints_.get();
}
const GreasePencil *GeometrySet::get_grease_pencil() const
{
const GreasePencilComponent *component = this->get_component<GreasePencilComponent>();
return (component == nullptr) ? nullptr : component->get();
}
bool GeometrySet::has_pointcloud() const
{
const PointCloudComponent *component = this->get_component<PointCloudComponent>();
return component != nullptr && component->has_pointcloud();
}
bool GeometrySet::has_instances() const
{
const InstancesComponent *component = this->get_component<InstancesComponent>();
return component != nullptr && component->get() != nullptr &&
component->get()->instances_num() >= 1;
}
bool GeometrySet::has_volume() const
{
const VolumeComponent *component = this->get_component<VolumeComponent>();
return component != nullptr && component->has_volume();
}
bool GeometrySet::has_curves() const
{
const CurveComponent *component = this->get_component<CurveComponent>();
return component != nullptr && component->has_curves();
}
bool GeometrySet::has_realized_data() const
{
for (const GeometryComponentPtr &component_ptr : components_) {
if (component_ptr) {
if (component_ptr->type() != GeometryComponent::Type::Instance) {
return true;
}
}
}
return false;
}
bool GeometrySet::has_grease_pencil() const
{
const GreasePencilComponent *component = this->get_component<GreasePencilComponent>();
return component != nullptr && component->has_grease_pencil();
}
bool GeometrySet::is_empty() const
{
return !(this->has_mesh() || this->has_curves() || this->has_pointcloud() ||
this->has_volume() || this->has_instances() || this->has_grease_pencil());
}
GeometrySet GeometrySet::from_mesh(Mesh *mesh, GeometryOwnershipType ownership)
{
GeometrySet geometry_set;
geometry_set.replace_mesh(mesh, ownership);
return geometry_set;
}
GeometrySet GeometrySet::from_volume(Volume *volume, GeometryOwnershipType ownership)
{
GeometrySet geometry_set;
geometry_set.replace_volume(volume, ownership);
return geometry_set;
}
GeometrySet GeometrySet::from_pointcloud(PointCloud *pointcloud, GeometryOwnershipType ownership)
{
GeometrySet geometry_set;
geometry_set.replace_pointcloud(pointcloud, ownership);
return geometry_set;
}
GeometrySet GeometrySet::from_curves(Curves *curves, GeometryOwnershipType ownership)
{
GeometrySet geometry_set;
geometry_set.replace_curves(curves, ownership);
return geometry_set;
}
GeometrySet GeometrySet::from_instances(Instances *instances, GeometryOwnershipType ownership)
{
GeometrySet geometry_set;
geometry_set.replace_instances(instances, ownership);
return geometry_set;
}
GeometrySet GeometrySet::from_grease_pencil(GreasePencil *grease_pencil,
GeometryOwnershipType ownership)
{
GeometrySet geometry_set;
geometry_set.replace_grease_pencil(grease_pencil, ownership);
return geometry_set;
}
void GeometrySet::replace_mesh(Mesh *mesh, GeometryOwnershipType ownership)
{
if (mesh == nullptr) {
this->remove<MeshComponent>();
return;
}
if (mesh == this->get_mesh()) {
return;
}
this->remove<MeshComponent>();
MeshComponent &component = this->get_component_for_write<MeshComponent>();
component.replace(mesh, ownership);
}
void GeometrySet::replace_curves(Curves *curves, GeometryOwnershipType ownership)
{
if (curves == nullptr) {
this->remove<CurveComponent>();
return;
}
if (curves == this->get_curves()) {
return;
}
this->remove<CurveComponent>();
CurveComponent &component = this->get_component_for_write<CurveComponent>();
component.replace(curves, ownership);
}
void GeometrySet::replace_instances(Instances *instances, GeometryOwnershipType ownership)
{
if (instances == nullptr) {
this->remove<InstancesComponent>();
return;
}
if (instances == this->get_instances()) {
return;
}
this->remove<InstancesComponent>();
InstancesComponent &component = this->get_component_for_write<InstancesComponent>();
component.replace(instances, ownership);
}
void GeometrySet::replace_pointcloud(PointCloud *pointcloud, GeometryOwnershipType ownership)
{
if (pointcloud == nullptr) {
this->remove<PointCloudComponent>();
return;
}
if (pointcloud == this->get_pointcloud()) {
return;
}
this->remove<PointCloudComponent>();
PointCloudComponent &component = this->get_component_for_write<PointCloudComponent>();
component.replace(pointcloud, ownership);
}
void GeometrySet::replace_volume(Volume *volume, GeometryOwnershipType ownership)
{
if (volume == nullptr) {
this->remove<VolumeComponent>();
return;
}
if (volume == this->get_volume()) {
return;
}
this->remove<VolumeComponent>();
VolumeComponent &component = this->get_component_for_write<VolumeComponent>();
component.replace(volume, ownership);
}
void GeometrySet::replace_grease_pencil(GreasePencil *grease_pencil,
GeometryOwnershipType ownership)
{
if (grease_pencil == nullptr) {
this->remove<GreasePencilComponent>();
return;
}
if (grease_pencil == this->get_grease_pencil()) {
return;
}
this->remove<GreasePencilComponent>();
GreasePencilComponent &component = this->get_component_for_write<GreasePencilComponent>();
component.replace(grease_pencil, ownership);
}
Mesh *GeometrySet::get_mesh_for_write()
{
MeshComponent *component = this->get_component_ptr<MeshComponent>();
return component == nullptr ? nullptr : component->get_for_write();
}
PointCloud *GeometrySet::get_pointcloud_for_write()
{
PointCloudComponent *component = this->get_component_ptr<PointCloudComponent>();
return component == nullptr ? nullptr : component->get_for_write();
}
Volume *GeometrySet::get_volume_for_write()
{
VolumeComponent *component = this->get_component_ptr<VolumeComponent>();
return component == nullptr ? nullptr : component->get_for_write();
}
Curves *GeometrySet::get_curves_for_write()
{
CurveComponent *component = this->get_component_ptr<CurveComponent>();
return component == nullptr ? nullptr : component->get_for_write();
}
Instances *GeometrySet::get_instances_for_write()
{
InstancesComponent *component = this->get_component_ptr<InstancesComponent>();
return component == nullptr ? nullptr : component->get_for_write();
}
CurvesEditHints *GeometrySet::get_curve_edit_hints_for_write()
{
if (!this->has<GeometryComponentEditData>()) {
return nullptr;
}
GeometryComponentEditData &component =
this->get_component_for_write<GeometryComponentEditData>();
return component.curves_edit_hints_.get();
}
GizmoEditHints *GeometrySet::get_gizmo_edit_hints_for_write()
{
if (!this->has<GeometryComponentEditData>()) {
return nullptr;
}
GeometryComponentEditData &component =
this->get_component_for_write<GeometryComponentEditData>();
return component.gizmo_edit_hints_.get();
}
GreasePencil *GeometrySet::get_grease_pencil_for_write()
{
GreasePencilComponent *component = this->get_component_ptr<GreasePencilComponent>();
return component == nullptr ? nullptr : component->get_for_write();
}
void GeometrySet::attribute_foreach(const Span<GeometryComponent::Type> component_types,
const bool include_instances,
const AttributeForeachCallback callback) const
{
for (const GeometryComponent::Type component_type : component_types) {
if (!this->has(component_type)) {
continue;
}
const GeometryComponent &component = *this->get_component(component_type);
const std::optional<AttributeAccessor> attributes = component.attributes();
if (attributes.has_value()) {
attributes->for_all(
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
callback(attribute_id, meta_data, component);
return true;
});
}
}
if (include_instances && this->has_instances()) {
const Instances &instances = *this->get_instances();
instances.foreach_referenced_geometry([&](const GeometrySet &instance_geometry_set) {
instance_geometry_set.attribute_foreach(component_types, include_instances, callback);
});
}
}
void GeometrySet::propagate_attributes_from_layer_to_instances(
const AttributeAccessor src_attributes,
MutableAttributeAccessor dst_attributes,
const AnonymousAttributePropagationInfo &propagation_info)
{
src_attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) {
if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) {
return true;
}
const GAttributeReader src = src_attributes.lookup(id, AttrDomain::Layer);
if (src.sharing_info && src.varray.is_span()) {
const AttributeInitShared init(src.varray.get_internal_span().data(), *src.sharing_info);
if (dst_attributes.add(id, AttrDomain::Instance, meta_data.data_type, init)) {
return true;
}
}
GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
id, AttrDomain::Instance, meta_data.data_type);
if (!dst) {
return true;
}
array_utils::copy(src.varray, dst.span);
dst.finish();
return true;
});
}
void GeometrySet::gather_attributes_for_propagation(
const Span<GeometryComponent::Type> component_types,
const GeometryComponent::Type dst_component_type,
bool include_instances,
const AnonymousAttributePropagationInfo &propagation_info,
Map<AttributeIDRef, AttributeKind> &r_attributes) const
{
/* Only needed right now to check if an attribute is built-in on this component type.
* TODO: Get rid of the dummy component. */
const GeometryComponentPtr dummy_component = GeometryComponent::create(dst_component_type);
this->attribute_foreach(
component_types,
include_instances,
[&](const AttributeIDRef &attribute_id,
const AttributeMetaData &meta_data,
const GeometryComponent &component) {
if (component.attributes()->is_builtin(attribute_id)) {
if (!dummy_component->attributes()->is_builtin(attribute_id)) {
/* Don't propagate built-in attributes that are not built-in on the destination
* component. */
return;
}
}
if (meta_data.data_type == CD_PROP_STRING) {
/* Propagating string attributes is not supported yet. */
return;
}
if (attribute_id.is_anonymous() &&
!propagation_info.propagate(attribute_id.anonymous_id())) {
return;
}
AttrDomain domain = meta_data.domain;
if (dst_component_type != GeometryComponent::Type::Instance &&
domain == AttrDomain::Instance) {
domain = AttrDomain::Point;
}
auto add_info = [&](AttributeKind *attribute_kind) {
attribute_kind->domain = domain;
attribute_kind->data_type = meta_data.data_type;
};
auto modify_info = [&](AttributeKind *attribute_kind) {
attribute_kind->domain = bke::attribute_domain_highest_priority(
{attribute_kind->domain, domain});
attribute_kind->data_type = bke::attribute_data_type_highest_complexity(
{attribute_kind->data_type, meta_data.data_type});
};
r_attributes.add_or_modify(attribute_id, add_info, modify_info);
});
}
static void gather_component_types_recursive(const GeometrySet &geometry_set,
const bool include_instances,
const bool ignore_empty,
Vector<GeometryComponent::Type> &r_types)
{
for (const GeometryComponent *component : geometry_set.get_components()) {
if (ignore_empty) {
if (component->is_empty()) {
continue;
}
}
r_types.append_non_duplicates(component->type());
}
if (!include_instances) {
return;
}
const Instances *instances = geometry_set.get_instances();
if (instances == nullptr) {
return;
}
instances->foreach_referenced_geometry([&](const GeometrySet &instance_geometry_set) {
gather_component_types_recursive(
instance_geometry_set, include_instances, ignore_empty, r_types);
});
}
Vector<GeometryComponent::Type> GeometrySet::gather_component_types(const bool include_instances,
bool ignore_empty) const
{
Vector<GeometryComponent::Type> types;
gather_component_types_recursive(*this, include_instances, ignore_empty, types);
return types;
}
static void gather_mutable_geometry_sets(GeometrySet &geometry_set,
Vector<GeometrySet *> &r_geometry_sets)
{
r_geometry_sets.append(&geometry_set);
if (!geometry_set.has_instances()) {
return;
}
/* In the future this can be improved by deduplicating instance references across different
* instances. */
Instances &instances = *geometry_set.get_instances_for_write();
instances.ensure_geometry_instances();
for (const int handle : instances.references().index_range()) {
if (instances.references()[handle].type() == InstanceReference::Type::GeometrySet) {
GeometrySet &instance_geometry = instances.geometry_set_from_reference(handle);
gather_mutable_geometry_sets(instance_geometry, r_geometry_sets);
}
}
}
void GeometrySet::modify_geometry_sets(ForeachSubGeometryCallback callback)
{
Vector<GeometrySet *> geometry_sets;
gather_mutable_geometry_sets(*this, geometry_sets);
if (geometry_sets.size() == 1) {
/* Avoid possible overhead and a large call stack when multithreading is pointless. */
callback(*geometry_sets.first());
}
else {
threading::parallel_for_each(geometry_sets,
[&](GeometrySet *geometry_set) { callback(*geometry_set); });
}
}
bool object_has_geometry_set_instances(const Object &object)
{
const GeometrySet *geometry_set = object.runtime->geometry_set_eval;
if (geometry_set == nullptr) {
return false;
}
for (const GeometryComponent *component : geometry_set->get_components()) {
if (component->is_empty()) {
continue;
}
const GeometryComponent::Type type = component->type();
bool is_instance = false;
switch (type) {
case GeometryComponent::Type::Mesh:
is_instance = object.type != OB_MESH;
break;
case GeometryComponent::Type::PointCloud:
is_instance = object.type != OB_POINTCLOUD;
break;
case GeometryComponent::Type::Instance:
is_instance = true;
break;
case GeometryComponent::Type::Volume:
is_instance = object.type != OB_VOLUME;
break;
case GeometryComponent::Type::Curve:
is_instance = !ELEM(object.type, OB_CURVES_LEGACY, OB_FONT);
break;
case GeometryComponent::Type::Edit:
break;
case GeometryComponent::Type::GreasePencil:
is_instance = object.type != OB_GREASE_PENCIL;
break;
}
if (is_instance) {
return true;
}
}
return false;
}
/** \} */
} // namespace blender::bke