Geometry Nodes: deterministic anonymous attribute lifetimes
Previously, the lifetimes of anonymous attributes were determined by reference counts which were non-deterministic when multiple threads are used. Now the lifetimes of anonymous attributes are handled more explicitly and deterministically. This is a prerequisite for any kind of caching, because caching the output of nodes that do things non-deterministically and have "invisible inputs" (reference counts) doesn't really work. For more details for how deterministic lifetimes are achieved, see D16858. No functional changes are expected. Small performance changes are expected as well (within few percent, anything larger regressions should be reported as bugs). Differential Revision: https://developer.blender.org/D16858
This commit is contained in:
@@ -1,29 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup bke
|
||||
*
|
||||
* An #AnonymousAttributeID is used to identify attributes that are not explicitly named.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct AnonymousAttributeID AnonymousAttributeID;
|
||||
|
||||
AnonymousAttributeID *BKE_anonymous_attribute_id_new_weak(const char *debug_name);
|
||||
AnonymousAttributeID *BKE_anonymous_attribute_id_new_strong(const char *debug_name);
|
||||
bool BKE_anonymous_attribute_id_has_strong_references(const AnonymousAttributeID *anonymous_id);
|
||||
void BKE_anonymous_attribute_id_increment_weak(const AnonymousAttributeID *anonymous_id);
|
||||
void BKE_anonymous_attribute_id_increment_strong(const AnonymousAttributeID *anonymous_id);
|
||||
void BKE_anonymous_attribute_id_decrement_weak(const AnonymousAttributeID *anonymous_id);
|
||||
void BKE_anonymous_attribute_id_decrement_strong(const AnonymousAttributeID *anonymous_id);
|
||||
const char *BKE_anonymous_attribute_id_debug_name(const AnonymousAttributeID *anonymous_id);
|
||||
const char *BKE_anonymous_attribute_id_internal_name(const AnonymousAttributeID *anonymous_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,155 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
|
||||
#include "BLI_hash.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
#include "BKE_anonymous_attribute.h"
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
/**
|
||||
* Wrapper for #AnonymousAttributeID with RAII semantics.
|
||||
* This class should typically not be used directly. Instead use #StrongAnonymousAttributeID or
|
||||
* #WeakAnonymousAttributeID.
|
||||
*/
|
||||
template<bool IsStrongReference> class OwnedAnonymousAttributeID {
|
||||
private:
|
||||
const AnonymousAttributeID *data_ = nullptr;
|
||||
|
||||
template<bool OtherIsStrongReference> friend class OwnedAnonymousAttributeID;
|
||||
|
||||
public:
|
||||
OwnedAnonymousAttributeID() = default;
|
||||
|
||||
/** Create a new anonymous attribute id. */
|
||||
explicit OwnedAnonymousAttributeID(StringRefNull debug_name)
|
||||
{
|
||||
if constexpr (IsStrongReference) {
|
||||
data_ = BKE_anonymous_attribute_id_new_strong(debug_name.c_str());
|
||||
}
|
||||
else {
|
||||
data_ = BKE_anonymous_attribute_id_new_weak(debug_name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This transfers ownership, so no incref is necessary.
|
||||
* The caller has to make sure that it owned the anonymous id.
|
||||
*/
|
||||
explicit OwnedAnonymousAttributeID(const AnonymousAttributeID *anonymous_id)
|
||||
: data_(anonymous_id)
|
||||
{
|
||||
}
|
||||
|
||||
template<bool OtherIsStrong>
|
||||
OwnedAnonymousAttributeID(const OwnedAnonymousAttributeID<OtherIsStrong> &other)
|
||||
{
|
||||
data_ = other.data_;
|
||||
this->incref();
|
||||
}
|
||||
|
||||
template<bool OtherIsStrong>
|
||||
OwnedAnonymousAttributeID(OwnedAnonymousAttributeID<OtherIsStrong> &&other)
|
||||
{
|
||||
data_ = other.data_;
|
||||
this->incref();
|
||||
other.decref();
|
||||
other.data_ = nullptr;
|
||||
}
|
||||
|
||||
~OwnedAnonymousAttributeID()
|
||||
{
|
||||
this->decref();
|
||||
}
|
||||
|
||||
template<bool OtherIsStrong>
|
||||
OwnedAnonymousAttributeID &operator=(const OwnedAnonymousAttributeID<OtherIsStrong> &other)
|
||||
{
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
this->~OwnedAnonymousAttributeID();
|
||||
new (this) OwnedAnonymousAttributeID(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<bool OtherIsStrong>
|
||||
OwnedAnonymousAttributeID &operator=(OwnedAnonymousAttributeID<OtherIsStrong> &&other)
|
||||
{
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
this->~OwnedAnonymousAttributeID();
|
||||
new (this) OwnedAnonymousAttributeID(std::move(other));
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return data_ != nullptr;
|
||||
}
|
||||
|
||||
StringRefNull debug_name() const
|
||||
{
|
||||
BLI_assert(data_ != nullptr);
|
||||
return BKE_anonymous_attribute_id_debug_name(data_);
|
||||
}
|
||||
|
||||
bool has_strong_references() const
|
||||
{
|
||||
BLI_assert(data_ != nullptr);
|
||||
return BKE_anonymous_attribute_id_has_strong_references(data_);
|
||||
}
|
||||
|
||||
/** Extract the ownership of the currently wrapped anonymous id. */
|
||||
const AnonymousAttributeID *extract()
|
||||
{
|
||||
const AnonymousAttributeID *extracted_data = data_;
|
||||
/* Don't decref because the caller becomes the new owner. */
|
||||
data_ = nullptr;
|
||||
return extracted_data;
|
||||
}
|
||||
|
||||
/** Get the wrapped anonymous id, without taking ownership. */
|
||||
const AnonymousAttributeID *get() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
private:
|
||||
void incref()
|
||||
{
|
||||
if (data_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
if constexpr (IsStrongReference) {
|
||||
BKE_anonymous_attribute_id_increment_strong(data_);
|
||||
}
|
||||
else {
|
||||
BKE_anonymous_attribute_id_increment_weak(data_);
|
||||
}
|
||||
}
|
||||
|
||||
void decref()
|
||||
{
|
||||
if (data_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
if constexpr (IsStrongReference) {
|
||||
BKE_anonymous_attribute_id_decrement_strong(data_);
|
||||
}
|
||||
else {
|
||||
BKE_anonymous_attribute_id_decrement_weak(data_);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using StrongAnonymousAttributeID = OwnedAnonymousAttributeID<true>;
|
||||
using WeakAnonymousAttributeID = OwnedAnonymousAttributeID<false>;
|
||||
|
||||
} // namespace blender::bke
|
||||
103
source/blender/blenkernel/BKE_anonymous_attribute_id.hh
Normal file
103
source/blender/blenkernel/BKE_anonymous_attribute_id.hh
Normal file
@@ -0,0 +1,103 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_user_counter.hh"
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
/**
|
||||
* An #AnonymousAttributeID contains information about a specific anonymous attribute.
|
||||
* Like normal attributes, anonymous attributes are also identified by their name, so one should
|
||||
* not have to compare #AnonymousAttributeID pointers.
|
||||
*
|
||||
* Anonymous attributes don't need additional information besides their name, with a few
|
||||
* exceptions:
|
||||
* - The name of anonymous attributes is generated automatically, so it is generally not human
|
||||
* readable (just random characters). #AnonymousAttributeID can provide more context as where a
|
||||
* specific anonymous attribute was created which can simplify debugging.
|
||||
* - [Not yet supported.] When anonymous attributes are contained in on-disk caches, we have to map
|
||||
* those back to anonymous attributes at run-time. The issue is that (for various reasons) we
|
||||
* might change how anonymous attribute names are generated in the future, which would lead to a
|
||||
* mis-match between stored and new attribute names. To work around it, we should cache
|
||||
* additional information for anonymous attributes on disk (like which node created it). This
|
||||
* information can then be used to map stored attributes to their run-time counterpart.
|
||||
*
|
||||
* Once created, #AnonymousAttributeID is immutable. Also it is intrinsicly reference counted so
|
||||
* that it can have shared ownership. `std::shared_ptr` can't be used for that purpose here,
|
||||
* because that is not available in C code. If possible, the #AutoAnonymousAttributeID wrapper
|
||||
* should be used to avoid manual reference counting in C++ code.
|
||||
*/
|
||||
class AnonymousAttributeID {
|
||||
private:
|
||||
mutable std::atomic<int> users_ = 1;
|
||||
|
||||
protected:
|
||||
std::string name_;
|
||||
|
||||
public:
|
||||
virtual ~AnonymousAttributeID() = default;
|
||||
|
||||
StringRefNull name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
void user_add() const
|
||||
{
|
||||
users_.fetch_add(1);
|
||||
}
|
||||
|
||||
void user_remove() const
|
||||
{
|
||||
const int new_users = users_.fetch_sub(1) - 1;
|
||||
if (new_users == 0) {
|
||||
MEM_delete(this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Wrapper for #AnonymousAttributeID that avoids manual reference counting. */
|
||||
using AutoAnonymousAttributeID = UserCounter<const AnonymousAttributeID>;
|
||||
|
||||
/**
|
||||
* A set of anonymous attribute names that is passed around in geometry nodes.
|
||||
*/
|
||||
class AnonymousAttributeSet {
|
||||
public:
|
||||
/**
|
||||
* This uses `std::shared_ptr` because attributes sets are passed around by value during geometry
|
||||
* nodes evaluation, and this makes it very small if there is no name. Also it makes copying very
|
||||
* cheap.
|
||||
*/
|
||||
std::shared_ptr<Set<std::string>> names;
|
||||
};
|
||||
|
||||
/**
|
||||
* Can be passed to algorithms which propagate attributes. It can tell the algorithm which
|
||||
* anonymous attributes should be propagated and can be skipped.
|
||||
*/
|
||||
class AnonymousAttributePropagationInfo {
|
||||
public:
|
||||
/**
|
||||
* This uses `std::shared_ptr` because it's usually initialized from an #AnonymousAttributeSet
|
||||
* and then the set doesn't have to be copied.
|
||||
*/
|
||||
std::shared_ptr<Set<std::string>> names;
|
||||
|
||||
/**
|
||||
* Propagate all anonymous attributes even if the set above is empty.
|
||||
*/
|
||||
bool propagate_all = true;
|
||||
|
||||
/**
|
||||
* Return true when the anonymous attribute should be propagated and false otherwise.
|
||||
*/
|
||||
bool propagate(const AnonymousAttributeID &anonymous_id) const;
|
||||
};
|
||||
|
||||
} // namespace blender::bke
|
||||
@@ -11,7 +11,7 @@
|
||||
#include "BLI_math_vec_types.hh"
|
||||
#include "BLI_set.hh"
|
||||
|
||||
#include "BKE_anonymous_attribute.hh"
|
||||
#include "BKE_anonymous_attribute_id.hh"
|
||||
#include "BKE_attribute.h"
|
||||
|
||||
struct Mesh;
|
||||
@@ -24,7 +24,7 @@ class GField;
|
||||
namespace blender::bke {
|
||||
|
||||
/**
|
||||
* Identifies an attribute that is either named or anonymous.
|
||||
* Identifies an attribute with optional anonymous attribute information.
|
||||
* It does not own the identifier, so it is just a reference.
|
||||
*/
|
||||
class AttributeIDRef {
|
||||
@@ -38,15 +38,14 @@ class AttributeIDRef {
|
||||
AttributeIDRef(StringRefNull name);
|
||||
AttributeIDRef(const char *name);
|
||||
AttributeIDRef(const std::string &name);
|
||||
AttributeIDRef(const AnonymousAttributeID &anonymous_id);
|
||||
AttributeIDRef(const AnonymousAttributeID *anonymous_id);
|
||||
|
||||
operator bool() const;
|
||||
uint64_t hash() const;
|
||||
bool is_named() const;
|
||||
bool is_anonymous() const;
|
||||
StringRef name() const;
|
||||
const AnonymousAttributeID &anonymous_id() const;
|
||||
bool should_be_kept() const;
|
||||
|
||||
friend bool operator==(const AttributeIDRef &a, const AttributeIDRef &b);
|
||||
friend std::ostream &operator<<(std::ostream &stream, const AttributeIDRef &attribute_id);
|
||||
@@ -749,6 +748,7 @@ Vector<AttributeTransferData> retrieve_attributes_for_transfer(
|
||||
const bke::AttributeAccessor src_attributes,
|
||||
bke::MutableAttributeAccessor dst_attributes,
|
||||
eAttrDomainMask domain_mask,
|
||||
const AnonymousAttributePropagationInfo &propagation_info,
|
||||
const Set<std::string> &skip = {});
|
||||
|
||||
/**
|
||||
@@ -762,6 +762,7 @@ void copy_attribute_domain(AttributeAccessor src_attributes,
|
||||
MutableAttributeAccessor dst_attributes,
|
||||
IndexMask selection,
|
||||
eAttrDomain domain,
|
||||
const AnonymousAttributePropagationInfo &propagation_info,
|
||||
const Set<std::string> &skip = {});
|
||||
|
||||
bool allow_procedural_attribute_access(StringRef attribute_name);
|
||||
@@ -852,29 +853,31 @@ inline AttributeIDRef::AttributeIDRef(const std::string &name) : name_(name)
|
||||
}
|
||||
|
||||
/* The anonymous id is only borrowed, the caller has to keep a reference to it. */
|
||||
inline AttributeIDRef::AttributeIDRef(const AnonymousAttributeID *anonymous_id)
|
||||
: anonymous_id_(anonymous_id)
|
||||
inline AttributeIDRef::AttributeIDRef(const AnonymousAttributeID &anonymous_id)
|
||||
: AttributeIDRef(anonymous_id.name())
|
||||
{
|
||||
anonymous_id_ = &anonymous_id;
|
||||
}
|
||||
|
||||
inline AttributeIDRef::AttributeIDRef(const AnonymousAttributeID *anonymous_id)
|
||||
: AttributeIDRef(anonymous_id ? anonymous_id->name() : "")
|
||||
{
|
||||
anonymous_id_ = anonymous_id;
|
||||
}
|
||||
|
||||
inline bool operator==(const AttributeIDRef &a, const AttributeIDRef &b)
|
||||
{
|
||||
return a.anonymous_id_ == b.anonymous_id_ && a.name_ == b.name_;
|
||||
return a.name_ == b.name_;
|
||||
}
|
||||
|
||||
inline AttributeIDRef::operator bool() const
|
||||
{
|
||||
return this->is_named() || this->is_anonymous();
|
||||
return !name_.is_empty();
|
||||
}
|
||||
|
||||
inline uint64_t AttributeIDRef::hash() const
|
||||
{
|
||||
return get_default_hash_2(name_, anonymous_id_);
|
||||
}
|
||||
|
||||
inline bool AttributeIDRef::is_named() const
|
||||
{
|
||||
return !name_.is_empty();
|
||||
return get_default_hash(name_);
|
||||
}
|
||||
|
||||
inline bool AttributeIDRef::is_anonymous() const
|
||||
@@ -884,7 +887,6 @@ inline bool AttributeIDRef::is_anonymous() const
|
||||
|
||||
inline StringRef AttributeIDRef::name() const
|
||||
{
|
||||
BLI_assert(this->is_named());
|
||||
return name_;
|
||||
}
|
||||
|
||||
@@ -894,14 +896,4 @@ inline const AnonymousAttributeID &AttributeIDRef::anonymous_id() const
|
||||
return *anonymous_id_;
|
||||
}
|
||||
|
||||
/**
|
||||
* \return True if the attribute should not be removed automatically as an optimization during
|
||||
* processing or copying. Anonymous attributes can be removed when they no longer have any
|
||||
* references.
|
||||
*/
|
||||
inline bool AttributeIDRef::should_be_kept() const
|
||||
{
|
||||
return this->is_named() || BKE_anonymous_attribute_id_has_strong_references(anonymous_id_);
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
@@ -11,6 +11,8 @@ struct Mesh;
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
class AnonymousAttributePropagationInfo;
|
||||
|
||||
/**
|
||||
* Extrude all splines in the profile curve along the path of every spline in the curve input.
|
||||
* Transfer curve attributes to the mesh.
|
||||
@@ -23,11 +25,13 @@ namespace blender::bke {
|
||||
*/
|
||||
Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
|
||||
const CurvesGeometry &profile,
|
||||
bool fill_caps);
|
||||
bool fill_caps,
|
||||
const AnonymousAttributePropagationInfo &propagation_info);
|
||||
/**
|
||||
* Create a loose-edge mesh based on the evaluated path of the curve's splines.
|
||||
* Transfer curve attributes to the mesh.
|
||||
*/
|
||||
Mesh *curve_to_wire_mesh(const CurvesGeometry &curve);
|
||||
Mesh *curve_to_wire_mesh(const CurvesGeometry &curve,
|
||||
const AnonymousAttributePropagationInfo &propagation_info);
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
@@ -404,8 +404,10 @@ class CurvesGeometry : public ::CurvesGeometry {
|
||||
|
||||
void calculate_bezier_auto_handles();
|
||||
|
||||
void remove_points(IndexMask points_to_delete);
|
||||
void remove_curves(IndexMask curves_to_delete);
|
||||
void remove_points(IndexMask points_to_delete,
|
||||
const AnonymousAttributePropagationInfo &propagation_info = {});
|
||||
void remove_curves(IndexMask curves_to_delete,
|
||||
const AnonymousAttributePropagationInfo &propagation_info = {});
|
||||
|
||||
/**
|
||||
* Change the direction of selected curves (switch the start and end) without changing their
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct AnonymousAttributeID;
|
||||
struct BMesh;
|
||||
struct BlendDataReader;
|
||||
struct BlendWriter;
|
||||
@@ -227,7 +226,7 @@ void *CustomData_add_layer_anonymous(struct CustomData *data,
|
||||
eCDAllocType alloctype,
|
||||
void *layer,
|
||||
int totelem,
|
||||
const struct AnonymousAttributeID *anonymous_id);
|
||||
const AnonymousAttributeIDHandle *anonymous_id);
|
||||
|
||||
/**
|
||||
* Frees the active or first data layer with the give type.
|
||||
@@ -275,8 +274,6 @@ void *CustomData_duplicate_referenced_layer_named(struct CustomData *data,
|
||||
int type,
|
||||
const char *name,
|
||||
int totelem);
|
||||
void *CustomData_duplicate_referenced_layer_anonymous(
|
||||
CustomData *data, int type, const struct AnonymousAttributeID *anonymous_id, int totelem);
|
||||
bool CustomData_is_referenced_layer(struct CustomData *data, int type);
|
||||
|
||||
/**
|
||||
|
||||
@@ -261,18 +261,14 @@ class NormalFieldInput : public GeometryFieldInput {
|
||||
|
||||
class AnonymousAttributeFieldInput : public GeometryFieldInput {
|
||||
private:
|
||||
/**
|
||||
* A strong reference is required to make sure that the referenced attribute is not removed
|
||||
* automatically.
|
||||
*/
|
||||
StrongAnonymousAttributeID anonymous_id_;
|
||||
AutoAnonymousAttributeID anonymous_id_;
|
||||
std::string producer_name_;
|
||||
|
||||
public:
|
||||
AnonymousAttributeFieldInput(StrongAnonymousAttributeID anonymous_id,
|
||||
AnonymousAttributeFieldInput(AutoAnonymousAttributeID anonymous_id,
|
||||
const CPPType &type,
|
||||
std::string producer_name)
|
||||
: GeometryFieldInput(type, anonymous_id.debug_name()),
|
||||
: GeometryFieldInput(type, anonymous_id->name()),
|
||||
anonymous_id_(std::move(anonymous_id)),
|
||||
producer_name_(producer_name)
|
||||
{
|
||||
@@ -280,7 +276,7 @@ class AnonymousAttributeFieldInput : public GeometryFieldInput {
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static fn::Field<T> Create(StrongAnonymousAttributeID anonymous_id, std::string producer_name)
|
||||
static fn::Field<T> Create(AutoAnonymousAttributeID anonymous_id, std::string producer_name)
|
||||
{
|
||||
const CPPType &type = CPPType::get<T>();
|
||||
auto field_input = std::make_shared<AnonymousAttributeFieldInput>(
|
||||
@@ -288,6 +284,11 @@ class AnonymousAttributeFieldInput : public GeometryFieldInput {
|
||||
return fn::Field<T>{field_input};
|
||||
}
|
||||
|
||||
const AutoAnonymousAttributeID &anonymous_id() const
|
||||
{
|
||||
return anonymous_id_;
|
||||
}
|
||||
|
||||
GVArray get_varray_for_context(const GeometryFieldContext &context,
|
||||
IndexMask mask) const override;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#include "BLI_user_counter.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "BKE_anonymous_attribute.hh"
|
||||
#include "BKE_anonymous_attribute_id.hh"
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_geometry_set.h"
|
||||
|
||||
@@ -213,6 +213,7 @@ struct GeometrySet {
|
||||
blender::Span<GeometryComponentType> component_types,
|
||||
GeometryComponentType dst_component_type,
|
||||
bool include_instances,
|
||||
const blender::bke::AnonymousAttributePropagationInfo &propagation_info,
|
||||
blender::Map<blender::bke::AttributeIDRef, blender::bke::AttributeKind> &r_attributes) const;
|
||||
|
||||
blender::Vector<GeometryComponentType> gather_component_types(bool include_instances,
|
||||
|
||||
@@ -155,7 +155,8 @@ class Instances {
|
||||
* Remove the indices that are not contained in the mask input, and remove unused instance
|
||||
* references afterwards.
|
||||
*/
|
||||
void remove(const blender::IndexMask mask);
|
||||
void remove(const blender::IndexMask mask,
|
||||
const blender::bke::AnonymousAttributePropagationInfo &propagation_info);
|
||||
/**
|
||||
* Get an id for every instance. These can be used for e.g. motion blur.
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "BLI_cache_mutex.hh"
|
||||
#include "BLI_multi_value_map.hh"
|
||||
#include "BLI_resource_scope.hh"
|
||||
#include "BLI_utility_mixins.hh"
|
||||
#include "BLI_vector.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
@@ -24,6 +25,10 @@ namespace blender::nodes {
|
||||
struct FieldInferencingInterface;
|
||||
class NodeDeclaration;
|
||||
struct GeometryNodesLazyFunctionGraphInfo;
|
||||
namespace anonymous_attribute_lifetime {
|
||||
struct RelationsInNode;
|
||||
}
|
||||
namespace aal = anonymous_attribute_lifetime;
|
||||
} // namespace blender::nodes
|
||||
|
||||
namespace blender {
|
||||
@@ -106,6 +111,8 @@ class bNodeTreeRuntime : NonCopyable, NonMovable {
|
||||
|
||||
/** Information about how inputs and outputs of the node group interact with fields. */
|
||||
std::unique_ptr<nodes::FieldInferencingInterface> field_inferencing_interface;
|
||||
/** Information about usage of anonymous attributes within the group. */
|
||||
std::unique_ptr<nodes::aal::RelationsInNode> anonymous_attribute_relations;
|
||||
|
||||
/**
|
||||
* For geometry nodes, a lazy function graph with some additional info is cached. This is used to
|
||||
@@ -330,7 +337,11 @@ inline bool topology_cache_is_available(const bNodeSocket &socket)
|
||||
namespace node_field_inferencing {
|
||||
bool update_field_inferencing(const bNodeTree &tree);
|
||||
}
|
||||
|
||||
namespace anonymous_attribute_inferencing {
|
||||
Array<const nodes::aal::RelationsInNode *> get_relations_by_node(const bNodeTree &tree,
|
||||
ResourceScope &scope);
|
||||
bool update_anonymous_attribute_relations(bNodeTree &tree);
|
||||
} // namespace anonymous_attribute_inferencing
|
||||
} // namespace blender::bke
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
@@ -64,7 +64,7 @@ set(SRC
|
||||
intern/anim_path.c
|
||||
intern/anim_sys.c
|
||||
intern/anim_visualization.c
|
||||
intern/anonymous_attribute.cc
|
||||
intern/anonymous_attribute_id.cc
|
||||
intern/appdir.c
|
||||
intern/armature.c
|
||||
intern/armature_deform.c
|
||||
@@ -229,6 +229,7 @@ set(SRC
|
||||
intern/nla.c
|
||||
intern/node.cc
|
||||
intern/node_runtime.cc
|
||||
intern/node_tree_anonymous_attributes.cc
|
||||
intern/node_tree_field_inferencing.cc
|
||||
intern/node_tree_update.cc
|
||||
intern/object.cc
|
||||
@@ -315,8 +316,7 @@ set(SRC
|
||||
BKE_anim_path.h
|
||||
BKE_anim_visualization.h
|
||||
BKE_animsys.h
|
||||
BKE_anonymous_attribute.h
|
||||
BKE_anonymous_attribute.hh
|
||||
BKE_anonymous_attribute_id.hh
|
||||
BKE_appdir.h
|
||||
BKE_armature.h
|
||||
BKE_armature.hh
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_anonymous_attribute.hh"
|
||||
|
||||
using namespace blender::bke;
|
||||
|
||||
/**
|
||||
* A struct that identifies an attribute. It's lifetime is managed by an atomic reference count.
|
||||
*
|
||||
* Additionally, this struct can be strongly or weakly owned. The difference is that strong
|
||||
* ownership means that attributes with this id will be kept around. Weak ownership just makes sure
|
||||
* that the struct itself stays alive, but corresponding attributes might still be removed
|
||||
* automatically.
|
||||
*/
|
||||
struct AnonymousAttributeID {
|
||||
/**
|
||||
* Total number of references to this attribute id. Once this reaches zero, the struct can be
|
||||
* freed. This includes strong and weak references.
|
||||
*/
|
||||
mutable std::atomic<int> refcount_tot = 0;
|
||||
|
||||
/**
|
||||
* Number of strong references to this attribute id. When this is zero, the corresponding
|
||||
* attributes can be removed from geometries automatically.
|
||||
*/
|
||||
mutable std::atomic<int> refcount_strong = 0;
|
||||
|
||||
/**
|
||||
* Only used to identify this struct in a debugging session.
|
||||
*/
|
||||
std::string debug_name;
|
||||
|
||||
/**
|
||||
* Unique name of the this attribute id during the current session.
|
||||
*/
|
||||
std::string internal_name;
|
||||
};
|
||||
|
||||
/** Every time this function is called, it outputs a different name. */
|
||||
static std::string get_new_internal_name()
|
||||
{
|
||||
static std::atomic<int> index = 0;
|
||||
const int next_index = index.fetch_add(1);
|
||||
return ".a_" + std::to_string(next_index);
|
||||
}
|
||||
|
||||
AnonymousAttributeID *BKE_anonymous_attribute_id_new_weak(const char *debug_name)
|
||||
{
|
||||
AnonymousAttributeID *anonymous_id = new AnonymousAttributeID();
|
||||
anonymous_id->debug_name = debug_name;
|
||||
anonymous_id->internal_name = get_new_internal_name();
|
||||
anonymous_id->refcount_tot.store(1);
|
||||
return anonymous_id;
|
||||
}
|
||||
|
||||
AnonymousAttributeID *BKE_anonymous_attribute_id_new_strong(const char *debug_name)
|
||||
{
|
||||
AnonymousAttributeID *anonymous_id = new AnonymousAttributeID();
|
||||
anonymous_id->debug_name = debug_name;
|
||||
anonymous_id->internal_name = get_new_internal_name();
|
||||
anonymous_id->refcount_tot.store(1);
|
||||
anonymous_id->refcount_strong.store(1);
|
||||
return anonymous_id;
|
||||
}
|
||||
|
||||
bool BKE_anonymous_attribute_id_has_strong_references(const AnonymousAttributeID *anonymous_id)
|
||||
{
|
||||
return anonymous_id->refcount_strong.load() >= 1;
|
||||
}
|
||||
|
||||
void BKE_anonymous_attribute_id_increment_weak(const AnonymousAttributeID *anonymous_id)
|
||||
{
|
||||
anonymous_id->refcount_tot.fetch_add(1);
|
||||
}
|
||||
|
||||
void BKE_anonymous_attribute_id_increment_strong(const AnonymousAttributeID *anonymous_id)
|
||||
{
|
||||
anonymous_id->refcount_tot.fetch_add(1);
|
||||
anonymous_id->refcount_strong.fetch_add(1);
|
||||
}
|
||||
|
||||
void BKE_anonymous_attribute_id_decrement_weak(const AnonymousAttributeID *anonymous_id)
|
||||
{
|
||||
const int new_refcount = anonymous_id->refcount_tot.fetch_sub(1) - 1;
|
||||
if (new_refcount == 0) {
|
||||
BLI_assert(anonymous_id->refcount_strong == 0);
|
||||
delete anonymous_id;
|
||||
}
|
||||
}
|
||||
|
||||
void BKE_anonymous_attribute_id_decrement_strong(const AnonymousAttributeID *anonymous_id)
|
||||
{
|
||||
anonymous_id->refcount_strong.fetch_sub(1);
|
||||
BKE_anonymous_attribute_id_decrement_weak(anonymous_id);
|
||||
}
|
||||
|
||||
const char *BKE_anonymous_attribute_id_debug_name(const AnonymousAttributeID *anonymous_id)
|
||||
{
|
||||
return anonymous_id->debug_name.c_str();
|
||||
}
|
||||
|
||||
const char *BKE_anonymous_attribute_id_internal_name(const AnonymousAttributeID *anonymous_id)
|
||||
{
|
||||
return anonymous_id->internal_name.c_str();
|
||||
}
|
||||
18
source/blender/blenkernel/intern/anonymous_attribute_id.cc
Normal file
18
source/blender/blenkernel/intern/anonymous_attribute_id.cc
Normal file
@@ -0,0 +1,18 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_anonymous_attribute_id.hh"
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
bool AnonymousAttributePropagationInfo::propagate(const AnonymousAttributeID &anonymous_id) const
|
||||
{
|
||||
if (this->propagate_all) {
|
||||
return true;
|
||||
}
|
||||
if (!this->names) {
|
||||
return false;
|
||||
}
|
||||
return this->names->contains_as(anonymous_id.name());
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
@@ -40,13 +40,9 @@ namespace blender::bke {
|
||||
|
||||
std::ostream &operator<<(std::ostream &stream, const AttributeIDRef &attribute_id)
|
||||
{
|
||||
if (attribute_id.is_named()) {
|
||||
if (attribute_id) {
|
||||
stream << attribute_id.name();
|
||||
}
|
||||
else if (attribute_id.is_anonymous()) {
|
||||
const AnonymousAttributeID &anonymous_id = attribute_id.anonymous_id();
|
||||
stream << "<" << BKE_anonymous_attribute_id_debug_name(&anonymous_id) << ">";
|
||||
}
|
||||
else {
|
||||
stream << "<none>";
|
||||
}
|
||||
@@ -153,7 +149,7 @@ eAttrDomain attribute_domain_highest_priority(Span<eAttrDomain> domains)
|
||||
static AttributeIDRef attribute_id_from_custom_data_layer(const CustomDataLayer &layer)
|
||||
{
|
||||
if (layer.anonymous_id != nullptr) {
|
||||
return layer.anonymous_id;
|
||||
return *layer.anonymous_id;
|
||||
}
|
||||
return layer.name;
|
||||
}
|
||||
@@ -207,7 +203,7 @@ static void *add_generic_custom_data_layer(CustomData &custom_data,
|
||||
const int domain_num,
|
||||
const AttributeIDRef &attribute_id)
|
||||
{
|
||||
if (attribute_id.is_named()) {
|
||||
if (!attribute_id.is_anonymous()) {
|
||||
char attribute_name_c[MAX_NAME];
|
||||
attribute_id.name().copy(attribute_name_c);
|
||||
return CustomData_add_layer_named(
|
||||
@@ -261,9 +257,6 @@ static bool custom_data_layer_matches_attribute_id(const CustomDataLayer &layer,
|
||||
if (!attribute_id) {
|
||||
return false;
|
||||
}
|
||||
if (attribute_id.is_anonymous()) {
|
||||
return layer.anonymous_id == &attribute_id.anonymous_id();
|
||||
}
|
||||
return layer.name == attribute_id.name();
|
||||
}
|
||||
|
||||
@@ -451,14 +444,8 @@ GAttributeWriter CustomDataAttributeProvider::try_get_for_write(
|
||||
if (!custom_data_layer_matches_attribute_id(layer, attribute_id)) {
|
||||
continue;
|
||||
}
|
||||
if (attribute_id.is_named()) {
|
||||
CustomData_duplicate_referenced_layer_named(
|
||||
custom_data, layer.type, layer.name, element_num);
|
||||
}
|
||||
else {
|
||||
CustomData_duplicate_referenced_layer_anonymous(
|
||||
custom_data, layer.type, &attribute_id.anonymous_id(), element_num);
|
||||
}
|
||||
CustomData_duplicate_referenced_layer_named(custom_data, layer.type, layer.name, element_num);
|
||||
|
||||
const CPPType *type = custom_data_type_to_cpp_type((eCustomDataType)layer.type);
|
||||
if (type == nullptr) {
|
||||
continue;
|
||||
@@ -854,7 +841,7 @@ void MutableAttributeAccessor::remove_anonymous()
|
||||
}
|
||||
|
||||
while (!anonymous_ids.is_empty()) {
|
||||
this->remove(anonymous_ids.pop_last());
|
||||
this->remove(*anonymous_ids.pop_last());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -883,12 +870,7 @@ GAttributeWriter MutableAttributeAccessor::lookup_for_write(const AttributeIDRef
|
||||
#ifdef DEBUG
|
||||
if (attribute) {
|
||||
auto checker = std::make_shared<FinishCallChecker>();
|
||||
if (attribute_id.is_named()) {
|
||||
checker->name = attribute_id.name();
|
||||
}
|
||||
else {
|
||||
checker->name = BKE_anonymous_attribute_id_debug_name(&attribute_id.anonymous_id());
|
||||
}
|
||||
checker->name = attribute_id.name();
|
||||
checker->real_finish_fn = attribute.tag_modified_fn;
|
||||
attribute.tag_modified_fn = [checker]() {
|
||||
if (checker->real_finish_fn) {
|
||||
@@ -968,6 +950,7 @@ Vector<AttributeTransferData> retrieve_attributes_for_transfer(
|
||||
const bke::AttributeAccessor src_attributes,
|
||||
bke::MutableAttributeAccessor dst_attributes,
|
||||
const eAttrDomainMask domain_mask,
|
||||
const AnonymousAttributePropagationInfo &propagation_info,
|
||||
const Set<std::string> &skip)
|
||||
{
|
||||
Vector<AttributeTransferData> attributes;
|
||||
@@ -976,10 +959,10 @@ Vector<AttributeTransferData> retrieve_attributes_for_transfer(
|
||||
if (!(ATTR_DOMAIN_AS_MASK(meta_data.domain) & domain_mask)) {
|
||||
return true;
|
||||
}
|
||||
if (id.is_named() && skip.contains(id.name())) {
|
||||
if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) {
|
||||
return true;
|
||||
}
|
||||
if (!id.should_be_kept()) {
|
||||
if (skip.contains(id.name())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -999,6 +982,7 @@ void copy_attribute_domain(const AttributeAccessor src_attributes,
|
||||
MutableAttributeAccessor dst_attributes,
|
||||
const IndexMask selection,
|
||||
const eAttrDomain domain,
|
||||
const AnonymousAttributePropagationInfo &propagation_info,
|
||||
const Set<std::string> &skip)
|
||||
{
|
||||
src_attributes.for_all(
|
||||
@@ -1006,10 +990,10 @@ void copy_attribute_domain(const AttributeAccessor src_attributes,
|
||||
if (meta_data.domain != domain) {
|
||||
return true;
|
||||
}
|
||||
if (id.is_named() && skip.contains(id.name())) {
|
||||
if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) {
|
||||
return true;
|
||||
}
|
||||
if (!id.should_be_kept()) {
|
||||
if (skip.contains(id.name())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -336,7 +336,7 @@ namespace attribute_accessor_functions {
|
||||
template<const ComponentAttributeProviders &providers>
|
||||
inline bool is_builtin(const void * /*owner*/, const AttributeIDRef &attribute_id)
|
||||
{
|
||||
if (!attribute_id.is_named()) {
|
||||
if (attribute_id.is_anonymous()) {
|
||||
return false;
|
||||
}
|
||||
const StringRef name = attribute_id.name();
|
||||
@@ -346,7 +346,7 @@ inline bool is_builtin(const void * /*owner*/, const AttributeIDRef &attribute_i
|
||||
template<const ComponentAttributeProviders &providers>
|
||||
inline GAttributeReader lookup(const void *owner, const AttributeIDRef &attribute_id)
|
||||
{
|
||||
if (attribute_id.is_named()) {
|
||||
if (!attribute_id.is_anonymous()) {
|
||||
const StringRef name = attribute_id.name();
|
||||
if (const BuiltinAttributeProvider *provider =
|
||||
providers.builtin_attribute_providers().lookup_default_as(name, nullptr)) {
|
||||
@@ -396,7 +396,7 @@ template<const ComponentAttributeProviders &providers>
|
||||
inline AttributeValidator lookup_validator(const void * /*owner*/,
|
||||
const blender::bke::AttributeIDRef &attribute_id)
|
||||
{
|
||||
if (!attribute_id.is_named()) {
|
||||
if (attribute_id.is_anonymous()) {
|
||||
return {};
|
||||
}
|
||||
const BuiltinAttributeProvider *provider =
|
||||
@@ -443,7 +443,7 @@ inline std::optional<AttributeMetaData> lookup_meta_data(const void *owner,
|
||||
template<const ComponentAttributeProviders &providers>
|
||||
inline GAttributeWriter lookup_for_write(void *owner, const AttributeIDRef &attribute_id)
|
||||
{
|
||||
if (attribute_id.is_named()) {
|
||||
if (!attribute_id.is_anonymous()) {
|
||||
const StringRef name = attribute_id.name();
|
||||
if (const BuiltinAttributeProvider *provider =
|
||||
providers.builtin_attribute_providers().lookup_default_as(name, nullptr)) {
|
||||
@@ -462,7 +462,7 @@ inline GAttributeWriter lookup_for_write(void *owner, const AttributeIDRef &attr
|
||||
template<const ComponentAttributeProviders &providers>
|
||||
inline bool remove(void *owner, const AttributeIDRef &attribute_id)
|
||||
{
|
||||
if (attribute_id.is_named()) {
|
||||
if (!attribute_id.is_anonymous()) {
|
||||
const StringRef name = attribute_id.name();
|
||||
if (const BuiltinAttributeProvider *provider =
|
||||
providers.builtin_attribute_providers().lookup_default_as(name, nullptr)) {
|
||||
@@ -487,7 +487,7 @@ inline bool add(void *owner,
|
||||
if (contains<providers>(owner, attribute_id)) {
|
||||
return false;
|
||||
}
|
||||
if (attribute_id.is_named()) {
|
||||
if (!attribute_id.is_anonymous()) {
|
||||
const StringRef name = attribute_id.name();
|
||||
if (const BuiltinAttributeProvider *provider =
|
||||
providers.builtin_attribute_providers().lookup_default_as(name, nullptr)) {
|
||||
|
||||
@@ -28,6 +28,8 @@ BLI_CPP_TYPE_MAKE(Material *, CPPTypeFlags::BasicType)
|
||||
|
||||
BLI_CPP_TYPE_MAKE(MStringProperty, CPPTypeFlags::None);
|
||||
|
||||
BLI_CPP_TYPE_MAKE(blender::bke::AnonymousAttributeSet, CPPTypeFlags::None);
|
||||
|
||||
void BKE_cpp_types_init()
|
||||
{
|
||||
blender::register_cpp_types();
|
||||
@@ -45,4 +47,6 @@ void BKE_cpp_types_init()
|
||||
BLI_CPP_TYPE_REGISTER(Material *);
|
||||
|
||||
BLI_CPP_TYPE_REGISTER(MStringProperty);
|
||||
|
||||
BLI_CPP_TYPE_REGISTER(blender::bke::AnonymousAttributeSet);
|
||||
}
|
||||
|
||||
@@ -331,18 +331,19 @@ static eAttrDomain get_attribute_domain_for_mesh(const AttributeAccessor &mesh_a
|
||||
static bool should_add_attribute_to_mesh(const AttributeAccessor &curve_attributes,
|
||||
const AttributeAccessor &mesh_attributes,
|
||||
const AttributeIDRef &id,
|
||||
const AttributeMetaData &meta_data)
|
||||
const AttributeMetaData &meta_data,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
|
||||
/* The position attribute has special non-generic evaluation. */
|
||||
if (id.is_named() && id.name() == "position") {
|
||||
if (id.name() == "position") {
|
||||
return false;
|
||||
}
|
||||
/* Don't propagate built-in curves attributes that are not built-in on meshes. */
|
||||
if (curve_attributes.is_builtin(id) && !mesh_attributes.is_builtin(id)) {
|
||||
return false;
|
||||
}
|
||||
if (!id.should_be_kept()) {
|
||||
if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) {
|
||||
return false;
|
||||
}
|
||||
if (meta_data.data_type == CD_PROP_STRING) {
|
||||
@@ -629,7 +630,8 @@ static void copy_curve_domain_attribute_to_mesh(const ResultOffsets &mesh_offset
|
||||
|
||||
Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
|
||||
const CurvesGeometry &profile,
|
||||
const bool fill_caps)
|
||||
const bool fill_caps,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const CurvesInfo curves_info = get_curves_info(main, profile);
|
||||
|
||||
@@ -716,7 +718,8 @@ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
|
||||
MutableAttributeAccessor mesh_attributes = mesh->attributes_for_write();
|
||||
|
||||
main_attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) {
|
||||
if (!should_add_attribute_to_mesh(main_attributes, mesh_attributes, id, meta_data)) {
|
||||
if (!should_add_attribute_to_mesh(
|
||||
main_attributes, mesh_attributes, id, meta_data, propagation_info)) {
|
||||
return true;
|
||||
}
|
||||
main_attributes_set.add_new(id);
|
||||
@@ -753,7 +756,8 @@ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
|
||||
if (main_attributes.contains(id)) {
|
||||
return true;
|
||||
}
|
||||
if (!should_add_attribute_to_mesh(profile_attributes, mesh_attributes, id, meta_data)) {
|
||||
if (!should_add_attribute_to_mesh(
|
||||
profile_attributes, mesh_attributes, id, meta_data, propagation_info)) {
|
||||
return true;
|
||||
}
|
||||
const eAttrDomain src_domain = meta_data.domain;
|
||||
@@ -797,10 +801,11 @@ static CurvesGeometry get_curve_single_vert()
|
||||
return curves;
|
||||
}
|
||||
|
||||
Mesh *curve_to_wire_mesh(const CurvesGeometry &curve)
|
||||
Mesh *curve_to_wire_mesh(const CurvesGeometry &curve,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
static const CurvesGeometry vert_curve = get_curve_single_vert();
|
||||
return curve_to_mesh_sweep(curve, vert_curve, false);
|
||||
return curve_to_mesh_sweep(curve, vert_curve, false, propagation_info);
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
@@ -1047,8 +1047,10 @@ static Array<int> build_point_to_curve_map(const CurvesGeometry &curves)
|
||||
return point_to_curve_map;
|
||||
}
|
||||
|
||||
static CurvesGeometry copy_with_removed_points(const CurvesGeometry &curves,
|
||||
const IndexMask points_to_delete)
|
||||
static CurvesGeometry copy_with_removed_points(
|
||||
const CurvesGeometry &curves,
|
||||
const IndexMask points_to_delete,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
/* Use a map from points to curves to facilitate using an #IndexMask input. */
|
||||
const Array<int> point_to_curve_map = build_point_to_curve_map(curves);
|
||||
@@ -1093,9 +1095,15 @@ static CurvesGeometry copy_with_removed_points(const CurvesGeometry &curves,
|
||||
|
||||
CurvesGeometry new_curves{new_point_count, new_curve_count};
|
||||
Vector<bke::AttributeTransferData> point_attributes = bke::retrieve_attributes_for_transfer(
|
||||
curves.attributes(), new_curves.attributes_for_write(), ATTR_DOMAIN_MASK_POINT);
|
||||
curves.attributes(),
|
||||
new_curves.attributes_for_write(),
|
||||
ATTR_DOMAIN_MASK_POINT,
|
||||
propagation_info);
|
||||
Vector<bke::AttributeTransferData> curve_attributes = bke::retrieve_attributes_for_transfer(
|
||||
curves.attributes(), new_curves.attributes_for_write(), ATTR_DOMAIN_MASK_CURVE);
|
||||
curves.attributes(),
|
||||
new_curves.attributes_for_write(),
|
||||
ATTR_DOMAIN_MASK_CURVE,
|
||||
propagation_info);
|
||||
|
||||
threading::parallel_invoke(
|
||||
256 < new_point_count * new_curve_count,
|
||||
@@ -1144,7 +1152,8 @@ static CurvesGeometry copy_with_removed_points(const CurvesGeometry &curves,
|
||||
return new_curves;
|
||||
}
|
||||
|
||||
void CurvesGeometry::remove_points(const IndexMask points_to_delete)
|
||||
void CurvesGeometry::remove_points(const IndexMask points_to_delete,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
if (points_to_delete.is_empty()) {
|
||||
return;
|
||||
@@ -1152,11 +1161,13 @@ void CurvesGeometry::remove_points(const IndexMask points_to_delete)
|
||||
if (points_to_delete.size() == this->points_num()) {
|
||||
*this = {};
|
||||
}
|
||||
*this = copy_with_removed_points(*this, points_to_delete);
|
||||
*this = copy_with_removed_points(*this, points_to_delete, propagation_info);
|
||||
}
|
||||
|
||||
static CurvesGeometry copy_with_removed_curves(const CurvesGeometry &curves,
|
||||
const IndexMask curves_to_delete)
|
||||
static CurvesGeometry copy_with_removed_curves(
|
||||
const CurvesGeometry &curves,
|
||||
const IndexMask curves_to_delete,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const Span<int> old_offsets = curves.offsets();
|
||||
const Vector<IndexRange> old_curve_ranges = curves_to_delete.extract_ranges_invert(
|
||||
@@ -1178,9 +1189,15 @@ static CurvesGeometry copy_with_removed_curves(const CurvesGeometry &curves,
|
||||
|
||||
CurvesGeometry new_curves{new_tot_points, new_tot_curves};
|
||||
Vector<bke::AttributeTransferData> point_attributes = bke::retrieve_attributes_for_transfer(
|
||||
curves.attributes(), new_curves.attributes_for_write(), ATTR_DOMAIN_MASK_POINT);
|
||||
curves.attributes(),
|
||||
new_curves.attributes_for_write(),
|
||||
ATTR_DOMAIN_MASK_POINT,
|
||||
propagation_info);
|
||||
Vector<bke::AttributeTransferData> curve_attributes = bke::retrieve_attributes_for_transfer(
|
||||
curves.attributes(), new_curves.attributes_for_write(), ATTR_DOMAIN_MASK_CURVE);
|
||||
curves.attributes(),
|
||||
new_curves.attributes_for_write(),
|
||||
ATTR_DOMAIN_MASK_CURVE,
|
||||
propagation_info);
|
||||
|
||||
threading::parallel_invoke(
|
||||
256 < new_tot_points * new_tot_curves,
|
||||
@@ -1251,7 +1268,8 @@ static CurvesGeometry copy_with_removed_curves(const CurvesGeometry &curves,
|
||||
return new_curves;
|
||||
}
|
||||
|
||||
void CurvesGeometry::remove_curves(const IndexMask curves_to_delete)
|
||||
void CurvesGeometry::remove_curves(const IndexMask curves_to_delete,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
if (curves_to_delete.is_empty()) {
|
||||
return;
|
||||
@@ -1260,7 +1278,7 @@ void CurvesGeometry::remove_curves(const IndexMask curves_to_delete)
|
||||
*this = {};
|
||||
return;
|
||||
}
|
||||
*this = copy_with_removed_curves(*this, curves_to_delete);
|
||||
*this = copy_with_removed_curves(*this, curves_to_delete, propagation_info);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
@@ -1315,7 +1333,7 @@ void CurvesGeometry::reverse_curves(const IndexMask curves_to_reverse)
|
||||
if (meta_data.data_type == CD_PROP_STRING) {
|
||||
return true;
|
||||
}
|
||||
if (id.is_named() && bezier_handle_names.contains(id.name())) {
|
||||
if (bezier_handle_names.contains(id.name())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
#include "BLT_translation.h"
|
||||
|
||||
#include "BKE_anonymous_attribute.h"
|
||||
#include "BKE_anonymous_attribute_id.hh"
|
||||
#include "BKE_customdata.h"
|
||||
#include "BKE_customdata_file.h"
|
||||
#include "BKE_deform.h"
|
||||
@@ -2356,7 +2356,7 @@ bool CustomData_merge(const CustomData *source,
|
||||
layer->anonymous_id = nullptr;
|
||||
}
|
||||
else {
|
||||
BKE_anonymous_attribute_id_increment_weak(layer->anonymous_id);
|
||||
layer->anonymous_id->user_add();
|
||||
}
|
||||
}
|
||||
if (alloctype == CD_ASSIGN) {
|
||||
@@ -2460,7 +2460,7 @@ static void customData_free_layer__internal(CustomDataLayer *layer, const int to
|
||||
const LayerTypeInfo *typeInfo;
|
||||
|
||||
if (layer->anonymous_id != nullptr) {
|
||||
BKE_anonymous_attribute_id_decrement_weak(layer->anonymous_id);
|
||||
layer->anonymous_id->user_remove();
|
||||
layer->anonymous_id = nullptr;
|
||||
}
|
||||
if (!(layer->flag & CD_FLAG_NOFREE) && layer->data) {
|
||||
@@ -2957,9 +2957,9 @@ void *CustomData_add_layer_anonymous(CustomData *data,
|
||||
const eCDAllocType alloctype,
|
||||
void *layerdata,
|
||||
const int totelem,
|
||||
const AnonymousAttributeID *anonymous_id)
|
||||
const AnonymousAttributeIDHandle *anonymous_id)
|
||||
{
|
||||
const char *name = BKE_anonymous_attribute_id_internal_name(anonymous_id);
|
||||
const char *name = anonymous_id->name().c_str();
|
||||
CustomDataLayer *layer = customData_add_layer__internal(
|
||||
data, type, alloctype, layerdata, totelem, name);
|
||||
CustomData_update_typemap(data);
|
||||
@@ -2968,7 +2968,7 @@ void *CustomData_add_layer_anonymous(CustomData *data,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BKE_anonymous_attribute_id_increment_weak(anonymous_id);
|
||||
anonymous_id->user_add();
|
||||
layer->anonymous_id = anonymous_id;
|
||||
return layer->data;
|
||||
}
|
||||
@@ -3147,20 +3147,6 @@ void *CustomData_duplicate_referenced_layer_named(CustomData *data,
|
||||
return customData_duplicate_referenced_layer_index(data, layer_index, totelem);
|
||||
}
|
||||
|
||||
void *CustomData_duplicate_referenced_layer_anonymous(CustomData *data,
|
||||
const int /*type*/,
|
||||
const AnonymousAttributeID *anonymous_id,
|
||||
const int totelem)
|
||||
{
|
||||
for (int i = 0; i < data->totlayer; i++) {
|
||||
if (data->layers[i].anonymous_id == anonymous_id) {
|
||||
return customData_duplicate_referenced_layer_index(data, i, totelem);
|
||||
}
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CustomData_duplicate_referenced_layers(CustomData *data, const int totelem)
|
||||
{
|
||||
for (int i = 0; i < data->totlayer; i++) {
|
||||
|
||||
@@ -1055,7 +1055,7 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider {
|
||||
GAttributeReader try_get_for_read(const void *owner,
|
||||
const AttributeIDRef &attribute_id) const final
|
||||
{
|
||||
if (!attribute_id.is_named()) {
|
||||
if (attribute_id.is_anonymous()) {
|
||||
return {};
|
||||
}
|
||||
const Mesh *mesh = static_cast<const Mesh *>(owner);
|
||||
@@ -1079,7 +1079,7 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider {
|
||||
|
||||
GAttributeWriter try_get_for_write(void *owner, const AttributeIDRef &attribute_id) const final
|
||||
{
|
||||
if (!attribute_id.is_named()) {
|
||||
if (attribute_id.is_anonymous()) {
|
||||
return {};
|
||||
}
|
||||
Mesh *mesh = static_cast<Mesh *>(owner);
|
||||
@@ -1100,7 +1100,7 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider {
|
||||
|
||||
bool try_delete(void *owner, const AttributeIDRef &attribute_id) const final
|
||||
{
|
||||
if (!attribute_id.is_named()) {
|
||||
if (attribute_id.is_anonymous()) {
|
||||
return false;
|
||||
}
|
||||
Mesh *mesh = static_cast<Mesh *>(owner);
|
||||
|
||||
@@ -332,7 +332,7 @@ GVArray AnonymousAttributeFieldInput::get_varray_for_context(const GeometryField
|
||||
const IndexMask /*mask*/) const
|
||||
{
|
||||
const eCustomDataType data_type = cpp_type_to_custom_data_type(*type_);
|
||||
return context.attributes()->lookup(anonymous_id_.get(), context.domain(), data_type);
|
||||
return context.attributes()->lookup(*anonymous_id_, context.domain(), data_type);
|
||||
}
|
||||
|
||||
std::string AnonymousAttributeFieldInput::socket_inspection_name() const
|
||||
@@ -363,8 +363,7 @@ std::optional<eAttrDomain> AnonymousAttributeFieldInput::preferred_domain(
|
||||
if (!attributes.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::optional<AttributeMetaData> meta_data = attributes->lookup_meta_data(
|
||||
anonymous_id_.get());
|
||||
const std::optional<AttributeMetaData> meta_data = attributes->lookup_meta_data(*anonymous_id_);
|
||||
if (!meta_data.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -581,6 +581,7 @@ void GeometrySet::gather_attributes_for_propagation(
|
||||
const Span<GeometryComponentType> component_types,
|
||||
const GeometryComponentType dst_component_type,
|
||||
bool include_instances,
|
||||
const blender::bke::AnonymousAttributePropagationInfo &propagation_info,
|
||||
blender::Map<blender::bke::AttributeIDRef, blender::bke::AttributeKind> &r_attributes) const
|
||||
{
|
||||
using namespace blender;
|
||||
@@ -605,7 +606,8 @@ void GeometrySet::gather_attributes_for_propagation(
|
||||
/* Propagating string attributes is not supported yet. */
|
||||
return;
|
||||
}
|
||||
if (!attribute_id.should_be_kept()) {
|
||||
if (attribute_id.is_anonymous() &&
|
||||
!propagation_info.propagate(attribute_id.anonymous_id())) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,8 @@ blender::Span<InstanceReference> Instances::references() const
|
||||
return references_;
|
||||
}
|
||||
|
||||
void Instances::remove(const IndexMask mask)
|
||||
void Instances::remove(const IndexMask mask,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
using namespace blender;
|
||||
if (mask.is_range() && mask.as_range().start() == 0) {
|
||||
@@ -132,7 +133,7 @@ void Instances::remove(const IndexMask mask)
|
||||
|
||||
src_attributes.foreach_attribute(
|
||||
[&](const bke::AttributeIDRef &id, const bke::AttributeMetaData &meta_data) {
|
||||
if (!id.should_be_kept()) {
|
||||
if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -839,7 +839,9 @@ static Mesh *mesh_new_from_evaluated_curve_type_object(const Object *evaluated_o
|
||||
return BKE_mesh_copy_for_eval(mesh, false);
|
||||
}
|
||||
if (const Curves *curves = get_evaluated_curves_from_object(evaluated_object)) {
|
||||
return blender::bke::curve_to_wire_mesh(blender::bke::CurvesGeometry::wrap(curves->geometry));
|
||||
const blender::bke::AnonymousAttributePropagationInfo propagation_info;
|
||||
return blender::bke::curve_to_wire_mesh(blender::bke::CurvesGeometry::wrap(curves->geometry),
|
||||
propagation_info);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -207,6 +207,11 @@ static void ntree_copy_data(Main * /*bmain*/, ID *id_dst, const ID *id_src, cons
|
||||
dst_runtime.field_inferencing_interface = std::make_unique<FieldInferencingInterface>(
|
||||
*ntree_src->runtime->field_inferencing_interface);
|
||||
}
|
||||
if (ntree_src->runtime->anonymous_attribute_relations) {
|
||||
dst_runtime.anonymous_attribute_relations =
|
||||
std::make_unique<blender::nodes::anonymous_attribute_lifetime::RelationsInNode>(
|
||||
*ntree_src->runtime->anonymous_attribute_relations);
|
||||
}
|
||||
|
||||
if (flag & LIB_ID_COPY_NO_PREVIEW) {
|
||||
ntree_dst->preview = nullptr;
|
||||
|
||||
@@ -0,0 +1,452 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "NOD_node_declaration.hh"
|
||||
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "BLI_multi_value_map.hh"
|
||||
#include "BLI_resource_scope.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_stack.hh"
|
||||
#include "BLI_timeit.hh"
|
||||
|
||||
namespace blender::bke::anonymous_attribute_inferencing {
|
||||
namespace aal = nodes::aal;
|
||||
using nodes::NodeDeclaration;
|
||||
|
||||
static const aal::RelationsInNode &get_relations_in_node(const bNode &node, ResourceScope &scope)
|
||||
{
|
||||
if (node.is_group()) {
|
||||
if (const bNodeTree *group = reinterpret_cast<const bNodeTree *>(node.id)) {
|
||||
BLI_assert(group->runtime->anonymous_attribute_relations);
|
||||
return *group->runtime->anonymous_attribute_relations;
|
||||
}
|
||||
}
|
||||
if (const NodeDeclaration *node_decl = node.declaration()) {
|
||||
if (const aal::RelationsInNode *relations = node_decl->anonymous_attribute_relations()) {
|
||||
return *relations;
|
||||
}
|
||||
}
|
||||
return scope.construct<aal::RelationsInNode>();
|
||||
}
|
||||
|
||||
Array<const aal::RelationsInNode *> get_relations_by_node(const bNodeTree &tree,
|
||||
ResourceScope &scope)
|
||||
{
|
||||
const Span<const bNode *> nodes = tree.all_nodes();
|
||||
Array<const aal::RelationsInNode *> relations_by_node(nodes.size());
|
||||
for (const int i : nodes.index_range()) {
|
||||
relations_by_node[i] = &get_relations_in_node(*nodes[i], scope);
|
||||
}
|
||||
return relations_by_node;
|
||||
}
|
||||
|
||||
static bool socket_is_field(const bNodeSocket &socket)
|
||||
{
|
||||
return socket.display_shape == SOCK_DISPLAY_SHAPE_DIAMOND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start at a group output socket and find all linked group inputs.
|
||||
*/
|
||||
static Vector<int> find_linked_group_inputs(
|
||||
const bNodeTree &tree,
|
||||
const bNodeSocket &group_output_socket,
|
||||
const FunctionRef<Vector<int>(const bNodeSocket &)> get_linked_node_inputs)
|
||||
{
|
||||
Set<const bNodeSocket *> found_sockets;
|
||||
Stack<const bNodeSocket *> sockets_to_check;
|
||||
|
||||
Vector<int> input_indices;
|
||||
|
||||
found_sockets.add_new(&group_output_socket);
|
||||
sockets_to_check.push(&group_output_socket);
|
||||
|
||||
while (!sockets_to_check.is_empty()) {
|
||||
const bNodeSocket &socket = *sockets_to_check.pop();
|
||||
if (socket.is_input()) {
|
||||
for (const bNodeLink *link : socket.directly_linked_links()) {
|
||||
if (link->is_used()) {
|
||||
const bNodeSocket &from_socket = *link->fromsock;
|
||||
if (found_sockets.add(&from_socket)) {
|
||||
sockets_to_check.push(&from_socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
const bNode &node = socket.owner_node();
|
||||
for (const int input_index : get_linked_node_inputs(socket)) {
|
||||
const bNodeSocket &input_socket = node.input_socket(input_index);
|
||||
if (input_socket.is_available()) {
|
||||
if (found_sockets.add(&input_socket)) {
|
||||
sockets_to_check.push(&input_socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const bNode *node : tree.group_input_nodes()) {
|
||||
for (const bNodeSocket *socket : node->output_sockets()) {
|
||||
if (found_sockets.contains(socket)) {
|
||||
input_indices.append_non_duplicates(socket->index());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return input_indices;
|
||||
}
|
||||
|
||||
static void infer_propagate_relations(const bNodeTree &tree,
|
||||
const Span<const aal::RelationsInNode *> relations_by_node,
|
||||
const bNode &group_output_node,
|
||||
aal::RelationsInNode &r_relations)
|
||||
{
|
||||
for (const bNodeSocket *group_output_socket : group_output_node.input_sockets().drop_back(1)) {
|
||||
if (group_output_socket->type != SOCK_GEOMETRY) {
|
||||
continue;
|
||||
}
|
||||
const Vector<int> input_indices = find_linked_group_inputs(
|
||||
tree, *group_output_socket, [&](const bNodeSocket &output_socket) {
|
||||
Vector<int> indices;
|
||||
for (const aal::PropagateRelation &relation :
|
||||
relations_by_node[output_socket.owner_node().index()]->propagate_relations) {
|
||||
if (relation.to_geometry_output == output_socket.index()) {
|
||||
indices.append(relation.from_geometry_input);
|
||||
}
|
||||
}
|
||||
return indices;
|
||||
});
|
||||
for (const int input_index : input_indices) {
|
||||
aal::PropagateRelation relation;
|
||||
relation.from_geometry_input = input_index;
|
||||
relation.to_geometry_output = group_output_socket->index();
|
||||
r_relations.propagate_relations.append(relation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void infer_reference_relations(const bNodeTree &tree,
|
||||
const Span<const aal::RelationsInNode *> relations_by_node,
|
||||
const bNode &group_output_node,
|
||||
aal::RelationsInNode &r_relations)
|
||||
{
|
||||
for (const bNodeSocket *group_output_socket : group_output_node.input_sockets().drop_back(1)) {
|
||||
if (!socket_is_field(*group_output_socket)) {
|
||||
continue;
|
||||
}
|
||||
const Vector<int> input_indices = find_linked_group_inputs(
|
||||
tree, *group_output_socket, [&](const bNodeSocket &output_socket) {
|
||||
Vector<int> indices;
|
||||
for (const aal::ReferenceRelation &relation :
|
||||
relations_by_node[output_socket.owner_node().index()]->reference_relations) {
|
||||
if (relation.to_field_output == output_socket.index()) {
|
||||
indices.append(relation.from_field_input);
|
||||
}
|
||||
}
|
||||
return indices;
|
||||
});
|
||||
for (const int input_index : input_indices) {
|
||||
if (tree.runtime->field_inferencing_interface->inputs[input_index] !=
|
||||
nodes::InputSocketFieldType::None) {
|
||||
aal::ReferenceRelation relation;
|
||||
relation.from_field_input = input_index;
|
||||
relation.to_field_output = group_output_socket->index();
|
||||
r_relations.reference_relations.append(relation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find group output geometries that contain anonymous attributes referenced by the field.
|
||||
* If `nullopt` is returned, the field does not depend on any anonymous attribute created in this
|
||||
* node tree.
|
||||
*/
|
||||
static std::optional<Vector<int>> find_available_on_outputs(
|
||||
const bNodeSocket &initial_group_output_socket,
|
||||
const bNode &group_output_node,
|
||||
const Span<const aal::RelationsInNode *> relations_by_node)
|
||||
{
|
||||
Set<const bNodeSocket *> geometry_sockets;
|
||||
|
||||
{
|
||||
/* Find the nodes that added anonymous attributes to the field. */
|
||||
Set<const bNodeSocket *> found_sockets;
|
||||
Stack<const bNodeSocket *> sockets_to_check;
|
||||
|
||||
found_sockets.add_new(&initial_group_output_socket);
|
||||
sockets_to_check.push(&initial_group_output_socket);
|
||||
|
||||
while (!sockets_to_check.is_empty()) {
|
||||
const bNodeSocket &socket = *sockets_to_check.pop();
|
||||
if (socket.is_input()) {
|
||||
for (const bNodeLink *link : socket.directly_linked_links()) {
|
||||
if (link->is_used()) {
|
||||
const bNodeSocket &from_socket = *link->fromsock;
|
||||
if (found_sockets.add(&from_socket)) {
|
||||
sockets_to_check.push(&from_socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
const bNode &node = socket.owner_node();
|
||||
const aal::RelationsInNode &relations = *relations_by_node[node.index()];
|
||||
for (const aal::AvailableRelation &relation : relations.available_relations) {
|
||||
if (socket.index() == relation.field_output) {
|
||||
const bNodeSocket &geometry_output = node.output_socket(relation.geometry_output);
|
||||
if (geometry_output.is_available()) {
|
||||
geometry_sockets.add(&geometry_output);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const aal::ReferenceRelation &relation : relations.reference_relations) {
|
||||
if (socket.index() == relation.to_field_output) {
|
||||
const bNodeSocket &field_input = node.input_socket(relation.from_field_input);
|
||||
if (field_input.is_available()) {
|
||||
if (found_sockets.add(&field_input)) {
|
||||
sockets_to_check.push(&field_input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (geometry_sockets.is_empty()) {
|
||||
/* The field does not depend on any anonymous attribute created within this node tree. */
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/* Find the group output geometries that contain the anonymous attribute referenced by the field
|
||||
* output. */
|
||||
Set<const bNodeSocket *> found_sockets;
|
||||
Stack<const bNodeSocket *> sockets_to_check;
|
||||
|
||||
for (const bNodeSocket *socket : geometry_sockets) {
|
||||
found_sockets.add_new(socket);
|
||||
sockets_to_check.push(socket);
|
||||
}
|
||||
|
||||
while (!sockets_to_check.is_empty()) {
|
||||
const bNodeSocket &socket = *sockets_to_check.pop();
|
||||
if (socket.is_input()) {
|
||||
const bNode &node = socket.owner_node();
|
||||
const aal::RelationsInNode &relations = *relations_by_node[node.index()];
|
||||
for (const aal::PropagateRelation &relation : relations.propagate_relations) {
|
||||
if (socket.index() == relation.from_geometry_input) {
|
||||
const bNodeSocket &output_socket = node.output_socket(relation.to_geometry_output);
|
||||
if (output_socket.is_available()) {
|
||||
if (found_sockets.add(&output_socket)) {
|
||||
sockets_to_check.push(&output_socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const bNodeLink *link : socket.directly_linked_links()) {
|
||||
if (link->is_used()) {
|
||||
const bNodeSocket &to_socket = *link->tosock;
|
||||
if (found_sockets.add(&to_socket)) {
|
||||
sockets_to_check.push(&to_socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector<int> output_indices;
|
||||
for (const bNodeSocket *socket : group_output_node.input_sockets().drop_back(1)) {
|
||||
if (found_sockets.contains(socket)) {
|
||||
output_indices.append(socket->index());
|
||||
}
|
||||
}
|
||||
return output_indices;
|
||||
}
|
||||
|
||||
static void infer_available_relations(const Span<const aal::RelationsInNode *> relations_by_node,
|
||||
const bNode &group_output_node,
|
||||
aal::RelationsInNode &r_relations)
|
||||
{
|
||||
for (const bNodeSocket *group_output_socket : group_output_node.input_sockets().drop_back(1)) {
|
||||
if (!socket_is_field(*group_output_socket)) {
|
||||
continue;
|
||||
}
|
||||
const std::optional<Vector<int>> output_indices = find_available_on_outputs(
|
||||
*group_output_socket, group_output_node, relations_by_node);
|
||||
if (output_indices.has_value()) {
|
||||
if (output_indices->is_empty()) {
|
||||
r_relations.available_on_none.append(group_output_socket->index());
|
||||
}
|
||||
else {
|
||||
for (const int output_index : *output_indices) {
|
||||
aal::AvailableRelation relation;
|
||||
relation.field_output = group_output_socket->index();
|
||||
relation.geometry_output = output_index;
|
||||
r_relations.available_relations.append(relation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all the geometry inputs that the field input may be evaluated on.
|
||||
*/
|
||||
static Vector<int> find_eval_on_inputs(const bNodeTree &tree,
|
||||
const int field_input_index,
|
||||
const Span<const aal::RelationsInNode *> relations_by_node)
|
||||
{
|
||||
const Span<const bNode *> group_input_nodes = tree.group_input_nodes();
|
||||
Set<const bNodeSocket *> geometry_sockets;
|
||||
|
||||
{
|
||||
/* Find all the nodes that evaluate the input field. */
|
||||
Set<const bNodeSocket *> found_sockets;
|
||||
Stack<const bNodeSocket *> sockets_to_check;
|
||||
|
||||
for (const bNode *node : group_input_nodes) {
|
||||
const bNodeSocket &socket = node->output_socket(field_input_index);
|
||||
found_sockets.add_new(&socket);
|
||||
sockets_to_check.push(&socket);
|
||||
}
|
||||
|
||||
while (!sockets_to_check.is_empty()) {
|
||||
const bNodeSocket &socket = *sockets_to_check.pop();
|
||||
if (socket.is_input()) {
|
||||
const bNode &node = socket.owner_node();
|
||||
const aal::RelationsInNode &relations = *relations_by_node[node.index()];
|
||||
for (const aal::EvalRelation &relation : relations.eval_relations) {
|
||||
if (socket.index() == relation.field_input) {
|
||||
const bNodeSocket &geometry_input = node.input_socket(relation.geometry_input);
|
||||
if (geometry_input.is_available()) {
|
||||
geometry_sockets.add(&geometry_input);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const aal::ReferenceRelation &relation : relations.reference_relations) {
|
||||
if (socket.index() == relation.from_field_input) {
|
||||
const bNodeSocket &field_output = node.output_socket(relation.to_field_output);
|
||||
if (field_output.is_available()) {
|
||||
if (found_sockets.add(&field_output)) {
|
||||
sockets_to_check.push(&field_output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const bNodeLink *link : socket.directly_linked_links()) {
|
||||
if (link->is_used()) {
|
||||
const bNodeSocket &to_socket = *link->tosock;
|
||||
if (found_sockets.add(&to_socket)) {
|
||||
sockets_to_check.push(&to_socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (geometry_sockets.is_empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
/* Find the group input geometries whose attributes are propagated to the nodes that evaluate the
|
||||
* field. */
|
||||
Set<const bNodeSocket *> found_sockets;
|
||||
Stack<const bNodeSocket *> sockets_to_check;
|
||||
|
||||
Vector<int> geometry_input_indices;
|
||||
|
||||
for (const bNodeSocket *socket : geometry_sockets) {
|
||||
found_sockets.add_new(socket);
|
||||
sockets_to_check.push(socket);
|
||||
}
|
||||
|
||||
while (!sockets_to_check.is_empty()) {
|
||||
const bNodeSocket &socket = *sockets_to_check.pop();
|
||||
if (socket.is_input()) {
|
||||
for (const bNodeLink *link : socket.directly_linked_links()) {
|
||||
if (link->is_used()) {
|
||||
const bNodeSocket &from_socket = *link->fromsock;
|
||||
if (found_sockets.add(&from_socket)) {
|
||||
sockets_to_check.push(&from_socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
const bNode &node = socket.owner_node();
|
||||
if (node.is_group_input()) {
|
||||
geometry_input_indices.append_non_duplicates(socket.index());
|
||||
}
|
||||
else {
|
||||
const aal::RelationsInNode &relations = *relations_by_node[node.index()];
|
||||
for (const aal::PropagateRelation &relation : relations.propagate_relations) {
|
||||
if (socket.index() == relation.to_geometry_output) {
|
||||
const bNodeSocket &input_socket = node.input_socket(relation.from_geometry_input);
|
||||
if (input_socket.is_available()) {
|
||||
if (found_sockets.add(&input_socket)) {
|
||||
sockets_to_check.push(&input_socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return geometry_input_indices;
|
||||
}
|
||||
|
||||
static void infer_eval_relations(const bNodeTree &tree,
|
||||
const Span<const aal::RelationsInNode *> relations_by_node,
|
||||
aal::RelationsInNode &r_relations)
|
||||
{
|
||||
for (const int input_index : tree.interface_inputs().index_range()) {
|
||||
if (tree.runtime->field_inferencing_interface->inputs[input_index] ==
|
||||
nodes::InputSocketFieldType::None) {
|
||||
continue;
|
||||
}
|
||||
const Vector<int> geometry_input_indices = find_eval_on_inputs(
|
||||
tree, input_index, relations_by_node);
|
||||
for (const int geometry_input : geometry_input_indices) {
|
||||
aal::EvalRelation relation;
|
||||
relation.field_input = input_index;
|
||||
relation.geometry_input = geometry_input;
|
||||
r_relations.eval_relations.append(std::move(relation));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool update_anonymous_attribute_relations(bNodeTree &tree)
|
||||
{
|
||||
tree.ensure_topology_cache();
|
||||
|
||||
ResourceScope scope;
|
||||
Array<const aal::RelationsInNode *> relations_by_node = get_relations_by_node(tree, scope);
|
||||
|
||||
std::unique_ptr<aal::RelationsInNode> new_relations = std::make_unique<aal::RelationsInNode>();
|
||||
if (!tree.has_available_link_cycle()) {
|
||||
if (const bNode *group_output_node = tree.group_output_node()) {
|
||||
infer_propagate_relations(tree, relations_by_node, *group_output_node, *new_relations);
|
||||
infer_reference_relations(tree, relations_by_node, *group_output_node, *new_relations);
|
||||
infer_available_relations(relations_by_node, *group_output_node, *new_relations);
|
||||
}
|
||||
infer_eval_relations(tree, relations_by_node, *new_relations);
|
||||
}
|
||||
|
||||
const bool group_interface_changed = !tree.runtime->anonymous_attribute_relations ||
|
||||
*tree.runtime->anonymous_attribute_relations !=
|
||||
*new_relations;
|
||||
tree.runtime->anonymous_attribute_relations = std::move(new_relations);
|
||||
|
||||
return group_interface_changed;
|
||||
}
|
||||
|
||||
} // namespace blender::bke::anonymous_attribute_inferencing
|
||||
@@ -473,6 +473,9 @@ class NodeTreeMainUpdater {
|
||||
if (node_field_inferencing::update_field_inferencing(ntree)) {
|
||||
result.interface_changed = true;
|
||||
}
|
||||
if (anonymous_attribute_inferencing::update_anonymous_attribute_relations(ntree)) {
|
||||
result.interface_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
result.output_changed = this->check_if_output_changed(ntree);
|
||||
|
||||
@@ -1484,5 +1484,6 @@ void ED_mesh_split_faces(Mesh *mesh)
|
||||
return;
|
||||
}
|
||||
|
||||
geometry::split_edges(*mesh, split_mask);
|
||||
const bke::AnonymousAttributePropagationInfo propagation_info;
|
||||
geometry::split_edges(*mesh, split_mask, propagation_info);
|
||||
}
|
||||
|
||||
@@ -53,6 +53,10 @@ class Socket : NonCopyable, NonMovable {
|
||||
* Index of the socket. E.g. 0 for the first input and the first output socket.
|
||||
*/
|
||||
int index_in_node_;
|
||||
/**
|
||||
* Index of the socket in the entire graph. Every socket has a different index.
|
||||
*/
|
||||
int index_in_graph_;
|
||||
|
||||
friend Graph;
|
||||
|
||||
@@ -61,6 +65,7 @@ class Socket : NonCopyable, NonMovable {
|
||||
bool is_output() const;
|
||||
|
||||
int index() const;
|
||||
int index_in_graph() const;
|
||||
|
||||
InputSocket &as_input();
|
||||
OutputSocket &as_output();
|
||||
@@ -179,6 +184,20 @@ class DummyDebugInfo {
|
||||
virtual std::string output_name(const int i) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Just stores a string per socket in a dummy node.
|
||||
*/
|
||||
class SimpleDummyDebugInfo : public DummyDebugInfo {
|
||||
public:
|
||||
std::string name;
|
||||
Vector<std::string> input_names;
|
||||
Vector<std::string> output_names;
|
||||
|
||||
std::string node_name() const override;
|
||||
std::string input_name(const int i) const override;
|
||||
std::string output_name(const int i) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* A #Node that does *not* correspond to a #LazyFunction. Instead it can be used to indicate inputs
|
||||
* and outputs of the entire graph. It can have an arbitrary number of inputs and outputs.
|
||||
@@ -205,6 +224,11 @@ class Graph : NonCopyable, NonMovable {
|
||||
* Contains all nodes in the graph so that it is efficient to iterate over them.
|
||||
*/
|
||||
Vector<Node *> nodes_;
|
||||
/**
|
||||
* Number of sockets in the graph. Can be used as array size when indexing using
|
||||
* `Socket::index_in_graph`.
|
||||
*/
|
||||
int socket_num_ = 0;
|
||||
|
||||
public:
|
||||
~Graph();
|
||||
@@ -213,6 +237,7 @@ class Graph : NonCopyable, NonMovable {
|
||||
* Get all nodes in the graph. The index in the span corresponds to #Node::index_in_graph.
|
||||
*/
|
||||
Span<const Node *> nodes() const;
|
||||
Span<Node *> nodes();
|
||||
|
||||
/**
|
||||
* Add a new function node with sockets that match the passed in #LazyFunction.
|
||||
@@ -232,10 +257,24 @@ class Graph : NonCopyable, NonMovable {
|
||||
*/
|
||||
void add_link(OutputSocket &from, InputSocket &to);
|
||||
|
||||
/**
|
||||
* If the socket is linked, remove the link.
|
||||
*/
|
||||
void clear_origin(InputSocket &socket);
|
||||
|
||||
/**
|
||||
* Make sure that #Node::index_in_graph is up to date.
|
||||
*/
|
||||
void update_node_indices();
|
||||
/**
|
||||
* Make sure that #Socket::index_in_graph is up to date.
|
||||
*/
|
||||
void update_socket_indices();
|
||||
|
||||
/**
|
||||
* Number of sockets in the graph.
|
||||
*/
|
||||
int socket_num() const;
|
||||
|
||||
/**
|
||||
* Can be used to assert that #update_node_indices has been called.
|
||||
@@ -280,6 +319,11 @@ inline int Socket::index() const
|
||||
return index_in_node_;
|
||||
}
|
||||
|
||||
inline int Socket::index_in_graph() const
|
||||
{
|
||||
return index_in_graph_;
|
||||
}
|
||||
|
||||
inline InputSocket &Socket::as_input()
|
||||
{
|
||||
BLI_assert(this->is_input());
|
||||
@@ -445,6 +489,16 @@ inline Span<const Node *> Graph::nodes() const
|
||||
return nodes_;
|
||||
}
|
||||
|
||||
inline Span<Node *> Graph::nodes()
|
||||
{
|
||||
return nodes_;
|
||||
}
|
||||
|
||||
inline int Graph::socket_num() const
|
||||
{
|
||||
return socket_num_;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::fn::lazy_function
|
||||
|
||||
@@ -86,6 +86,14 @@ void Graph::add_link(OutputSocket &from, InputSocket &to)
|
||||
from.targets_.append(&to);
|
||||
}
|
||||
|
||||
void Graph::clear_origin(InputSocket &socket)
|
||||
{
|
||||
if (socket.origin_ != nullptr) {
|
||||
socket.origin_->targets_.remove_first_occurrence_and_reorder(&socket);
|
||||
socket.origin_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Graph::update_node_indices()
|
||||
{
|
||||
for (const int i : nodes_.index_range()) {
|
||||
@@ -93,6 +101,20 @@ void Graph::update_node_indices()
|
||||
}
|
||||
}
|
||||
|
||||
void Graph::update_socket_indices()
|
||||
{
|
||||
int socket_counter = 0;
|
||||
for (const int i : nodes_.index_range()) {
|
||||
for (InputSocket *socket : nodes_[i]->inputs()) {
|
||||
socket->index_in_graph_ = socket_counter++;
|
||||
}
|
||||
for (OutputSocket *socket : nodes_[i]->outputs()) {
|
||||
socket->index_in_graph_ = socket_counter++;
|
||||
}
|
||||
}
|
||||
socket_num_ = socket_counter;
|
||||
}
|
||||
|
||||
bool Graph::node_indices_are_valid() const
|
||||
{
|
||||
for (const int i : nodes_.index_range()) {
|
||||
@@ -152,6 +174,21 @@ std::string DummyDebugInfo::output_name(const int /*i*/) const
|
||||
return fallback_name;
|
||||
}
|
||||
|
||||
std::string SimpleDummyDebugInfo::node_name() const
|
||||
{
|
||||
return this->name;
|
||||
}
|
||||
|
||||
std::string SimpleDummyDebugInfo::input_name(const int i) const
|
||||
{
|
||||
return this->input_names[i];
|
||||
}
|
||||
|
||||
std::string SimpleDummyDebugInfo::output_name(const int i) const
|
||||
{
|
||||
return this->output_names[i];
|
||||
}
|
||||
|
||||
std::string Graph::ToDotOptions::socket_name(const Socket &socket) const
|
||||
{
|
||||
return socket.name();
|
||||
|
||||
@@ -9,15 +9,19 @@
|
||||
|
||||
namespace blender::geometry {
|
||||
|
||||
bke::CurvesGeometry fillet_curves_poly(const bke::CurvesGeometry &src_curves,
|
||||
IndexMask curve_selection,
|
||||
const VArray<float> &radius,
|
||||
const VArray<int> &counts,
|
||||
bool limit_radius);
|
||||
bke::CurvesGeometry fillet_curves_poly(
|
||||
const bke::CurvesGeometry &src_curves,
|
||||
IndexMask curve_selection,
|
||||
const VArray<float> &radius,
|
||||
const VArray<int> &counts,
|
||||
bool limit_radius,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info);
|
||||
|
||||
bke::CurvesGeometry fillet_curves_bezier(const bke::CurvesGeometry &src_curves,
|
||||
IndexMask curve_selection,
|
||||
const VArray<float> &radius,
|
||||
bool limit_radius);
|
||||
bke::CurvesGeometry fillet_curves_bezier(
|
||||
const bke::CurvesGeometry &src_curves,
|
||||
IndexMask curve_selection,
|
||||
const VArray<float> &radius,
|
||||
bool limit_radius,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info);
|
||||
|
||||
} // namespace blender::geometry
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
|
||||
#include "BLI_index_mask.hh"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
|
||||
struct Mesh;
|
||||
|
||||
namespace blender::geometry {
|
||||
|
||||
void split_edges(Mesh &mesh, IndexMask mask);
|
||||
void split_edges(Mesh &mesh,
|
||||
IndexMask mask,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info);
|
||||
|
||||
} // namespace blender::geometry
|
||||
|
||||
@@ -19,11 +19,16 @@ namespace blender::geometry {
|
||||
* intersections of more than three edges will become breaks in curves. Attributes that
|
||||
* are not built-in on meshes and not curves are transferred to the result curve.
|
||||
*/
|
||||
bke::CurvesGeometry mesh_to_curve_convert(const Mesh &mesh, const IndexMask selection);
|
||||
bke::CurvesGeometry mesh_to_curve_convert(
|
||||
const Mesh &mesh,
|
||||
const IndexMask selection,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info);
|
||||
|
||||
bke::CurvesGeometry create_curve_from_vert_indices(const Mesh &mesh,
|
||||
Span<int> vert_indices,
|
||||
Span<int> curve_offsets,
|
||||
IndexRange cyclic_curves);
|
||||
bke::CurvesGeometry create_curve_from_vert_indices(
|
||||
const Mesh &mesh,
|
||||
Span<int> vert_indices,
|
||||
Span<int> curve_offsets,
|
||||
IndexRange cyclic_curves,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info);
|
||||
|
||||
} // namespace blender::geometry
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "BLI_index_mask.hh"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
|
||||
#pragma once
|
||||
|
||||
struct PointCloud;
|
||||
@@ -17,8 +19,10 @@ namespace blender::geometry {
|
||||
* Merge selected points into other selected points within the \a merge_distance. The merged
|
||||
* indices favor speed over accuracy, since the results will depend on the order of the points.
|
||||
*/
|
||||
PointCloud *point_merge_by_distance(const PointCloud &src_points,
|
||||
const float merge_distance,
|
||||
const IndexMask selection);
|
||||
PointCloud *point_merge_by_distance(
|
||||
const PointCloud &src_points,
|
||||
const float merge_distance,
|
||||
const IndexMask selection,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info);
|
||||
|
||||
} // namespace blender::geometry
|
||||
|
||||
@@ -19,6 +19,8 @@ struct RealizeInstancesOptions {
|
||||
* instances. Otherwise, instance attributes are ignored.
|
||||
*/
|
||||
bool realize_instance_attributes = true;
|
||||
|
||||
bke::AnonymousAttributePropagationInfo propagation_info;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include "FN_field.hh"
|
||||
|
||||
#include "BKE_anonymous_attribute.hh"
|
||||
#include "BKE_anonymous_attribute_id.hh"
|
||||
#include "BKE_curves.hh"
|
||||
|
||||
namespace blender::geometry {
|
||||
|
||||
@@ -27,6 +27,7 @@ bool try_curves_conversion_in_place(IndexMask selection,
|
||||
*/
|
||||
bke::CurvesGeometry convert_curves(const bke::CurvesGeometry &src_curves,
|
||||
IndexMask selection,
|
||||
CurveType dst_type);
|
||||
CurveType dst_type,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info);
|
||||
|
||||
} // namespace blender::geometry
|
||||
|
||||
@@ -17,8 +17,10 @@ namespace blender::geometry {
|
||||
*
|
||||
* \param selection: A selection of curves to consider when subdividing.
|
||||
*/
|
||||
bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves,
|
||||
IndexMask selection,
|
||||
const VArray<int> &cuts);
|
||||
bke::CurvesGeometry subdivide_curves(
|
||||
const bke::CurvesGeometry &src_curves,
|
||||
IndexMask selection,
|
||||
const VArray<int> &cuts,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info);
|
||||
|
||||
} // namespace blender::geometry
|
||||
|
||||
@@ -19,6 +19,7 @@ bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves,
|
||||
IndexMask selection,
|
||||
const VArray<float> &starts,
|
||||
const VArray<float> &ends,
|
||||
GeometryNodeCurveSampleMode mode);
|
||||
GeometryNodeCurveSampleMode mode,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info);
|
||||
|
||||
} // namespace blender::geometry
|
||||
|
||||
@@ -372,7 +372,7 @@ AddCurvesOnMeshOutputs add_curves_on_mesh(CurvesGeometry &curves,
|
||||
Set<std::string> attributes_to_skip{{"position", "curve_type", "surface_uv_coordinate"}};
|
||||
attributes.for_all(
|
||||
[&](const bke::AttributeIDRef &id, const bke::AttributeMetaData /*meta_data*/) {
|
||||
if (id.is_named() && attributes_to_skip.contains(id.name())) {
|
||||
if (attributes_to_skip.contains(id.name())) {
|
||||
return true;
|
||||
}
|
||||
bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
|
||||
|
||||
@@ -397,12 +397,14 @@ static void calculate_bezier_handles_poly_mode(const Span<float3> src_handles_l,
|
||||
});
|
||||
}
|
||||
|
||||
static bke::CurvesGeometry fillet_curves(const bke::CurvesGeometry &src_curves,
|
||||
const IndexMask curve_selection,
|
||||
const VArray<float> &radius_input,
|
||||
const VArray<int> &counts,
|
||||
const bool limit_radius,
|
||||
const bool use_bezier_mode)
|
||||
static bke::CurvesGeometry fillet_curves(
|
||||
const bke::CurvesGeometry &src_curves,
|
||||
const IndexMask curve_selection,
|
||||
const VArray<float> &radius_input,
|
||||
const VArray<int> &counts,
|
||||
const bool limit_radius,
|
||||
const bool use_bezier_mode,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const Vector<IndexRange> unselected_ranges = curve_selection.extract_ranges_invert(
|
||||
src_curves.curves_range());
|
||||
@@ -520,6 +522,7 @@ static bke::CurvesGeometry fillet_curves(const bke::CurvesGeometry &src_curves,
|
||||
src_attributes,
|
||||
dst_attributes,
|
||||
ATTR_DOMAIN_MASK_POINT,
|
||||
propagation_info,
|
||||
{"position", "handle_type_left", "handle_type_right", "handle_right", "handle_left"})) {
|
||||
duplicate_fillet_point_data(
|
||||
src_curves, dst_curves, curve_selection, point_offsets, attribute.src, attribute.dst.span);
|
||||
@@ -528,7 +531,7 @@ static bke::CurvesGeometry fillet_curves(const bke::CurvesGeometry &src_curves,
|
||||
|
||||
if (!unselected_ranges.is_empty()) {
|
||||
for (auto &attribute : bke::retrieve_attributes_for_transfer(
|
||||
src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) {
|
||||
src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, propagation_info)) {
|
||||
bke::curves::copy_point_data(
|
||||
src_curves, dst_curves, unselected_ranges, attribute.src, attribute.dst.span);
|
||||
attribute.dst.finish();
|
||||
@@ -538,26 +541,32 @@ static bke::CurvesGeometry fillet_curves(const bke::CurvesGeometry &src_curves,
|
||||
return dst_curves;
|
||||
}
|
||||
|
||||
bke::CurvesGeometry fillet_curves_poly(const bke::CurvesGeometry &src_curves,
|
||||
const IndexMask curve_selection,
|
||||
const VArray<float> &radius,
|
||||
const VArray<int> &count,
|
||||
const bool limit_radius)
|
||||
bke::CurvesGeometry fillet_curves_poly(
|
||||
const bke::CurvesGeometry &src_curves,
|
||||
const IndexMask curve_selection,
|
||||
const VArray<float> &radius,
|
||||
const VArray<int> &count,
|
||||
const bool limit_radius,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
return fillet_curves(src_curves, curve_selection, radius, count, limit_radius, false);
|
||||
return fillet_curves(
|
||||
src_curves, curve_selection, radius, count, limit_radius, false, propagation_info);
|
||||
}
|
||||
|
||||
bke::CurvesGeometry fillet_curves_bezier(const bke::CurvesGeometry &src_curves,
|
||||
const IndexMask curve_selection,
|
||||
const VArray<float> &radius,
|
||||
const bool limit_radius)
|
||||
bke::CurvesGeometry fillet_curves_bezier(
|
||||
const bke::CurvesGeometry &src_curves,
|
||||
const IndexMask curve_selection,
|
||||
const VArray<float> &radius,
|
||||
const bool limit_radius,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
return fillet_curves(src_curves,
|
||||
curve_selection,
|
||||
radius,
|
||||
VArray<int>::ForSingle(1, src_curves.points_num()),
|
||||
limit_radius,
|
||||
true);
|
||||
true,
|
||||
propagation_info);
|
||||
}
|
||||
|
||||
} // namespace blender::geometry
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "BLI_array_utils.hh"
|
||||
#include "BLI_devirtualize_parameters.hh"
|
||||
#include "BLI_index_mask.hh"
|
||||
#include "BLI_user_counter.hh"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_attribute_math.hh"
|
||||
@@ -59,35 +60,36 @@ static void add_new_vertices(Mesh &mesh, const Span<int> new_to_old_verts_map)
|
||||
|
||||
static void add_new_edges(Mesh &mesh,
|
||||
const Span<MEdge> new_edges,
|
||||
const Span<int> new_to_old_edges_map)
|
||||
const Span<int> new_to_old_edges_map,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
|
||||
|
||||
/* Store a copy of the IDs locally since we will remove the existing attributes which
|
||||
* can also free the names, since the API does not provide pointer stability. */
|
||||
Vector<std::string> named_ids;
|
||||
Vector<bke::WeakAnonymousAttributeID> anonymous_ids;
|
||||
Vector<UserCounter<const bke::AnonymousAttributeID>> anonymous_ids;
|
||||
for (const bke::AttributeIDRef &id : attributes.all_ids()) {
|
||||
if (attributes.lookup_meta_data(id)->domain != ATTR_DOMAIN_EDGE) {
|
||||
continue;
|
||||
}
|
||||
if (!id.should_be_kept()) {
|
||||
if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) {
|
||||
continue;
|
||||
}
|
||||
if (id.is_named()) {
|
||||
if (!id.is_anonymous()) {
|
||||
named_ids.append(id.name());
|
||||
}
|
||||
else {
|
||||
anonymous_ids.append(bke::WeakAnonymousAttributeID(&id.anonymous_id()));
|
||||
BKE_anonymous_attribute_id_increment_weak(&id.anonymous_id());
|
||||
anonymous_ids.append(&id.anonymous_id());
|
||||
id.anonymous_id().user_add();
|
||||
}
|
||||
}
|
||||
Vector<bke::AttributeIDRef> local_edge_ids;
|
||||
for (const StringRef name : named_ids) {
|
||||
local_edge_ids.append(name);
|
||||
}
|
||||
for (const bke::WeakAnonymousAttributeID &id : anonymous_ids) {
|
||||
local_edge_ids.append(id.get());
|
||||
for (const UserCounter<const bke::AnonymousAttributeID> &id : anonymous_ids) {
|
||||
local_edge_ids.append(*id);
|
||||
}
|
||||
|
||||
/* Build new arrays for the copied edge attributes. Unlike vertices, new edges aren't all at the
|
||||
@@ -348,7 +350,9 @@ static void split_edge_per_poly(const int edge_i,
|
||||
edge_to_loop_map[edge_i].resize(1);
|
||||
}
|
||||
|
||||
void split_edges(Mesh &mesh, const IndexMask mask)
|
||||
void split_edges(Mesh &mesh,
|
||||
const IndexMask mask,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
/* Flag vertices that need to be split. */
|
||||
Array<bool> should_split_vert(mesh.totvert, false);
|
||||
@@ -483,7 +487,7 @@ void split_edges(Mesh &mesh, const IndexMask mask)
|
||||
|
||||
/* Step 5: Resize the mesh to add the new vertices and rebuild the edges. */
|
||||
add_new_vertices(mesh, new_to_old_verts_map);
|
||||
add_new_edges(mesh, new_edges, new_to_old_edges_map);
|
||||
add_new_edges(mesh, new_edges, new_to_old_edges_map, propagation_info);
|
||||
|
||||
BKE_mesh_tag_edges_split(&mesh);
|
||||
}
|
||||
|
||||
@@ -19,10 +19,12 @@
|
||||
|
||||
namespace blender::geometry {
|
||||
|
||||
bke::CurvesGeometry create_curve_from_vert_indices(const Mesh &mesh,
|
||||
const Span<int> vert_indices,
|
||||
const Span<int> curve_offsets,
|
||||
const IndexRange cyclic_curves)
|
||||
bke::CurvesGeometry create_curve_from_vert_indices(
|
||||
const Mesh &mesh,
|
||||
const Span<int> vert_indices,
|
||||
const Span<int> curve_offsets,
|
||||
const IndexRange cyclic_curves,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
bke::CurvesGeometry curves(vert_indices.size(), curve_offsets.size());
|
||||
curves.offsets_for_write().drop_back(1).copy_from(curve_offsets);
|
||||
@@ -43,7 +45,7 @@ bke::CurvesGeometry create_curve_from_vert_indices(const Mesh &mesh,
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!attribute_id.should_be_kept()) {
|
||||
if (attribute_id.is_anonymous() && !propagation_info.propagate(attribute_id.anonymous_id())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -209,14 +211,17 @@ static Vector<std::pair<int, int>> get_selected_edges(const Mesh &mesh, const In
|
||||
return selected_edges;
|
||||
}
|
||||
|
||||
bke::CurvesGeometry mesh_to_curve_convert(const Mesh &mesh, const IndexMask selection)
|
||||
bke::CurvesGeometry mesh_to_curve_convert(
|
||||
const Mesh &mesh,
|
||||
const IndexMask selection,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
Vector<std::pair<int, int>> selected_edges = get_selected_edges(mesh, selection);
|
||||
const Span<MVert> verts = mesh.verts();
|
||||
CurveFromEdgesOutput output = edges_to_curve_point_indices(verts, selected_edges);
|
||||
|
||||
return create_curve_from_vert_indices(
|
||||
mesh, output.vert_indices, output.curve_offsets, output.cyclic_curves);
|
||||
mesh, output.vert_indices, output.curve_offsets, output.cyclic_curves, propagation_info);
|
||||
}
|
||||
|
||||
} // namespace blender::geometry
|
||||
|
||||
@@ -15,7 +15,8 @@ namespace blender::geometry {
|
||||
|
||||
PointCloud *point_merge_by_distance(const PointCloud &src_points,
|
||||
const float merge_distance,
|
||||
const IndexMask selection)
|
||||
const IndexMask selection,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const bke::AttributeAccessor src_attributes = src_points.attributes();
|
||||
VArraySpan<float3> positions = src_attributes.lookup_or_default<float3>(
|
||||
@@ -125,7 +126,7 @@ PointCloud *point_merge_by_distance(const PointCloud &src_points,
|
||||
|
||||
/* Transfer all other attributes. */
|
||||
for (const bke::AttributeIDRef &id : attribute_ids) {
|
||||
if (!id.should_be_kept()) {
|
||||
if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -636,8 +636,11 @@ static OrderedAttributes gather_generic_pointcloud_attributes_to_propagate(
|
||||
}
|
||||
|
||||
Map<AttributeIDRef, AttributeKind> attributes_to_propagate;
|
||||
in_geometry_set.gather_attributes_for_propagation(
|
||||
src_component_types, GEO_COMPONENT_TYPE_POINT_CLOUD, true, attributes_to_propagate);
|
||||
in_geometry_set.gather_attributes_for_propagation(src_component_types,
|
||||
GEO_COMPONENT_TYPE_POINT_CLOUD,
|
||||
true,
|
||||
options.propagation_info,
|
||||
attributes_to_propagate);
|
||||
attributes_to_propagate.remove("position");
|
||||
r_create_id = attributes_to_propagate.pop_try("id").has_value();
|
||||
r_create_radii = attributes_to_propagate.pop_try("radius").has_value();
|
||||
@@ -829,8 +832,11 @@ static OrderedAttributes gather_generic_mesh_attributes_to_propagate(
|
||||
}
|
||||
|
||||
Map<AttributeIDRef, AttributeKind> attributes_to_propagate;
|
||||
in_geometry_set.gather_attributes_for_propagation(
|
||||
src_component_types, GEO_COMPONENT_TYPE_MESH, true, attributes_to_propagate);
|
||||
in_geometry_set.gather_attributes_for_propagation(src_component_types,
|
||||
GEO_COMPONENT_TYPE_MESH,
|
||||
true,
|
||||
options.propagation_info,
|
||||
attributes_to_propagate);
|
||||
attributes_to_propagate.remove("position");
|
||||
attributes_to_propagate.remove("normal");
|
||||
attributes_to_propagate.remove("shade_smooth");
|
||||
@@ -1149,8 +1155,11 @@ static OrderedAttributes gather_generic_curve_attributes_to_propagate(
|
||||
}
|
||||
|
||||
Map<AttributeIDRef, AttributeKind> attributes_to_propagate;
|
||||
in_geometry_set.gather_attributes_for_propagation(
|
||||
src_component_types, GEO_COMPONENT_TYPE_CURVE, true, attributes_to_propagate);
|
||||
in_geometry_set.gather_attributes_for_propagation(src_component_types,
|
||||
GEO_COMPONENT_TYPE_CURVE,
|
||||
true,
|
||||
options.propagation_info,
|
||||
attributes_to_propagate);
|
||||
attributes_to_propagate.remove("position");
|
||||
attributes_to_propagate.remove("radius");
|
||||
attributes_to_propagate.remove("resolution");
|
||||
|
||||
@@ -53,7 +53,7 @@ static fn::Field<int> get_count_input_from_length(const fn::Field<float> &length
|
||||
static bool interpolate_attribute_to_curves(const bke::AttributeIDRef &attribute_id,
|
||||
const std::array<int, CURVE_TYPES_NUM> &type_counts)
|
||||
{
|
||||
if (!attribute_id.is_named()) {
|
||||
if (attribute_id.is_anonymous()) {
|
||||
return true;
|
||||
}
|
||||
if (ELEM(attribute_id.name(),
|
||||
@@ -81,7 +81,7 @@ static bool interpolate_attribute_to_poly_curve(const bke::AttributeIDRef &attri
|
||||
"handle_left",
|
||||
"nurbs_weight",
|
||||
}};
|
||||
return !(attribute_id.is_named() && no_interpolation.contains(attribute_id.name()));
|
||||
return !no_interpolation.contains(attribute_id.name());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -286,8 +286,10 @@ static void retrieve_curve_sizes(const bke::CurvesGeometry &curves, MutableSpan<
|
||||
});
|
||||
}
|
||||
|
||||
static bke::CurvesGeometry convert_curves_to_bezier(const bke::CurvesGeometry &src_curves,
|
||||
const IndexMask selection)
|
||||
static bke::CurvesGeometry convert_curves_to_bezier(
|
||||
const bke::CurvesGeometry &src_curves,
|
||||
const IndexMask selection,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const VArray<int8_t> src_knot_modes = src_curves.nurbs_knots_modes();
|
||||
const VArray<int8_t> src_types = src_curves.curve_types();
|
||||
@@ -315,6 +317,7 @@ static bke::CurvesGeometry convert_curves_to_bezier(const bke::CurvesGeometry &s
|
||||
src_attributes,
|
||||
dst_attributes,
|
||||
ATTR_DOMAIN_MASK_POINT,
|
||||
propagation_info,
|
||||
{"position",
|
||||
"handle_type_left",
|
||||
"handle_type_right",
|
||||
@@ -460,8 +463,10 @@ static bke::CurvesGeometry convert_curves_to_bezier(const bke::CurvesGeometry &s
|
||||
return dst_curves;
|
||||
}
|
||||
|
||||
static bke::CurvesGeometry convert_curves_to_nurbs(const bke::CurvesGeometry &src_curves,
|
||||
const IndexMask selection)
|
||||
static bke::CurvesGeometry convert_curves_to_nurbs(
|
||||
const bke::CurvesGeometry &src_curves,
|
||||
const IndexMask selection,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const VArray<int8_t> src_types = src_curves.curve_types();
|
||||
const VArray<bool> src_cyclic = src_curves.cyclic();
|
||||
@@ -487,6 +492,7 @@ static bke::CurvesGeometry convert_curves_to_nurbs(const bke::CurvesGeometry &sr
|
||||
src_attributes,
|
||||
dst_attributes,
|
||||
ATTR_DOMAIN_MASK_POINT,
|
||||
propagation_info,
|
||||
{"position",
|
||||
"handle_type_left",
|
||||
"handle_type_right",
|
||||
@@ -639,16 +645,17 @@ static bke::CurvesGeometry convert_curves_trivial(const bke::CurvesGeometry &src
|
||||
|
||||
bke::CurvesGeometry convert_curves(const bke::CurvesGeometry &src_curves,
|
||||
const IndexMask selection,
|
||||
const CurveType dst_type)
|
||||
const CurveType dst_type,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
switch (dst_type) {
|
||||
case CURVE_TYPE_CATMULL_ROM:
|
||||
case CURVE_TYPE_POLY:
|
||||
return convert_curves_trivial(src_curves, selection, dst_type);
|
||||
case CURVE_TYPE_BEZIER:
|
||||
return convert_curves_to_bezier(src_curves, selection);
|
||||
return convert_curves_to_bezier(src_curves, selection, propagation_info);
|
||||
case CURVE_TYPE_NURBS:
|
||||
return convert_curves_to_nurbs(src_curves, selection);
|
||||
return convert_curves_to_nurbs(src_curves, selection, propagation_info);
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
return {};
|
||||
|
||||
@@ -294,9 +294,11 @@ static void subdivide_bezier_positions(const Span<float3> src_positions,
|
||||
cyclic, dst_types_l, dst_types_r, dst_positions, dst_handles_l, dst_handles_r);
|
||||
}
|
||||
|
||||
bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves,
|
||||
const IndexMask selection,
|
||||
const VArray<int> &cuts)
|
||||
bke::CurvesGeometry subdivide_curves(
|
||||
const bke::CurvesGeometry &src_curves,
|
||||
const IndexMask selection,
|
||||
const VArray<int> &cuts,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const Vector<IndexRange> unselected_ranges = selection.extract_ranges_invert(
|
||||
src_curves.curves_range());
|
||||
@@ -338,7 +340,7 @@ bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves,
|
||||
|
||||
auto subdivide_catmull_rom = [&](IndexMask selection) {
|
||||
for (auto &attribute : bke::retrieve_attributes_for_transfer(
|
||||
src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) {
|
||||
src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, propagation_info)) {
|
||||
subdivide_attribute_catmull_rom(src_curves,
|
||||
dst_curves,
|
||||
selection,
|
||||
@@ -352,7 +354,7 @@ bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves,
|
||||
|
||||
auto subdivide_poly = [&](IndexMask selection) {
|
||||
for (auto &attribute : bke::retrieve_attributes_for_transfer(
|
||||
src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) {
|
||||
src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, propagation_info)) {
|
||||
subdivide_attribute_linear(
|
||||
src_curves, dst_curves, selection, point_offsets, attribute.src, attribute.dst.span);
|
||||
attribute.dst.finish();
|
||||
@@ -396,6 +398,7 @@ bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves,
|
||||
for (auto &attribute : bke::retrieve_attributes_for_transfer(src_attributes,
|
||||
dst_attributes,
|
||||
ATTR_DOMAIN_MASK_POINT,
|
||||
propagation_info,
|
||||
{"position",
|
||||
"handle_type_left",
|
||||
"handle_type_right",
|
||||
@@ -421,7 +424,7 @@ bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves,
|
||||
|
||||
if (!unselected_ranges.is_empty()) {
|
||||
for (auto &attribute : bke::retrieve_attributes_for_transfer(
|
||||
src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) {
|
||||
src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, propagation_info)) {
|
||||
bke::curves::copy_point_data(
|
||||
src_curves, dst_curves, unselected_ranges, attribute.src, attribute.dst.span);
|
||||
attribute.dst.finish();
|
||||
|
||||
@@ -929,7 +929,8 @@ bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves,
|
||||
const IndexMask selection,
|
||||
const VArray<float> &starts,
|
||||
const VArray<float> &ends,
|
||||
const GeometryNodeCurveSampleMode mode)
|
||||
const GeometryNodeCurveSampleMode mode,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
BLI_assert(selection.size() > 0);
|
||||
BLI_assert(selection.last() <= src_curves.curves_num());
|
||||
@@ -991,14 +992,19 @@ bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves,
|
||||
transfer_curve_skip.remove("nurbs_order");
|
||||
transfer_curve_skip.remove("knots_mode");
|
||||
}
|
||||
bke::copy_attribute_domain(
|
||||
src_attributes, dst_attributes, selection, ATTR_DOMAIN_CURVE, transfer_curve_skip);
|
||||
bke::copy_attribute_domain(src_attributes,
|
||||
dst_attributes,
|
||||
selection,
|
||||
ATTR_DOMAIN_CURVE,
|
||||
propagation_info,
|
||||
transfer_curve_skip);
|
||||
|
||||
/* Fetch custom point domain attributes for transfer (copy). */
|
||||
Vector<bke::AttributeTransferData> transfer_attributes = bke::retrieve_attributes_for_transfer(
|
||||
src_attributes,
|
||||
dst_attributes,
|
||||
ATTR_DOMAIN_MASK_POINT,
|
||||
propagation_info,
|
||||
{"position",
|
||||
"handle_left",
|
||||
"handle_right",
|
||||
@@ -1063,8 +1069,12 @@ bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves,
|
||||
/* Copy unselected */
|
||||
if (!inverse_selection.is_empty()) {
|
||||
transfer_curve_skip.remove("cyclic");
|
||||
bke::copy_attribute_domain(
|
||||
src_attributes, dst_attributes, inverse_selection, ATTR_DOMAIN_CURVE, transfer_curve_skip);
|
||||
bke::copy_attribute_domain(src_attributes,
|
||||
dst_attributes,
|
||||
inverse_selection,
|
||||
ATTR_DOMAIN_CURVE,
|
||||
propagation_info,
|
||||
transfer_curve_skip);
|
||||
/* Trim curves are no longer cyclic. If all curves are trimmed, this will be set implicitly. */
|
||||
dst_curves.cyclic_for_write().fill_indices(selection, false);
|
||||
|
||||
@@ -1075,8 +1085,11 @@ bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves,
|
||||
}
|
||||
|
||||
/* Copy point domain. */
|
||||
for (auto &attribute : bke::retrieve_attributes_for_transfer(
|
||||
src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, copy_point_skip)) {
|
||||
for (auto &attribute : bke::retrieve_attributes_for_transfer(src_attributes,
|
||||
dst_attributes,
|
||||
ATTR_DOMAIN_MASK_POINT,
|
||||
propagation_info,
|
||||
copy_point_skip)) {
|
||||
bke::curves::copy_point_data(
|
||||
src_curves, dst_curves, inverse_selection, attribute.src, attribute.dst.span);
|
||||
attribute.dst.finish();
|
||||
|
||||
@@ -15,7 +15,15 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct AnonymousAttributeID;
|
||||
/** Workaround to forward-declare C++ type in C header. */
|
||||
#ifdef __cplusplus
|
||||
namespace blender::bke {
|
||||
class AnonymousAttributeID;
|
||||
} // namespace blender::bke
|
||||
using AnonymousAttributeIDHandle = blender::bke::AnonymousAttributeID;
|
||||
#else
|
||||
typedef struct AnonymousAttributeIDHandle AnonymousAttributeIDHandle;
|
||||
#endif
|
||||
|
||||
/** Descriptor and storage for a custom data layer. */
|
||||
typedef struct CustomDataLayer {
|
||||
@@ -40,12 +48,10 @@ typedef struct CustomDataLayer {
|
||||
/** Layer data. */
|
||||
void *data;
|
||||
/**
|
||||
* Run-time identifier for this layer. If no one has a strong reference to this id anymore,
|
||||
* the layer can be removed. The custom data layer only has a weak reference to the id, because
|
||||
* otherwise there will always be a strong reference and the attribute can't be removed
|
||||
* automatically.
|
||||
* Run-time identifier for this layer. Can be used to retrieve information about where this
|
||||
* attribute was created.
|
||||
*/
|
||||
const struct AnonymousAttributeID *anonymous_id;
|
||||
const AnonymousAttributeIDHandle *anonymous_id;
|
||||
} CustomDataLayer;
|
||||
|
||||
#define MAX_CUSTOMDATA_LAYER_NAME 64
|
||||
|
||||
@@ -884,12 +884,12 @@ static void find_side_effect_nodes_for_viewer_path(
|
||||
|
||||
/* Not only mark the viewer node as having side effects, but also all group nodes it is contained
|
||||
* in. */
|
||||
r_side_effect_nodes.add(compute_context_builder.hash(),
|
||||
&find_viewer_lf_node(*found_viewer_node));
|
||||
r_side_effect_nodes.add_non_duplicates(compute_context_builder.hash(),
|
||||
&find_viewer_lf_node(*found_viewer_node));
|
||||
compute_context_builder.pop();
|
||||
while (!compute_context_builder.is_empty()) {
|
||||
r_side_effect_nodes.add(compute_context_builder.hash(),
|
||||
&find_group_lf_node(*group_node_stack.pop()));
|
||||
r_side_effect_nodes.add_non_duplicates(compute_context_builder.hash(),
|
||||
&find_group_lf_node(*group_node_stack.pop()));
|
||||
compute_context_builder.pop();
|
||||
}
|
||||
}
|
||||
@@ -1124,12 +1124,11 @@ static GeometrySet compute_geometry(
|
||||
{
|
||||
const blender::nodes::GeometryNodeLazyFunctionGraphMapping &mapping = lf_graph_info.mapping;
|
||||
|
||||
Span<const lf::OutputSocket *> graph_inputs = mapping.group_input_sockets;
|
||||
Vector<const lf::InputSocket *> graph_outputs;
|
||||
for (const bNodeSocket *bsocket : output_node.input_sockets().drop_back(1)) {
|
||||
const lf::InputSocket &socket = mapping.dummy_socket_map.lookup(bsocket)->as_input();
|
||||
graph_outputs.append(&socket);
|
||||
}
|
||||
Vector<const lf::OutputSocket *> graph_inputs = mapping.group_input_sockets;
|
||||
graph_inputs.extend(mapping.group_output_used_sockets);
|
||||
graph_inputs.extend(mapping.attribute_set_by_geometry_output.values().begin(),
|
||||
mapping.attribute_set_by_geometry_output.values().end());
|
||||
Vector<const lf::InputSocket *> graph_outputs = mapping.standard_group_output_sockets;
|
||||
|
||||
Array<GMutablePointer> param_inputs(graph_inputs.size());
|
||||
Array<GMutablePointer> param_outputs(graph_outputs.size());
|
||||
@@ -1166,21 +1165,36 @@ static GeometrySet compute_geometry(
|
||||
blender::LinearAllocator<> allocator;
|
||||
Vector<GMutablePointer> inputs_to_destruct;
|
||||
|
||||
int input_index;
|
||||
LISTBASE_FOREACH_INDEX (bNodeSocket *, interface_socket, &btree.inputs, input_index) {
|
||||
if (interface_socket->type == SOCK_GEOMETRY && input_index == 0) {
|
||||
int input_index = -1;
|
||||
for (const int i : btree.interface_inputs().index_range()) {
|
||||
input_index++;
|
||||
const bNodeSocket &interface_socket = *btree.interface_inputs()[i];
|
||||
if (interface_socket.type == SOCK_GEOMETRY && input_index == 0) {
|
||||
param_inputs[input_index] = &input_geometry_set;
|
||||
continue;
|
||||
}
|
||||
|
||||
const CPPType *type = interface_socket->typeinfo->geometry_nodes_cpp_type;
|
||||
const CPPType *type = interface_socket.typeinfo->geometry_nodes_cpp_type;
|
||||
BLI_assert(type != nullptr);
|
||||
void *value = allocator.allocate(type->size(), type->alignment());
|
||||
initialize_group_input(*nmd, *interface_socket, input_index, value);
|
||||
initialize_group_input(*nmd, interface_socket, i, value);
|
||||
param_inputs[input_index] = {type, value};
|
||||
inputs_to_destruct.append({type, value});
|
||||
}
|
||||
|
||||
Array<bool> output_used_inputs(btree.interface_outputs().size(), true);
|
||||
for (const int i : btree.interface_outputs().index_range()) {
|
||||
input_index++;
|
||||
param_inputs[input_index] = &output_used_inputs[i];
|
||||
}
|
||||
|
||||
Array<blender::bke::AnonymousAttributeSet> attributes_to_propagate(
|
||||
mapping.attribute_set_by_geometry_output.size());
|
||||
for (const int i : attributes_to_propagate.index_range()) {
|
||||
input_index++;
|
||||
param_inputs[input_index] = &attributes_to_propagate[i];
|
||||
}
|
||||
|
||||
for (const int i : graph_outputs.index_range()) {
|
||||
const lf::InputSocket &socket = *graph_outputs[i];
|
||||
const CPPType &type = socket.type();
|
||||
|
||||
@@ -19,6 +19,8 @@ struct ModifierData;
|
||||
namespace blender::nodes {
|
||||
|
||||
using bke::AnonymousAttributeFieldInput;
|
||||
using bke::AnonymousAttributeID;
|
||||
using bke::AnonymousAttributePropagationInfo;
|
||||
using bke::AttributeAccessor;
|
||||
using bke::AttributeFieldInput;
|
||||
using bke::AttributeIDRef;
|
||||
@@ -26,13 +28,12 @@ using bke::AttributeKind;
|
||||
using bke::AttributeMetaData;
|
||||
using bke::AttributeReader;
|
||||
using bke::AttributeWriter;
|
||||
using bke::AutoAnonymousAttributeID;
|
||||
using bke::GAttributeReader;
|
||||
using bke::GAttributeWriter;
|
||||
using bke::GSpanAttributeWriter;
|
||||
using bke::MutableAttributeAccessor;
|
||||
using bke::SpanAttributeWriter;
|
||||
using bke::StrongAnonymousAttributeID;
|
||||
using bke::WeakAnonymousAttributeID;
|
||||
using fn::Field;
|
||||
using fn::FieldContext;
|
||||
using fn::FieldEvaluator;
|
||||
@@ -43,15 +44,38 @@ using fn::ValueOrField;
|
||||
using geo_eval_log::NamedAttributeUsage;
|
||||
using geo_eval_log::NodeWarningType;
|
||||
|
||||
/**
|
||||
* An anonymous attribute created by a node.
|
||||
*/
|
||||
class NodeAnonymousAttributeID : public AnonymousAttributeID {
|
||||
std::string long_name_;
|
||||
|
||||
public:
|
||||
NodeAnonymousAttributeID(const Object &object,
|
||||
const ComputeContext &compute_context,
|
||||
const bNode &bnode,
|
||||
const StringRef identifier);
|
||||
};
|
||||
|
||||
class GeoNodeExecParams {
|
||||
private:
|
||||
const bNode &node_;
|
||||
lf::Params ¶ms_;
|
||||
const lf::Context &lf_context_;
|
||||
const Map<StringRef, int> &lf_input_for_output_bsocket_usage_;
|
||||
const Map<StringRef, int> &lf_input_for_attribute_propagation_to_output_;
|
||||
|
||||
public:
|
||||
GeoNodeExecParams(const bNode &node, lf::Params ¶ms, const lf::Context &lf_context)
|
||||
: node_(node), params_(params), lf_context_(lf_context)
|
||||
GeoNodeExecParams(const bNode &node,
|
||||
lf::Params ¶ms,
|
||||
const lf::Context &lf_context,
|
||||
const Map<StringRef, int> &lf_input_for_output_bsocket_usage,
|
||||
const Map<StringRef, int> &lf_input_for_attribute_propagation_to_output)
|
||||
: node_(node),
|
||||
params_(params),
|
||||
lf_context_(lf_context),
|
||||
lf_input_for_output_bsocket_usage_(lf_input_for_output_bsocket_usage),
|
||||
lf_input_for_attribute_propagation_to_output_(lf_input_for_attribute_propagation_to_output)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -245,6 +269,49 @@ class GeoNodeExecParams {
|
||||
|
||||
void used_named_attribute(StringRef attribute_name, NamedAttributeUsage usage);
|
||||
|
||||
/**
|
||||
* Return true when the anonymous attribute referenced by the given output should be created.
|
||||
*/
|
||||
bool anonymous_attribute_output_is_required(const StringRef output_identifier)
|
||||
{
|
||||
const int lf_index = lf_input_for_output_bsocket_usage_.lookup(output_identifier);
|
||||
return params_.get_input<bool>(lf_index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new anonymous attribute id for the given output. None is returned if the anonymous
|
||||
* attribute is not needed.
|
||||
*/
|
||||
AutoAnonymousAttributeID get_output_anonymous_attribute_id_if_needed(
|
||||
const StringRef output_identifier)
|
||||
{
|
||||
if (!this->anonymous_attribute_output_is_required(output_identifier)) {
|
||||
return {};
|
||||
}
|
||||
const GeoNodesLFUserData &user_data = *this->user_data();
|
||||
const ComputeContext &compute_context = *user_data.compute_context;
|
||||
return MEM_new<NodeAnonymousAttributeID>(__func__,
|
||||
*user_data.modifier_data->self_object,
|
||||
compute_context,
|
||||
node_,
|
||||
output_identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about which anonymous attributes should be propagated to the given output.
|
||||
*/
|
||||
AnonymousAttributePropagationInfo get_output_propagation_info(
|
||||
const StringRef output_identifier) const
|
||||
{
|
||||
const int lf_index = lf_input_for_attribute_propagation_to_output_.lookup(output_identifier);
|
||||
const bke::AnonymousAttributeSet &set = params_.get_input<bke::AnonymousAttributeSet>(
|
||||
lf_index);
|
||||
AnonymousAttributePropagationInfo info;
|
||||
info.names = set.names;
|
||||
info.propagate_all = false;
|
||||
return info;
|
||||
}
|
||||
|
||||
private:
|
||||
/* Utilities for detecting common errors at when using this class. */
|
||||
void check_input_access(StringRef identifier, const CPPType *requested_type = nullptr) const;
|
||||
|
||||
@@ -78,6 +78,28 @@ struct GeoNodesLFUserData : public lf::UserData {
|
||||
bool log_socket_values = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* In the general case, this is #DynamicSocket. That means that to determine if a node group will
|
||||
* use a particular input, it has to be partially executed.
|
||||
*
|
||||
* In other cases, it's not necessary to look into the node group to determine if an input is
|
||||
* necessary.
|
||||
*/
|
||||
enum class InputUsageHintType {
|
||||
/** The input socket is never used. */
|
||||
Never,
|
||||
/** The input socket is used when a subset of the outputs is used. */
|
||||
DependsOnOutput,
|
||||
/** Can't determine statically if the input is used, check the corresponding output socket. */
|
||||
DynamicSocket,
|
||||
};
|
||||
|
||||
struct InputUsageHint {
|
||||
InputUsageHintType type = InputUsageHintType::DependsOnOutput;
|
||||
/** Used in depends-on-output mode. */
|
||||
Vector<int> output_dependencies;
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains the mapping between the #bNodeTree and the corresponding lazy-function graph.
|
||||
* This is *not* a one-to-one mapping.
|
||||
@@ -91,7 +113,32 @@ struct GeometryNodeLazyFunctionGraphMapping {
|
||||
* The inputs sockets in the graph. Multiple group input nodes are combined into one in the
|
||||
* lazy-function graph.
|
||||
*/
|
||||
Vector<lf::OutputSocket *> group_input_sockets;
|
||||
Vector<const lf::OutputSocket *> group_input_sockets;
|
||||
/**
|
||||
* Dummy output sockets that correspond to the active group output node. If there is no such
|
||||
* node, defaulted fallback outputs are created.
|
||||
*/
|
||||
Vector<const lf::InputSocket *> standard_group_output_sockets;
|
||||
/**
|
||||
* Dummy boolean sockets that have to be passed in from the outside and indicate whether a
|
||||
* specific output will be used.
|
||||
*/
|
||||
Vector<const lf::OutputSocket *> group_output_used_sockets;
|
||||
/**
|
||||
* Dummy boolean sockets that can be used as group output that indicate whether a specific input
|
||||
* will be used (this may depend on the used outputs as well as other inputs).
|
||||
*/
|
||||
Vector<const lf::InputSocket *> group_input_usage_sockets;
|
||||
/**
|
||||
* This is an optimization to avoid partially evaluating a node group just to figure out which
|
||||
* inputs are needed.
|
||||
*/
|
||||
Vector<InputUsageHint> group_input_usage_hints;
|
||||
/**
|
||||
* If the node group propagates attributes from an input geometry to the output, it has to know
|
||||
* which attributes should be propagated and which can be removed (for optimization purposes).
|
||||
*/
|
||||
Map<int, const lf::OutputSocket *> attribute_set_by_geometry_output;
|
||||
/**
|
||||
* A mapping used for logging intermediate values.
|
||||
*/
|
||||
|
||||
@@ -50,10 +50,10 @@ Mesh *create_grid_mesh(
|
||||
int verts_x, int verts_y, float size_x, float size_y, const AttributeIDRef &uv_map_id);
|
||||
|
||||
struct ConeAttributeOutputs {
|
||||
StrongAnonymousAttributeID top_id;
|
||||
StrongAnonymousAttributeID bottom_id;
|
||||
StrongAnonymousAttributeID side_id;
|
||||
StrongAnonymousAttributeID uv_map_id;
|
||||
AutoAnonymousAttributeID top_id;
|
||||
AutoAnonymousAttributeID bottom_id;
|
||||
AutoAnonymousAttributeID side_id;
|
||||
AutoAnonymousAttributeID uv_map_id;
|
||||
};
|
||||
|
||||
Mesh *create_cylinder_or_cone_mesh(float radius_top,
|
||||
@@ -81,6 +81,7 @@ void separate_geometry(GeometrySet &geometry_set,
|
||||
eAttrDomain domain,
|
||||
GeometryNodeDeleteGeometryMode mode,
|
||||
const Field<bool> &selection_field,
|
||||
const AnonymousAttributePropagationInfo &propagation_info,
|
||||
bool &r_is_error);
|
||||
|
||||
void get_closest_in_bvhtree(BVHTreeFromMesh &tree_data,
|
||||
|
||||
@@ -142,9 +142,12 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
const eAttrDomain domain = eAttrDomain(storage.domain);
|
||||
|
||||
const std::string output_identifier = "Attribute" + identifier_suffix(data_type);
|
||||
AutoAnonymousAttributeID attribute_id = params.get_output_anonymous_attribute_id_if_needed(
|
||||
output_identifier);
|
||||
|
||||
if (!params.output_is_required(output_identifier)) {
|
||||
if (!attribute_id) {
|
||||
params.set_output("Geometry", geometry_set);
|
||||
params.set_default_remaining_outputs();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -171,7 +174,6 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
break;
|
||||
}
|
||||
|
||||
WeakAnonymousAttributeID anonymous_id{"Attribute"};
|
||||
const CPPType &type = field.cpp_type();
|
||||
|
||||
/* Run on the instances component separately to only affect the top level of instances. */
|
||||
@@ -179,7 +181,7 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
if (geometry_set.has_instances()) {
|
||||
GeometryComponent &component = geometry_set.get_component_for_write(
|
||||
GEO_COMPONENT_TYPE_INSTANCES);
|
||||
bke::try_capture_field_on_geometry(component, anonymous_id.get(), domain, field);
|
||||
bke::try_capture_field_on_geometry(component, *attribute_id, domain, field);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -190,14 +192,14 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
for (const GeometryComponentType type : types) {
|
||||
if (geometry_set.has(type)) {
|
||||
GeometryComponent &component = geometry_set.get_component_for_write(type);
|
||||
bke::try_capture_field_on_geometry(component, anonymous_id.get(), domain, field);
|
||||
bke::try_capture_field_on_geometry(component, *attribute_id, domain, field);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
GField output_field{std::make_shared<bke::AnonymousAttributeFieldInput>(
|
||||
std::move(anonymous_id), type, params.attribute_producer_name())};
|
||||
std::move(attribute_id), type, params.attribute_producer_name())};
|
||||
|
||||
switch (data_type) {
|
||||
case CD_PROP_FLOAT: {
|
||||
|
||||
@@ -30,7 +30,7 @@ static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
}
|
||||
|
||||
struct AttributeOutputs {
|
||||
StrongAnonymousAttributeID intersecting_edges_id;
|
||||
AutoAnonymousAttributeID intersecting_edges_id;
|
||||
};
|
||||
|
||||
static void node_update(bNodeTree *ntree, bNode *node)
|
||||
@@ -125,9 +125,8 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
}
|
||||
|
||||
AttributeOutputs attribute_outputs;
|
||||
if (params.output_is_required("Intersecting Edges")) {
|
||||
attribute_outputs.intersecting_edges_id = StrongAnonymousAttributeID("Intersecting Edges");
|
||||
}
|
||||
attribute_outputs.intersecting_edges_id = params.get_output_anonymous_attribute_id_if_needed(
|
||||
"Intersecting Edges");
|
||||
|
||||
Vector<int> intersecting_edges;
|
||||
Mesh *result = blender::meshintersect::direct_mesh_boolean(
|
||||
|
||||
@@ -67,6 +67,9 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
count_field.emplace(params.extract_input<Field<int>>("Count"));
|
||||
}
|
||||
|
||||
const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info(
|
||||
"Curve");
|
||||
|
||||
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
|
||||
if (!geometry_set.has_curves()) {
|
||||
return;
|
||||
@@ -82,7 +85,11 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
case GEO_NODE_CURVE_FILLET_BEZIER: {
|
||||
evaluator.evaluate();
|
||||
bke::CurvesGeometry dst_curves = geometry::fillet_curves_bezier(
|
||||
curves, curves.curves_range(), evaluator.get_evaluated<float>(0), limit_radius);
|
||||
curves,
|
||||
curves.curves_range(),
|
||||
evaluator.get_evaluated<float>(0),
|
||||
limit_radius,
|
||||
propagation_info);
|
||||
Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves));
|
||||
bke::curves_copy_parameters(curves_id, *dst_curves_id);
|
||||
geometry_set.replace_curves(dst_curves_id);
|
||||
@@ -96,7 +103,8 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
curves.curves_range(),
|
||||
evaluator.get_evaluated<float>(0),
|
||||
evaluator.get_evaluated<int>(1),
|
||||
limit_radius);
|
||||
limit_radius,
|
||||
propagation_info);
|
||||
Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves));
|
||||
bke::curves_copy_parameters(curves_id, *dst_curves_id);
|
||||
geometry_set.replace_curves(dst_curves_id);
|
||||
|
||||
@@ -59,10 +59,10 @@ static Curves *create_star_curve(const float inner_radius,
|
||||
}
|
||||
|
||||
static void create_selection_output(CurveComponent &component,
|
||||
StrongAnonymousAttributeID &r_attribute)
|
||||
AutoAnonymousAttributeID &r_attribute)
|
||||
{
|
||||
SpanAttributeWriter<bool> selection =
|
||||
component.attributes_for_write()->lookup_or_add_for_write_only_span<bool>(r_attribute.get(),
|
||||
component.attributes_for_write()->lookup_or_add_for_write_only_span<bool>(*r_attribute,
|
||||
ATTR_DOMAIN_POINT);
|
||||
for (int i : selection.span.index_range()) {
|
||||
selection.span[i] = i % 2 == 0;
|
||||
@@ -78,12 +78,12 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
std::max(params.extract_input<int>("Points"), 3));
|
||||
GeometrySet output = GeometrySet::create_with_curves(curves);
|
||||
|
||||
if (params.output_is_required("Outer Points")) {
|
||||
StrongAnonymousAttributeID attribute_output("Outer Points");
|
||||
create_selection_output(output.get_component_for_write<CurveComponent>(), attribute_output);
|
||||
if (AutoAnonymousAttributeID outer_points_id =
|
||||
params.get_output_anonymous_attribute_id_if_needed("Outer Points")) {
|
||||
create_selection_output(output.get_component_for_write<CurveComponent>(), outer_points_id);
|
||||
params.set_output("Outer Points",
|
||||
AnonymousAttributeFieldInput::Create<bool>(
|
||||
std::move(attribute_output), params.attribute_producer_name()));
|
||||
std::move(outer_points_id), params.attribute_producer_name()));
|
||||
}
|
||||
params.set_output("Curve", std::move(output));
|
||||
}
|
||||
|
||||
@@ -67,7 +67,8 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
return;
|
||||
}
|
||||
|
||||
bke::CurvesGeometry dst_curves = geometry::convert_curves(src_curves, selection, dst_type);
|
||||
bke::CurvesGeometry dst_curves = geometry::convert_curves(
|
||||
src_curves, selection, dst_type, params.get_output_propagation_info("Curve"));
|
||||
|
||||
Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves));
|
||||
bke::curves_copy_parameters(src_curves_id, *dst_curves_id);
|
||||
|
||||
@@ -49,7 +49,7 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
}
|
||||
|
||||
bke::CurvesGeometry dst_curves = geometry::subdivide_curves(
|
||||
src_curves, src_curves.curves_range(), cuts);
|
||||
src_curves, src_curves.curves_range(), cuts, params.get_output_propagation_info("Curve"));
|
||||
|
||||
Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves));
|
||||
bke::curves_copy_parameters(src_curves_id, *dst_curves_id);
|
||||
|
||||
@@ -25,7 +25,8 @@ static void node_declare(NodeDeclarationBuilder &b)
|
||||
|
||||
static void geometry_set_curve_to_mesh(GeometrySet &geometry_set,
|
||||
const GeometrySet &profile_set,
|
||||
const bool fill_caps)
|
||||
const bool fill_caps,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const Curves &curves = *geometry_set.get_curves_for_read();
|
||||
const Curves *profile_curves = profile_set.get_curves_for_read();
|
||||
@@ -33,13 +34,15 @@ static void geometry_set_curve_to_mesh(GeometrySet &geometry_set,
|
||||
GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set);
|
||||
|
||||
if (profile_curves == nullptr) {
|
||||
Mesh *mesh = bke::curve_to_wire_mesh(bke::CurvesGeometry::wrap(curves.geometry));
|
||||
Mesh *mesh = bke::curve_to_wire_mesh(bke::CurvesGeometry::wrap(curves.geometry),
|
||||
propagation_info);
|
||||
geometry_set.replace_mesh(mesh);
|
||||
}
|
||||
else {
|
||||
Mesh *mesh = bke::curve_to_mesh_sweep(bke::CurvesGeometry::wrap(curves.geometry),
|
||||
bke::CurvesGeometry::wrap(profile_curves->geometry),
|
||||
fill_caps);
|
||||
fill_caps,
|
||||
propagation_info);
|
||||
geometry_set.replace_mesh(mesh);
|
||||
}
|
||||
}
|
||||
@@ -52,7 +55,8 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
|
||||
curve_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
|
||||
if (geometry_set.has_curves()) {
|
||||
geometry_set_curve_to_mesh(geometry_set, profile_set, fill_caps);
|
||||
geometry_set_curve_to_mesh(
|
||||
geometry_set, profile_set, fill_caps, params.get_output_propagation_info("Mesh"));
|
||||
}
|
||||
geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH});
|
||||
});
|
||||
|
||||
@@ -111,19 +111,12 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
|
||||
GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set);
|
||||
|
||||
StrongAnonymousAttributeID tangent_anonymous_id;
|
||||
StrongAnonymousAttributeID normal_anonymous_id;
|
||||
StrongAnonymousAttributeID rotation_anonymous_id;
|
||||
const bool rotation_required = params.output_is_required("Rotation");
|
||||
if (params.output_is_required("Tangent") || rotation_required) {
|
||||
tangent_anonymous_id = StrongAnonymousAttributeID("Tangent");
|
||||
}
|
||||
if (params.output_is_required("Normal") || rotation_required) {
|
||||
normal_anonymous_id = StrongAnonymousAttributeID("Normal");
|
||||
}
|
||||
if (rotation_required) {
|
||||
rotation_anonymous_id = StrongAnonymousAttributeID("Rotation");
|
||||
}
|
||||
AutoAnonymousAttributeID tangent_anonymous_id =
|
||||
params.get_output_anonymous_attribute_id_if_needed("Tangent");
|
||||
AutoAnonymousAttributeID normal_anonymous_id =
|
||||
params.get_output_anonymous_attribute_id_if_needed("Normal");
|
||||
AutoAnonymousAttributeID rotation_anonymous_id =
|
||||
params.get_output_anonymous_attribute_id_if_needed("Rotation");
|
||||
|
||||
geometry::ResampleCurvesOutputAttributeIDs resample_attributes;
|
||||
resample_attributes.tangent_id = tangent_anonymous_id.get();
|
||||
|
||||
@@ -112,7 +112,8 @@ static void geometry_set_curve_trim(GeometrySet &geometry_set,
|
||||
const GeometryNodeCurveSampleMode mode,
|
||||
Field<bool> &selection_field,
|
||||
Field<float> &start_field,
|
||||
Field<float> &end_field)
|
||||
Field<float> &end_field,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
if (!geometry_set.has_curves()) {
|
||||
return;
|
||||
@@ -139,7 +140,7 @@ static void geometry_set_curve_trim(GeometrySet &geometry_set,
|
||||
}
|
||||
|
||||
bke::CurvesGeometry dst_curves = geometry::trim_curves(
|
||||
src_curves, selection, starts, ends, mode);
|
||||
src_curves, selection, starts, ends, mode, propagation_info);
|
||||
Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves));
|
||||
bke::curves_copy_parameters(src_curves_id, *dst_curves_id);
|
||||
geometry_set.replace_curves(dst_curves_id);
|
||||
@@ -153,19 +154,24 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve");
|
||||
GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set);
|
||||
|
||||
const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info(
|
||||
"Curve");
|
||||
|
||||
Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
|
||||
if (mode == GEO_NODE_CURVE_SAMPLE_FACTOR) {
|
||||
Field<float> start_field = params.extract_input<Field<float>>("Start");
|
||||
Field<float> end_field = params.extract_input<Field<float>>("End");
|
||||
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
|
||||
geometry_set_curve_trim(geometry_set, mode, selection_field, start_field, end_field);
|
||||
geometry_set_curve_trim(
|
||||
geometry_set, mode, selection_field, start_field, end_field, propagation_info);
|
||||
});
|
||||
}
|
||||
else if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) {
|
||||
Field<float> start_field = params.extract_input<Field<float>>("Start_001");
|
||||
Field<float> end_field = params.extract_input<Field<float>>("End_001");
|
||||
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
|
||||
geometry_set_curve_trim(geometry_set, mode, selection_field, start_field, end_field);
|
||||
geometry_set_curve_trim(
|
||||
geometry_set, mode, selection_field, start_field, end_field, propagation_info);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -306,7 +306,8 @@ static void copy_masked_polys_to_new_mesh(const Mesh &src_mesh,
|
||||
|
||||
static void delete_curves_selection(GeometrySet &geometry_set,
|
||||
const Field<bool> &selection_field,
|
||||
const eAttrDomain selection_domain)
|
||||
const eAttrDomain selection_domain,
|
||||
const bke::AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const Curves &src_curves_id = *geometry_set.get_curves_for_read();
|
||||
const bke::CurvesGeometry &src_curves = bke::CurvesGeometry::wrap(src_curves_id.geometry);
|
||||
@@ -330,15 +331,17 @@ static void delete_curves_selection(GeometrySet &geometry_set,
|
||||
bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry);
|
||||
|
||||
if (selection_domain == ATTR_DOMAIN_POINT) {
|
||||
curves.remove_points(selection);
|
||||
curves.remove_points(selection, propagation_info);
|
||||
}
|
||||
else if (selection_domain == ATTR_DOMAIN_CURVE) {
|
||||
curves.remove_curves(selection);
|
||||
curves.remove_curves(selection, propagation_info);
|
||||
}
|
||||
}
|
||||
|
||||
static void separate_point_cloud_selection(GeometrySet &geometry_set,
|
||||
const Field<bool> &selection_field)
|
||||
static void separate_point_cloud_selection(
|
||||
GeometrySet &geometry_set,
|
||||
const Field<bool> &selection_field,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const PointCloud &src_pointcloud = *geometry_set.get_pointcloud_for_read();
|
||||
|
||||
@@ -355,8 +358,11 @@ static void separate_point_cloud_selection(GeometrySet &geometry_set,
|
||||
PointCloud *pointcloud = BKE_pointcloud_new_nomain(selection.size());
|
||||
|
||||
Map<AttributeIDRef, AttributeKind> attributes;
|
||||
geometry_set.gather_attributes_for_propagation(
|
||||
{GEO_COMPONENT_TYPE_POINT_CLOUD}, GEO_COMPONENT_TYPE_POINT_CLOUD, false, attributes);
|
||||
geometry_set.gather_attributes_for_propagation({GEO_COMPONENT_TYPE_POINT_CLOUD},
|
||||
GEO_COMPONENT_TYPE_POINT_CLOUD,
|
||||
false,
|
||||
propagation_info,
|
||||
attributes);
|
||||
|
||||
copy_attributes_based_on_mask(attributes,
|
||||
src_pointcloud.attributes(),
|
||||
@@ -367,7 +373,8 @@ static void separate_point_cloud_selection(GeometrySet &geometry_set,
|
||||
}
|
||||
|
||||
static void delete_selected_instances(GeometrySet &geometry_set,
|
||||
const Field<bool> &selection_field)
|
||||
const Field<bool> &selection_field,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
bke::Instances &instances = *geometry_set.get_instances_for_write();
|
||||
bke::InstancesFieldContext field_context{instances};
|
||||
@@ -381,7 +388,7 @@ static void delete_selected_instances(GeometrySet &geometry_set,
|
||||
return;
|
||||
}
|
||||
|
||||
instances.remove(selection);
|
||||
instances.remove(selection, propagation_info);
|
||||
}
|
||||
|
||||
static void compute_selected_verts_from_vertex_selection(const Span<bool> vertex_selection,
|
||||
@@ -819,7 +826,8 @@ static void do_mesh_separation(GeometrySet &geometry_set,
|
||||
const Mesh &mesh_in,
|
||||
const Span<bool> selection,
|
||||
const eAttrDomain domain,
|
||||
const GeometryNodeDeleteGeometryMode mode)
|
||||
const GeometryNodeDeleteGeometryMode mode,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
/* Needed in all cases. */
|
||||
Vector<int> selected_poly_indices;
|
||||
@@ -831,7 +839,7 @@ static void do_mesh_separation(GeometrySet &geometry_set,
|
||||
|
||||
Map<AttributeIDRef, AttributeKind> attributes;
|
||||
geometry_set.gather_attributes_for_propagation(
|
||||
{GEO_COMPONENT_TYPE_MESH}, GEO_COMPONENT_TYPE_MESH, false, attributes);
|
||||
{GEO_COMPONENT_TYPE_MESH}, GEO_COMPONENT_TYPE_MESH, false, propagation_info, attributes);
|
||||
|
||||
switch (mode) {
|
||||
case GEO_NODE_DELETE_GEOMETRY_MODE_ALL: {
|
||||
@@ -1059,7 +1067,8 @@ static void do_mesh_separation(GeometrySet &geometry_set,
|
||||
static void separate_mesh_selection(GeometrySet &geometry_set,
|
||||
const Field<bool> &selection_field,
|
||||
const eAttrDomain selection_domain,
|
||||
const GeometryNodeDeleteGeometryMode mode)
|
||||
const GeometryNodeDeleteGeometryMode mode,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const Mesh &src_mesh = *geometry_set.get_mesh_for_read();
|
||||
bke::MeshFieldContext field_context{src_mesh, selection_domain};
|
||||
@@ -1074,7 +1083,8 @@ static void separate_mesh_selection(GeometrySet &geometry_set,
|
||||
|
||||
const VArraySpan<bool> selection_span{selection};
|
||||
|
||||
do_mesh_separation(geometry_set, src_mesh, selection_span, selection_domain, mode);
|
||||
do_mesh_separation(
|
||||
geometry_set, src_mesh, selection_span, selection_domain, mode, propagation_info);
|
||||
}
|
||||
|
||||
} // namespace blender::nodes::node_geo_delete_geometry_cc
|
||||
@@ -1085,6 +1095,7 @@ void separate_geometry(GeometrySet &geometry_set,
|
||||
const eAttrDomain domain,
|
||||
const GeometryNodeDeleteGeometryMode mode,
|
||||
const Field<bool> &selection_field,
|
||||
const AnonymousAttributePropagationInfo &propagation_info,
|
||||
bool &r_is_error)
|
||||
{
|
||||
namespace file_ns = blender::nodes::node_geo_delete_geometry_cc;
|
||||
@@ -1092,26 +1103,27 @@ void separate_geometry(GeometrySet &geometry_set,
|
||||
bool some_valid_domain = false;
|
||||
if (geometry_set.has_pointcloud()) {
|
||||
if (domain == ATTR_DOMAIN_POINT) {
|
||||
file_ns::separate_point_cloud_selection(geometry_set, selection_field);
|
||||
file_ns::separate_point_cloud_selection(geometry_set, selection_field, propagation_info);
|
||||
some_valid_domain = true;
|
||||
}
|
||||
}
|
||||
if (geometry_set.has_mesh()) {
|
||||
if (ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_EDGE, ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER)) {
|
||||
file_ns::separate_mesh_selection(geometry_set, selection_field, domain, mode);
|
||||
file_ns::separate_mesh_selection(
|
||||
geometry_set, selection_field, domain, mode, propagation_info);
|
||||
some_valid_domain = true;
|
||||
}
|
||||
}
|
||||
if (geometry_set.has_curves()) {
|
||||
if (ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE)) {
|
||||
file_ns::delete_curves_selection(
|
||||
geometry_set, fn::invert_boolean_field(selection_field), domain);
|
||||
geometry_set, fn::invert_boolean_field(selection_field), domain, propagation_info);
|
||||
some_valid_domain = true;
|
||||
}
|
||||
}
|
||||
if (geometry_set.has_instances()) {
|
||||
if (domain == ATTR_DOMAIN_INSTANCE) {
|
||||
file_ns::delete_selected_instances(geometry_set, selection_field);
|
||||
file_ns::delete_selected_instances(geometry_set, selection_field, propagation_info);
|
||||
some_valid_domain = true;
|
||||
}
|
||||
}
|
||||
@@ -1171,15 +1183,18 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
const eAttrDomain domain = eAttrDomain(storage.domain);
|
||||
const GeometryNodeDeleteGeometryMode mode = (GeometryNodeDeleteGeometryMode)storage.mode;
|
||||
|
||||
const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info(
|
||||
"Geometry");
|
||||
|
||||
if (domain == ATTR_DOMAIN_INSTANCE) {
|
||||
bool is_error;
|
||||
separate_geometry(geometry_set, domain, mode, selection, is_error);
|
||||
separate_geometry(geometry_set, domain, mode, selection, propagation_info, is_error);
|
||||
}
|
||||
else {
|
||||
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
|
||||
bool is_error;
|
||||
/* Invert here because we want to keep the things not in the selection. */
|
||||
separate_geometry(geometry_set, domain, mode, selection, is_error);
|
||||
separate_geometry(geometry_set, domain, mode, selection, propagation_info, is_error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -320,8 +320,8 @@ BLI_NOINLINE static void propagate_existing_attributes(
|
||||
|
||||
namespace {
|
||||
struct AttributeOutputs {
|
||||
StrongAnonymousAttributeID normal_id;
|
||||
StrongAnonymousAttributeID rotation_id;
|
||||
AutoAnonymousAttributeID normal_id;
|
||||
AutoAnonymousAttributeID rotation_id;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
@@ -496,8 +496,11 @@ static void point_distribution_calculate(GeometrySet &geometry_set,
|
||||
geometry_set.replace_pointcloud(pointcloud);
|
||||
|
||||
Map<AttributeIDRef, AttributeKind> attributes;
|
||||
geometry_set.gather_attributes_for_propagation(
|
||||
{GEO_COMPONENT_TYPE_MESH}, GEO_COMPONENT_TYPE_POINT_CLOUD, false, attributes);
|
||||
geometry_set.gather_attributes_for_propagation({GEO_COMPONENT_TYPE_MESH},
|
||||
GEO_COMPONENT_TYPE_POINT_CLOUD,
|
||||
false,
|
||||
params.get_output_propagation_info("Points"),
|
||||
attributes);
|
||||
|
||||
/* Position is set separately. */
|
||||
attributes.remove("position");
|
||||
@@ -518,12 +521,8 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
|
||||
|
||||
AttributeOutputs attribute_outputs;
|
||||
if (params.output_is_required("Normal")) {
|
||||
attribute_outputs.normal_id = StrongAnonymousAttributeID("Normal");
|
||||
}
|
||||
if (params.output_is_required("Rotation")) {
|
||||
attribute_outputs.rotation_id = StrongAnonymousAttributeID("Rotation");
|
||||
}
|
||||
attribute_outputs.normal_id = params.get_output_anonymous_attribute_id_if_needed("Normal");
|
||||
attribute_outputs.rotation_id = params.get_output_anonymous_attribute_id_if_needed("Rotation");
|
||||
|
||||
lazy_threading::send_hint();
|
||||
|
||||
|
||||
@@ -132,6 +132,7 @@ static void transfer_attributes(
|
||||
const Span<int> new_to_old_edges_map,
|
||||
const Span<int> new_to_old_face_corners_map,
|
||||
const Span<std::pair<int, int>> boundary_vertex_to_relevant_face_map,
|
||||
const AnonymousAttributePropagationInfo &propagation_info,
|
||||
const AttributeAccessor src_attributes,
|
||||
MutableAttributeAccessor dst_attributes)
|
||||
{
|
||||
@@ -139,7 +140,9 @@ static void transfer_attributes(
|
||||
* Remove anonymous attributes that don't need to be propagated. */
|
||||
Set<AttributeIDRef> attribute_ids = src_attributes.all_ids();
|
||||
attribute_ids.remove("position");
|
||||
attribute_ids.remove_if([](const AttributeIDRef &id) { return !id.should_be_kept(); });
|
||||
attribute_ids.remove_if([&](const AttributeIDRef &id) {
|
||||
return id.is_anonymous() && !propagation_info.propagate(id.anonymous_id());
|
||||
});
|
||||
|
||||
for (const AttributeIDRef &id : attribute_ids) {
|
||||
GAttributeReader src_attribute = src_attributes.lookup(id);
|
||||
@@ -606,7 +609,9 @@ static void dissolve_redundant_verts(const Span<MEdge> edges,
|
||||
*
|
||||
* Some special cases are needed for boundaries and non-manifold geometry.
|
||||
*/
|
||||
static Mesh *calc_dual_mesh(const Mesh &src_mesh, const bool keep_boundaries)
|
||||
static Mesh *calc_dual_mesh(const Mesh &src_mesh,
|
||||
const bool keep_boundaries,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const Span<MVert> src_verts = src_mesh.verts();
|
||||
const Span<MEdge> src_edges = src_mesh.edges();
|
||||
@@ -887,6 +892,7 @@ static Mesh *calc_dual_mesh(const Mesh &src_mesh, const bool keep_boundaries)
|
||||
new_to_old_edges_map,
|
||||
new_to_old_face_corners_map,
|
||||
boundary_vertex_to_relevant_face_map,
|
||||
propagation_info,
|
||||
src_mesh.attributes(),
|
||||
mesh_out->attributes_for_write());
|
||||
|
||||
@@ -918,7 +924,8 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
const bool keep_boundaries = params.extract_input<bool>("Keep Boundaries");
|
||||
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
|
||||
if (const Mesh *mesh = geometry_set.get_mesh_for_read()) {
|
||||
Mesh *new_mesh = calc_dual_mesh(*mesh, keep_boundaries);
|
||||
Mesh *new_mesh = calc_dual_mesh(
|
||||
*mesh, keep_boundaries, params.get_output_propagation_info("Dual Mesh"));
|
||||
geometry_set.replace_mesh(new_mesh);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -56,7 +56,7 @@ static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
}
|
||||
|
||||
struct IndexAttributes {
|
||||
StrongAnonymousAttributeID duplicate_index;
|
||||
AutoAnonymousAttributeID duplicate_index;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
@@ -64,11 +64,13 @@ struct IndexAttributes {
|
||||
* \{ */
|
||||
|
||||
static Map<AttributeIDRef, AttributeKind> gather_attributes_without_id(
|
||||
const GeometrySet &geometry_set, const GeometryComponentType component_type)
|
||||
const GeometrySet &geometry_set,
|
||||
const GeometryComponentType component_type,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
Map<AttributeIDRef, AttributeKind> attributes;
|
||||
geometry_set.gather_attributes_for_propagation(
|
||||
{component_type}, component_type, false, attributes);
|
||||
{component_type}, component_type, false, propagation_info, attributes);
|
||||
attributes.remove("id");
|
||||
return attributes;
|
||||
};
|
||||
@@ -181,11 +183,12 @@ static void copy_attributes_without_id(GeometrySet &geometry_set,
|
||||
const eAttrDomain domain,
|
||||
const Span<int> offsets,
|
||||
const IndexMask selection,
|
||||
const AnonymousAttributePropagationInfo &propagation_info,
|
||||
const bke::AttributeAccessor src_attributes,
|
||||
bke::MutableAttributeAccessor dst_attributes)
|
||||
{
|
||||
const Map<AttributeIDRef, AttributeKind> attributes = gather_attributes_without_id(
|
||||
geometry_set, component_type);
|
||||
geometry_set, component_type, propagation_info);
|
||||
|
||||
for (const Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) {
|
||||
const AttributeIDRef attribute_id = entry.key;
|
||||
@@ -221,14 +224,16 @@ static void copy_attributes_without_id(GeometrySet &geometry_set,
|
||||
* Copies the attributes for curve duplicates. If copying the curve domain, the attributes are
|
||||
* copied with an offset fill, otherwise a mapping is used.
|
||||
*/
|
||||
static void copy_curve_attributes_without_id(const GeometrySet &geometry_set,
|
||||
const bke::CurvesGeometry &src_curves,
|
||||
const IndexMask selection,
|
||||
const Span<int> curve_offsets,
|
||||
bke::CurvesGeometry &dst_curves)
|
||||
static void copy_curve_attributes_without_id(
|
||||
const GeometrySet &geometry_set,
|
||||
const bke::CurvesGeometry &src_curves,
|
||||
const IndexMask selection,
|
||||
const Span<int> curve_offsets,
|
||||
const AnonymousAttributePropagationInfo &propagation_info,
|
||||
bke::CurvesGeometry &dst_curves)
|
||||
{
|
||||
Map<AttributeIDRef, AttributeKind> attributes = gather_attributes_without_id(
|
||||
geometry_set, GEO_COMPONENT_TYPE_CURVE);
|
||||
geometry_set, GEO_COMPONENT_TYPE_CURVE, propagation_info);
|
||||
|
||||
for (const Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) {
|
||||
const AttributeIDRef attribute_id = entry.key;
|
||||
@@ -318,7 +323,8 @@ static void copy_stable_id_curves(const bke::CurvesGeometry &src_curves,
|
||||
static void duplicate_curves(GeometrySet &geometry_set,
|
||||
const Field<int> &count_field,
|
||||
const Field<bool> &selection_field,
|
||||
const IndexAttributes &attribute_outputs)
|
||||
const IndexAttributes &attribute_outputs,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
if (!geometry_set.has_curves()) {
|
||||
geometry_set.remove_geometry_during_modify();
|
||||
@@ -373,7 +379,8 @@ static void duplicate_curves(GeometrySet &geometry_set,
|
||||
});
|
||||
all_dst_offsets.last() = dst_points_num;
|
||||
|
||||
copy_curve_attributes_without_id(geometry_set, curves, selection, curve_offsets, new_curves);
|
||||
copy_curve_attributes_without_id(
|
||||
geometry_set, curves, selection, curve_offsets, propagation_info, new_curves);
|
||||
|
||||
copy_stable_id_curves(curves, selection, curve_offsets, new_curves);
|
||||
|
||||
@@ -398,17 +405,19 @@ static void duplicate_curves(GeometrySet &geometry_set,
|
||||
* Copies the attributes for face duplicates. If copying the face domain, the attributes are
|
||||
* copied with an offset fill, otherwise a mapping is used.
|
||||
*/
|
||||
static void copy_face_attributes_without_id(GeometrySet &geometry_set,
|
||||
const Span<int> edge_mapping,
|
||||
const Span<int> vert_mapping,
|
||||
const Span<int> loop_mapping,
|
||||
const Span<int> offsets,
|
||||
const IndexMask selection,
|
||||
const bke::AttributeAccessor src_attributes,
|
||||
bke::MutableAttributeAccessor dst_attributes)
|
||||
static void copy_face_attributes_without_id(
|
||||
GeometrySet &geometry_set,
|
||||
const Span<int> edge_mapping,
|
||||
const Span<int> vert_mapping,
|
||||
const Span<int> loop_mapping,
|
||||
const Span<int> offsets,
|
||||
const IndexMask selection,
|
||||
const AnonymousAttributePropagationInfo &propagation_info,
|
||||
const bke::AttributeAccessor src_attributes,
|
||||
bke::MutableAttributeAccessor dst_attributes)
|
||||
{
|
||||
Map<AttributeIDRef, AttributeKind> attributes = gather_attributes_without_id(
|
||||
geometry_set, GEO_COMPONENT_TYPE_MESH);
|
||||
geometry_set, GEO_COMPONENT_TYPE_MESH, propagation_info);
|
||||
|
||||
for (const Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) {
|
||||
const AttributeIDRef attribute_id = entry.key;
|
||||
@@ -506,7 +515,8 @@ static void copy_stable_id_faces(const Mesh &mesh,
|
||||
static void duplicate_faces(GeometrySet &geometry_set,
|
||||
const Field<int> &count_field,
|
||||
const Field<bool> &selection_field,
|
||||
const IndexAttributes &attribute_outputs)
|
||||
const IndexAttributes &attribute_outputs,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
if (!geometry_set.has_mesh()) {
|
||||
geometry_set.remove_geometry_during_modify();
|
||||
@@ -588,6 +598,7 @@ static void duplicate_faces(GeometrySet &geometry_set,
|
||||
loop_mapping,
|
||||
offsets,
|
||||
selection,
|
||||
propagation_info,
|
||||
mesh.attributes(),
|
||||
new_mesh->attributes_for_write());
|
||||
|
||||
@@ -612,15 +623,17 @@ static void duplicate_faces(GeometrySet &geometry_set,
|
||||
* Copies the attributes for edge duplicates. If copying the edge domain, the attributes are
|
||||
* copied with an offset fill, for point domain a mapping is used.
|
||||
*/
|
||||
static void copy_edge_attributes_without_id(GeometrySet &geometry_set,
|
||||
const Span<int> point_mapping,
|
||||
const Span<int> offsets,
|
||||
const IndexMask selection,
|
||||
const bke::AttributeAccessor src_attributes,
|
||||
bke::MutableAttributeAccessor dst_attributes)
|
||||
static void copy_edge_attributes_without_id(
|
||||
GeometrySet &geometry_set,
|
||||
const Span<int> point_mapping,
|
||||
const Span<int> offsets,
|
||||
const IndexMask selection,
|
||||
const AnonymousAttributePropagationInfo &propagation_info,
|
||||
const bke::AttributeAccessor src_attributes,
|
||||
bke::MutableAttributeAccessor dst_attributes)
|
||||
{
|
||||
Map<AttributeIDRef, AttributeKind> attributes = gather_attributes_without_id(
|
||||
geometry_set, GEO_COMPONENT_TYPE_MESH);
|
||||
geometry_set, GEO_COMPONENT_TYPE_MESH, propagation_info);
|
||||
|
||||
for (const Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) {
|
||||
const AttributeIDRef attribute_id = entry.key;
|
||||
@@ -704,7 +717,8 @@ static void copy_stable_id_edges(const Mesh &mesh,
|
||||
static void duplicate_edges(GeometrySet &geometry_set,
|
||||
const Field<int> &count_field,
|
||||
const Field<bool> &selection_field,
|
||||
const IndexAttributes &attribute_outputs)
|
||||
const IndexAttributes &attribute_outputs,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
if (!geometry_set.has_mesh()) {
|
||||
geometry_set.remove_geometry_during_modify();
|
||||
@@ -756,6 +770,7 @@ static void duplicate_edges(GeometrySet &geometry_set,
|
||||
vert_orig_indices,
|
||||
edge_offsets,
|
||||
selection,
|
||||
propagation_info,
|
||||
mesh.attributes(),
|
||||
new_mesh->attributes_for_write());
|
||||
|
||||
@@ -782,7 +797,8 @@ static void duplicate_edges(GeometrySet &geometry_set,
|
||||
static void duplicate_points_curve(GeometrySet &geometry_set,
|
||||
const Field<int> &count_field,
|
||||
const Field<bool> &selection_field,
|
||||
const IndexAttributes &attribute_outputs)
|
||||
const IndexAttributes &attribute_outputs,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const Curves &src_curves_id = *geometry_set.get_curves_for_read();
|
||||
const bke::CurvesGeometry &src_curves = bke::CurvesGeometry::wrap(src_curves_id.geometry);
|
||||
@@ -819,7 +835,7 @@ static void duplicate_points_curve(GeometrySet &geometry_set,
|
||||
new_curve_offsets.last() = dst_num;
|
||||
|
||||
Map<AttributeIDRef, AttributeKind> attributes = gather_attributes_without_id(
|
||||
geometry_set, GEO_COMPONENT_TYPE_CURVE);
|
||||
geometry_set, GEO_COMPONENT_TYPE_CURVE, propagation_info);
|
||||
|
||||
for (const Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) {
|
||||
const AttributeIDRef attribute_id = entry.key;
|
||||
@@ -885,7 +901,8 @@ static void duplicate_points_curve(GeometrySet &geometry_set,
|
||||
static void duplicate_points_mesh(GeometrySet &geometry_set,
|
||||
const Field<int> &count_field,
|
||||
const Field<bool> &selection_field,
|
||||
const IndexAttributes &attribute_outputs)
|
||||
const IndexAttributes &attribute_outputs,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const Mesh &mesh = *geometry_set.get_mesh_for_read();
|
||||
const Span<MVert> src_verts = mesh.verts();
|
||||
@@ -910,6 +927,7 @@ static void duplicate_points_mesh(GeometrySet &geometry_set,
|
||||
ATTR_DOMAIN_POINT,
|
||||
offsets,
|
||||
selection,
|
||||
propagation_info,
|
||||
mesh.attributes(),
|
||||
new_mesh->attributes_for_write());
|
||||
|
||||
@@ -935,7 +953,8 @@ static void duplicate_points_mesh(GeometrySet &geometry_set,
|
||||
static void duplicate_points_pointcloud(GeometrySet &geometry_set,
|
||||
const Field<int> &count_field,
|
||||
const Field<bool> &selection_field,
|
||||
const IndexAttributes &attribute_outputs)
|
||||
const IndexAttributes &attribute_outputs,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const PointCloud &src_points = *geometry_set.get_pointcloud_for_read();
|
||||
|
||||
@@ -956,6 +975,7 @@ static void duplicate_points_pointcloud(GeometrySet &geometry_set,
|
||||
ATTR_DOMAIN_POINT,
|
||||
offsets,
|
||||
selection,
|
||||
propagation_info,
|
||||
src_points.attributes(),
|
||||
pointcloud->attributes_for_write());
|
||||
|
||||
@@ -980,7 +1000,8 @@ static void duplicate_points_pointcloud(GeometrySet &geometry_set,
|
||||
static void duplicate_points(GeometrySet &geometry_set,
|
||||
const Field<int> &count_field,
|
||||
const Field<bool> &selection_field,
|
||||
const IndexAttributes &attribute_outputs)
|
||||
const IndexAttributes &attribute_outputs,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
Vector<GeometryComponentType> component_types = geometry_set.gather_component_types(true, true);
|
||||
for (const GeometryComponentType component_type : component_types) {
|
||||
@@ -988,17 +1009,19 @@ static void duplicate_points(GeometrySet &geometry_set,
|
||||
case GEO_COMPONENT_TYPE_POINT_CLOUD:
|
||||
if (geometry_set.has_pointcloud()) {
|
||||
duplicate_points_pointcloud(
|
||||
geometry_set, count_field, selection_field, attribute_outputs);
|
||||
geometry_set, count_field, selection_field, attribute_outputs, propagation_info);
|
||||
}
|
||||
break;
|
||||
case GEO_COMPONENT_TYPE_MESH:
|
||||
if (geometry_set.has_mesh()) {
|
||||
duplicate_points_mesh(geometry_set, count_field, selection_field, attribute_outputs);
|
||||
duplicate_points_mesh(
|
||||
geometry_set, count_field, selection_field, attribute_outputs, propagation_info);
|
||||
}
|
||||
break;
|
||||
case GEO_COMPONENT_TYPE_CURVE:
|
||||
if (geometry_set.has_curves()) {
|
||||
duplicate_points_curve(geometry_set, count_field, selection_field, attribute_outputs);
|
||||
duplicate_points_curve(
|
||||
geometry_set, count_field, selection_field, attribute_outputs, propagation_info);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -1018,7 +1041,8 @@ static void duplicate_points(GeometrySet &geometry_set,
|
||||
static void duplicate_instances(GeometrySet &geometry_set,
|
||||
const Field<int> &count_field,
|
||||
const Field<bool> &selection_field,
|
||||
const IndexAttributes &attribute_outputs)
|
||||
const IndexAttributes &attribute_outputs,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
if (!geometry_set.has_instances()) {
|
||||
geometry_set.clear();
|
||||
@@ -1062,6 +1086,7 @@ static void duplicate_instances(GeometrySet &geometry_set,
|
||||
ATTR_DOMAIN_INSTANCE,
|
||||
offsets,
|
||||
selection,
|
||||
propagation_info,
|
||||
src_instances.attributes(),
|
||||
dst_instances->attributes_for_write());
|
||||
|
||||
@@ -1092,27 +1117,34 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
Field<int> count_field = params.extract_input<Field<int>>("Amount");
|
||||
Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
|
||||
IndexAttributes attribute_outputs;
|
||||
if (params.output_is_required("Duplicate Index")) {
|
||||
attribute_outputs.duplicate_index = StrongAnonymousAttributeID("duplicate_index");
|
||||
}
|
||||
attribute_outputs.duplicate_index = params.get_output_anonymous_attribute_id_if_needed(
|
||||
"Duplicate Index");
|
||||
|
||||
const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info(
|
||||
"Geometry");
|
||||
|
||||
if (duplicate_domain == ATTR_DOMAIN_INSTANCE) {
|
||||
duplicate_instances(geometry_set, count_field, selection_field, attribute_outputs);
|
||||
duplicate_instances(
|
||||
geometry_set, count_field, selection_field, attribute_outputs, propagation_info);
|
||||
}
|
||||
else {
|
||||
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
|
||||
switch (duplicate_domain) {
|
||||
case ATTR_DOMAIN_CURVE:
|
||||
duplicate_curves(geometry_set, count_field, selection_field, attribute_outputs);
|
||||
duplicate_curves(
|
||||
geometry_set, count_field, selection_field, attribute_outputs, propagation_info);
|
||||
break;
|
||||
case ATTR_DOMAIN_FACE:
|
||||
duplicate_faces(geometry_set, count_field, selection_field, attribute_outputs);
|
||||
duplicate_faces(
|
||||
geometry_set, count_field, selection_field, attribute_outputs, propagation_info);
|
||||
break;
|
||||
case ATTR_DOMAIN_EDGE:
|
||||
duplicate_edges(geometry_set, count_field, selection_field, attribute_outputs);
|
||||
duplicate_edges(
|
||||
geometry_set, count_field, selection_field, attribute_outputs, propagation_info);
|
||||
break;
|
||||
case ATTR_DOMAIN_POINT:
|
||||
duplicate_points(geometry_set, count_field, selection_field, attribute_outputs);
|
||||
duplicate_points(
|
||||
geometry_set, count_field, selection_field, attribute_outputs, propagation_info);
|
||||
break;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
|
||||
@@ -18,9 +18,11 @@ static void node_declare(NodeDeclarationBuilder &b)
|
||||
b.add_output<decl::Geometry>(N_("Curves")).propagate_all();
|
||||
}
|
||||
|
||||
static Curves *edge_paths_to_curves_convert(const Mesh &mesh,
|
||||
const IndexMask start_verts_mask,
|
||||
const Span<int> next_indices)
|
||||
static Curves *edge_paths_to_curves_convert(
|
||||
const Mesh &mesh,
|
||||
const IndexMask start_verts_mask,
|
||||
const Span<int> next_indices,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
Vector<int> vert_indices;
|
||||
Vector<int> curve_offsets;
|
||||
@@ -58,8 +60,8 @@ static Curves *edge_paths_to_curves_convert(const Mesh &mesh,
|
||||
if (vert_indices.is_empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
Curves *curves_id = bke::curves_new_nomain(
|
||||
geometry::create_curve_from_vert_indices(mesh, vert_indices, curve_offsets, IndexRange(0)));
|
||||
Curves *curves_id = bke::curves_new_nomain(geometry::create_curve_from_vert_indices(
|
||||
mesh, vert_indices, curve_offsets, IndexRange(0), propagation_info));
|
||||
return curves_id;
|
||||
}
|
||||
|
||||
@@ -87,7 +89,8 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
return;
|
||||
}
|
||||
|
||||
geometry_set.replace_curves(edge_paths_to_curves_convert(*mesh, start_verts, next_vert));
|
||||
geometry_set.replace_curves(edge_paths_to_curves_convert(
|
||||
*mesh, start_verts, next_vert, params.get_output_propagation_info("Curves")));
|
||||
geometry_set.keep_only({GEO_COMPONENT_TYPE_CURVE, GEO_COMPONENT_TYPE_INSTANCES});
|
||||
});
|
||||
|
||||
|
||||
@@ -33,7 +33,8 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
return;
|
||||
}
|
||||
|
||||
geometry::split_edges(*geometry_set.get_mesh_for_write(), mask);
|
||||
geometry::split_edges(
|
||||
*geometry_set.get_mesh_for_write(), mask, params.get_output_propagation_info("Mesh"));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ static void node_declare(NodeDeclarationBuilder &b)
|
||||
b.add_input<decl::Bool>(N_("Selection")).default_value(true).field_on_all().hide_value();
|
||||
b.add_input<decl::Vector>(N_("Offset"))
|
||||
.subtype(PROP_TRANSLATION)
|
||||
.implicit_field(implicit_field_inputs::normal)
|
||||
.implicit_field_on_all(implicit_field_inputs::normal)
|
||||
.hide_value();
|
||||
b.add_input<decl::Float>(N_("Offset Scale")).default_value(1.0f).field_on_all();
|
||||
b.add_input<decl::Bool>(N_("Individual")).default_value(true);
|
||||
@@ -61,8 +61,8 @@ static void node_update(bNodeTree *ntree, bNode *node)
|
||||
}
|
||||
|
||||
struct AttributeOutputs {
|
||||
StrongAnonymousAttributeID top_id;
|
||||
StrongAnonymousAttributeID side_id;
|
||||
AutoAnonymousAttributeID top_id;
|
||||
AutoAnonymousAttributeID side_id;
|
||||
};
|
||||
|
||||
static void save_selection_as_attribute(Mesh &mesh,
|
||||
@@ -1334,12 +1334,8 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
const Field<float3> final_offset{std::move(multiply_op)};
|
||||
|
||||
AttributeOutputs attribute_outputs;
|
||||
if (params.output_is_required("Top")) {
|
||||
attribute_outputs.top_id = StrongAnonymousAttributeID("Top");
|
||||
}
|
||||
if (params.output_is_required("Side")) {
|
||||
attribute_outputs.side_id = StrongAnonymousAttributeID("Side");
|
||||
}
|
||||
attribute_outputs.top_id = params.get_output_anonymous_attribute_id_if_needed("Top");
|
||||
attribute_outputs.side_id = params.get_output_anonymous_attribute_id_if_needed("Side");
|
||||
|
||||
const bool extrude_individual = mode == GEO_NODE_EXTRUDE_MESH_FACES &&
|
||||
params.extract_input<bool>("Individual");
|
||||
|
||||
@@ -198,8 +198,11 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE};
|
||||
|
||||
Map<AttributeIDRef, AttributeKind> attributes_to_propagate;
|
||||
geometry_set.gather_attributes_for_propagation(
|
||||
types, GEO_COMPONENT_TYPE_INSTANCES, false, attributes_to_propagate);
|
||||
geometry_set.gather_attributes_for_propagation(types,
|
||||
GEO_COMPONENT_TYPE_INSTANCES,
|
||||
false,
|
||||
params.get_output_propagation_info("Instances"),
|
||||
attributes_to_propagate);
|
||||
attributes_to_propagate.remove("position");
|
||||
|
||||
for (const GeometryComponentType type : types) {
|
||||
|
||||
@@ -26,7 +26,8 @@ static void node_declare(NodeDeclarationBuilder &b)
|
||||
static void convert_instances_to_points(GeometrySet &geometry_set,
|
||||
Field<float3> position_field,
|
||||
Field<float> radius_field,
|
||||
const Field<bool> selection_field)
|
||||
const Field<bool> selection_field,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const bke::Instances &instances = *geometry_set.get_instances_for_read();
|
||||
|
||||
@@ -62,6 +63,7 @@ static void convert_instances_to_points(GeometrySet &geometry_set,
|
||||
geometry_set.gather_attributes_for_propagation({GEO_COMPONENT_TYPE_INSTANCES},
|
||||
GEO_COMPONENT_TYPE_POINT_CLOUD,
|
||||
false,
|
||||
propagation_info,
|
||||
attributes_to_propagate);
|
||||
/* These two attributes are added by the implicit inputs above. */
|
||||
attributes_to_propagate.remove("position");
|
||||
@@ -91,7 +93,8 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
convert_instances_to_points(geometry_set,
|
||||
params.extract_input<Field<float3>>("Position"),
|
||||
params.extract_input<Field<float>>("Radius"),
|
||||
params.extract_input<Field<bool>>("Selection"));
|
||||
params.extract_input<Field<bool>>("Selection"),
|
||||
params.get_output_propagation_info("Points"));
|
||||
geometry_set.keep_only({GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_EDIT});
|
||||
params.set_output("Points", std::move(geometry_set));
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ static Map<AttributeIDRef, AttributeMetaData> get_final_attribute_info(
|
||||
for (const GeometryComponent *component : components) {
|
||||
component->attributes()->for_all(
|
||||
[&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
|
||||
if (attribute_id.is_named() && ignored_attributes.contains(attribute_id.name())) {
|
||||
if (ignored_attributes.contains(attribute_id.name())) {
|
||||
return true;
|
||||
}
|
||||
if (meta_data.data_type == CD_PROP_STRING) {
|
||||
@@ -143,7 +143,9 @@ static void join_components(Span<const VolumeComponent *> /*src_components*/,
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
static void join_component_type(Span<GeometrySet> src_geometry_sets, GeometrySet &result)
|
||||
static void join_component_type(Span<GeometrySet> src_geometry_sets,
|
||||
GeometrySet &result,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
Vector<const Component *> components;
|
||||
for (const GeometrySet &geometry_set : src_geometry_sets) {
|
||||
@@ -176,6 +178,7 @@ static void join_component_type(Span<GeometrySet> src_geometry_sets, GeometrySet
|
||||
geometry::RealizeInstancesOptions options;
|
||||
options.keep_original_ids = true;
|
||||
options.realize_instance_attributes = false;
|
||||
options.propagation_info = propagation_info;
|
||||
GeometrySet joined_components = geometry::realize_instances(
|
||||
GeometrySet::create_with_instances(instances.release()), options);
|
||||
result.add(joined_components.get_component_for_write<Component>());
|
||||
@@ -186,13 +189,17 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
{
|
||||
Vector<GeometrySet> geometry_sets = params.extract_input<Vector<GeometrySet>>("Geometry");
|
||||
|
||||
const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info(
|
||||
"Geometry");
|
||||
|
||||
GeometrySet geometry_set_result;
|
||||
join_component_type<MeshComponent>(geometry_sets, geometry_set_result);
|
||||
join_component_type<PointCloudComponent>(geometry_sets, geometry_set_result);
|
||||
join_component_type<InstancesComponent>(geometry_sets, geometry_set_result);
|
||||
join_component_type<VolumeComponent>(geometry_sets, geometry_set_result);
|
||||
join_component_type<CurveComponent>(geometry_sets, geometry_set_result);
|
||||
join_component_type<GeometryComponentEditData>(geometry_sets, geometry_set_result);
|
||||
join_component_type<MeshComponent>(geometry_sets, geometry_set_result, propagation_info);
|
||||
join_component_type<PointCloudComponent>(geometry_sets, geometry_set_result, propagation_info);
|
||||
join_component_type<InstancesComponent>(geometry_sets, geometry_set_result, propagation_info);
|
||||
join_component_type<VolumeComponent>(geometry_sets, geometry_set_result, propagation_info);
|
||||
join_component_type<CurveComponent>(geometry_sets, geometry_set_result, propagation_info);
|
||||
join_component_type<GeometryComponentEditData>(
|
||||
geometry_sets, geometry_set_result, propagation_info);
|
||||
|
||||
params.set_output("Geometry", std::move(geometry_set_result));
|
||||
}
|
||||
|
||||
@@ -38,9 +38,11 @@ static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
static PointCloud *pointcloud_merge_by_distance(const PointCloud &src_points,
|
||||
const float merge_distance,
|
||||
const Field<bool> &selection_field)
|
||||
static PointCloud *pointcloud_merge_by_distance(
|
||||
const PointCloud &src_points,
|
||||
const float merge_distance,
|
||||
const Field<bool> &selection_field,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
bke::PointCloudFieldContext context{src_points};
|
||||
FieldEvaluator evaluator{context, src_points.totpoint};
|
||||
@@ -52,7 +54,8 @@ static PointCloud *pointcloud_merge_by_distance(const PointCloud &src_points,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return geometry::point_merge_by_distance(src_points, merge_distance, selection);
|
||||
return geometry::point_merge_by_distance(
|
||||
src_points, merge_distance, selection, propagation_info);
|
||||
}
|
||||
|
||||
static std::optional<Mesh *> mesh_merge_by_distance_connected(const Mesh &mesh,
|
||||
@@ -97,7 +100,8 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
|
||||
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
|
||||
if (const PointCloud *pointcloud = geometry_set.get_pointcloud_for_read()) {
|
||||
PointCloud *result = pointcloud_merge_by_distance(*pointcloud, merge_distance, selection);
|
||||
PointCloud *result = pointcloud_merge_by_distance(
|
||||
*pointcloud, merge_distance, selection, params.get_output_propagation_info("Geometry"));
|
||||
if (result) {
|
||||
geometry_set.replace_pointcloud(result);
|
||||
}
|
||||
|
||||
@@ -814,18 +814,10 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
const float depth = params.extract_input<float>("Depth");
|
||||
|
||||
ConeAttributeOutputs attribute_outputs;
|
||||
if (params.output_is_required("Top")) {
|
||||
attribute_outputs.top_id = StrongAnonymousAttributeID("top_selection");
|
||||
}
|
||||
if (params.output_is_required("Bottom")) {
|
||||
attribute_outputs.bottom_id = StrongAnonymousAttributeID("bottom_selection");
|
||||
}
|
||||
if (params.output_is_required("Side")) {
|
||||
attribute_outputs.side_id = StrongAnonymousAttributeID("side_selection");
|
||||
}
|
||||
if (params.output_is_required("UV Map")) {
|
||||
attribute_outputs.uv_map_id = StrongAnonymousAttributeID("uv_map");
|
||||
}
|
||||
attribute_outputs.top_id = params.get_output_anonymous_attribute_id_if_needed("Top");
|
||||
attribute_outputs.bottom_id = params.get_output_anonymous_attribute_id_if_needed("Bottom");
|
||||
attribute_outputs.side_id = params.get_output_anonymous_attribute_id_if_needed("Side");
|
||||
attribute_outputs.uv_map_id = params.get_output_anonymous_attribute_id_if_needed("UV Map");
|
||||
|
||||
Mesh *mesh = create_cylinder_or_cone_mesh(radius_top,
|
||||
radius_bottom,
|
||||
|
||||
@@ -107,10 +107,8 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
return;
|
||||
}
|
||||
|
||||
StrongAnonymousAttributeID uv_map_id;
|
||||
if (params.output_is_required("UV Map")) {
|
||||
uv_map_id = StrongAnonymousAttributeID("uv_map");
|
||||
}
|
||||
AutoAnonymousAttributeID uv_map_id = params.get_output_anonymous_attribute_id_if_needed(
|
||||
"UV Map");
|
||||
|
||||
Mesh *mesh = create_cube_mesh(size, verts_x, verts_y, verts_z, uv_map_id.get());
|
||||
|
||||
|
||||
@@ -107,18 +107,10 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
}
|
||||
|
||||
ConeAttributeOutputs attribute_outputs;
|
||||
if (params.output_is_required("Top")) {
|
||||
attribute_outputs.top_id = StrongAnonymousAttributeID("top_selection");
|
||||
}
|
||||
if (params.output_is_required("Bottom")) {
|
||||
attribute_outputs.bottom_id = StrongAnonymousAttributeID("bottom_selection");
|
||||
}
|
||||
if (params.output_is_required("Side")) {
|
||||
attribute_outputs.side_id = StrongAnonymousAttributeID("side_selection");
|
||||
}
|
||||
if (params.output_is_required("UV Map")) {
|
||||
attribute_outputs.uv_map_id = StrongAnonymousAttributeID("uv_map");
|
||||
}
|
||||
attribute_outputs.top_id = params.get_output_anonymous_attribute_id_if_needed("Top");
|
||||
attribute_outputs.bottom_id = params.get_output_anonymous_attribute_id_if_needed("Bottom");
|
||||
attribute_outputs.side_id = params.get_output_anonymous_attribute_id_if_needed("Side");
|
||||
attribute_outputs.uv_map_id = params.get_output_anonymous_attribute_id_if_needed("UV Map");
|
||||
|
||||
/* The cylinder is a special case of the cone mesh where the top and bottom radius are equal. */
|
||||
Mesh *mesh = create_cylinder_or_cone_mesh(radius,
|
||||
|
||||
@@ -194,10 +194,8 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
return;
|
||||
}
|
||||
|
||||
StrongAnonymousAttributeID uv_map_id;
|
||||
if (params.output_is_required("UV Map")) {
|
||||
uv_map_id = StrongAnonymousAttributeID("uv_map");
|
||||
}
|
||||
AutoAnonymousAttributeID uv_map_id = params.get_output_anonymous_attribute_id_if_needed(
|
||||
"UV Map");
|
||||
|
||||
Mesh *mesh = create_grid_mesh(verts_x, verts_y, size_x, size_y, uv_map_id.get());
|
||||
BKE_id_material_eval_ensure_default_slot(&mesh->id);
|
||||
|
||||
@@ -77,10 +77,8 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
const int subdivisions = std::min(params.extract_input<int>("Subdivisions"), 10);
|
||||
const float radius = params.extract_input<float>("Radius");
|
||||
|
||||
StrongAnonymousAttributeID uv_map_id;
|
||||
if (params.output_is_required("UV Map")) {
|
||||
uv_map_id = StrongAnonymousAttributeID("uv_map");
|
||||
}
|
||||
AutoAnonymousAttributeID uv_map_id = params.get_output_anonymous_attribute_id_if_needed(
|
||||
"UV Map");
|
||||
|
||||
Mesh *mesh = create_ico_sphere_mesh(subdivisions, radius, uv_map_id.get());
|
||||
params.set_output("Mesh", GeometrySet::create_with_mesh(mesh));
|
||||
|
||||
@@ -362,10 +362,8 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
|
||||
const float radius = params.extract_input<float>("Radius");
|
||||
|
||||
StrongAnonymousAttributeID uv_map_id;
|
||||
if (params.output_is_required("UV Map")) {
|
||||
uv_map_id = StrongAnonymousAttributeID("uv_map");
|
||||
}
|
||||
AutoAnonymousAttributeID uv_map_id = params.get_output_anonymous_attribute_id_if_needed(
|
||||
"UV Map");
|
||||
|
||||
Mesh *mesh = create_uv_sphere_mesh(radius, segments_num, rings_num, uv_map_id.get());
|
||||
params.set_output("Mesh", GeometrySet::create_with_mesh(mesh));
|
||||
|
||||
@@ -36,7 +36,8 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
return;
|
||||
}
|
||||
|
||||
bke::CurvesGeometry curves = geometry::mesh_to_curve_convert(*mesh, selection);
|
||||
bke::CurvesGeometry curves = geometry::mesh_to_curve_convert(
|
||||
*mesh, selection, params.get_output_propagation_info("Curve"));
|
||||
geometry_set.replace_curves(bke::curves_new_nomain(std::move(curves)));
|
||||
geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_CURVE});
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@ static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_input<decl::Geometry>(N_("Mesh")).supported_type(GEO_COMPONENT_TYPE_MESH);
|
||||
b.add_input<decl::Bool>(N_("Selection")).default_value(true).field_on_all().hide_value();
|
||||
b.add_input<decl::Vector>(N_("Position")).implicit_field(implicit_field_inputs::position);
|
||||
b.add_input<decl::Vector>(N_("Position")).implicit_field_on_all(implicit_field_inputs::position);
|
||||
b.add_input<decl::Float>(N_("Radius"))
|
||||
.default_value(0.05f)
|
||||
.min(0.0f)
|
||||
@@ -49,7 +49,8 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set,
|
||||
Field<float3> &position_field,
|
||||
Field<float> &radius_field,
|
||||
Field<bool> &selection_field,
|
||||
const eAttrDomain domain)
|
||||
const eAttrDomain domain,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const Mesh *mesh = geometry_set.get_mesh_for_read();
|
||||
if (mesh == nullptr) {
|
||||
@@ -87,8 +88,11 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set,
|
||||
radius.finish();
|
||||
|
||||
Map<AttributeIDRef, AttributeKind> attributes;
|
||||
geometry_set.gather_attributes_for_propagation(
|
||||
{GEO_COMPONENT_TYPE_MESH}, GEO_COMPONENT_TYPE_POINT_CLOUD, false, attributes);
|
||||
geometry_set.gather_attributes_for_propagation({GEO_COMPONENT_TYPE_MESH},
|
||||
GEO_COMPONENT_TYPE_POINT_CLOUD,
|
||||
false,
|
||||
propagation_info,
|
||||
attributes);
|
||||
attributes.remove("position");
|
||||
|
||||
const AttributeAccessor src_attributes = mesh->attributes();
|
||||
@@ -128,23 +132,42 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
const NodeGeometryMeshToPoints &storage = node_storage(params.node());
|
||||
const GeometryNodeMeshToPointsMode mode = (GeometryNodeMeshToPointsMode)storage.mode;
|
||||
|
||||
const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info(
|
||||
"Points");
|
||||
|
||||
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
|
||||
switch (mode) {
|
||||
case GEO_NODE_MESH_TO_POINTS_VERTICES:
|
||||
geometry_set_mesh_to_points(
|
||||
geometry_set, position, positive_radius, selection, ATTR_DOMAIN_POINT);
|
||||
geometry_set_mesh_to_points(geometry_set,
|
||||
position,
|
||||
positive_radius,
|
||||
selection,
|
||||
ATTR_DOMAIN_POINT,
|
||||
propagation_info);
|
||||
break;
|
||||
case GEO_NODE_MESH_TO_POINTS_EDGES:
|
||||
geometry_set_mesh_to_points(
|
||||
geometry_set, position, positive_radius, selection, ATTR_DOMAIN_EDGE);
|
||||
geometry_set_mesh_to_points(geometry_set,
|
||||
position,
|
||||
positive_radius,
|
||||
selection,
|
||||
ATTR_DOMAIN_EDGE,
|
||||
propagation_info);
|
||||
break;
|
||||
case GEO_NODE_MESH_TO_POINTS_FACES:
|
||||
geometry_set_mesh_to_points(
|
||||
geometry_set, position, positive_radius, selection, ATTR_DOMAIN_FACE);
|
||||
geometry_set_mesh_to_points(geometry_set,
|
||||
position,
|
||||
positive_radius,
|
||||
selection,
|
||||
ATTR_DOMAIN_FACE,
|
||||
propagation_info);
|
||||
break;
|
||||
case GEO_NODE_MESH_TO_POINTS_CORNERS:
|
||||
geometry_set_mesh_to_points(
|
||||
geometry_set, position, positive_radius, selection, ATTR_DOMAIN_CORNER);
|
||||
geometry_set_mesh_to_points(geometry_set,
|
||||
position,
|
||||
positive_radius,
|
||||
selection,
|
||||
ATTR_DOMAIN_CORNER,
|
||||
propagation_info);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -21,8 +21,10 @@ static void node_declare(NodeDeclarationBuilder &b)
|
||||
}
|
||||
|
||||
/* One improvement would be to move the attribute arrays directly to the mesh when possible. */
|
||||
static void geometry_set_points_to_vertices(GeometrySet &geometry_set,
|
||||
Field<bool> &selection_field)
|
||||
static void geometry_set_points_to_vertices(
|
||||
GeometrySet &geometry_set,
|
||||
Field<bool> &selection_field,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const PointCloud *points = geometry_set.get_pointcloud_for_read();
|
||||
if (points == nullptr) {
|
||||
@@ -41,8 +43,11 @@ static void geometry_set_points_to_vertices(GeometrySet &geometry_set,
|
||||
const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0);
|
||||
|
||||
Map<AttributeIDRef, AttributeKind> attributes;
|
||||
geometry_set.gather_attributes_for_propagation(
|
||||
{GEO_COMPONENT_TYPE_POINT_CLOUD}, GEO_COMPONENT_TYPE_MESH, false, attributes);
|
||||
geometry_set.gather_attributes_for_propagation({GEO_COMPONENT_TYPE_POINT_CLOUD},
|
||||
GEO_COMPONENT_TYPE_MESH,
|
||||
false,
|
||||
propagation_info,
|
||||
attributes);
|
||||
|
||||
Mesh *mesh = BKE_mesh_new_nomain(selection.size(), 0, 0, 0, 0);
|
||||
geometry_set.replace_mesh(mesh);
|
||||
@@ -73,7 +78,8 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
|
||||
|
||||
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
|
||||
geometry_set_points_to_vertices(geometry_set, selection_field);
|
||||
geometry_set_points_to_vertices(
|
||||
geometry_set, selection_field, params.get_output_propagation_info("Mesh"));
|
||||
});
|
||||
|
||||
params.set_output("Mesh", std::move(geometry_set));
|
||||
|
||||
@@ -37,6 +37,7 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
geometry::RealizeInstancesOptions options;
|
||||
options.keep_original_ids = legacy_behavior;
|
||||
options.realize_instance_attributes = !legacy_behavior;
|
||||
options.propagation_info = params.get_output_propagation_info("Geometry");
|
||||
geometry_set = geometry::realize_instances(geometry_set, options);
|
||||
params.set_output("Geometry", std::move(geometry_set));
|
||||
}
|
||||
|
||||
@@ -47,29 +47,42 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
const NodeGeometrySeparateGeometry &storage = node_storage(params.node());
|
||||
const eAttrDomain domain = eAttrDomain(storage.domain);
|
||||
|
||||
auto separate_geometry_maybe_recursively = [&](GeometrySet &geometry_set,
|
||||
const Field<bool> &selection) {
|
||||
bool is_error;
|
||||
if (domain == ATTR_DOMAIN_INSTANCE) {
|
||||
/* Only delete top level instances. */
|
||||
separate_geometry(
|
||||
geometry_set, domain, GEO_NODE_DELETE_GEOMETRY_MODE_ALL, selection, is_error);
|
||||
}
|
||||
else {
|
||||
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
|
||||
separate_geometry(
|
||||
geometry_set, domain, GEO_NODE_DELETE_GEOMETRY_MODE_ALL, selection, is_error);
|
||||
});
|
||||
}
|
||||
};
|
||||
auto separate_geometry_maybe_recursively =
|
||||
[&](GeometrySet &geometry_set,
|
||||
const Field<bool> &selection,
|
||||
const AnonymousAttributePropagationInfo &propagation_info) {
|
||||
bool is_error;
|
||||
if (domain == ATTR_DOMAIN_INSTANCE) {
|
||||
/* Only delete top level instances. */
|
||||
separate_geometry(geometry_set,
|
||||
domain,
|
||||
GEO_NODE_DELETE_GEOMETRY_MODE_ALL,
|
||||
selection,
|
||||
propagation_info,
|
||||
is_error);
|
||||
}
|
||||
else {
|
||||
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
|
||||
separate_geometry(geometry_set,
|
||||
domain,
|
||||
GEO_NODE_DELETE_GEOMETRY_MODE_ALL,
|
||||
selection,
|
||||
propagation_info,
|
||||
is_error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
GeometrySet second_set(geometry_set);
|
||||
if (params.output_is_required("Selection")) {
|
||||
separate_geometry_maybe_recursively(geometry_set, selection_field);
|
||||
separate_geometry_maybe_recursively(
|
||||
geometry_set, selection_field, params.get_output_propagation_info("Selection"));
|
||||
params.set_output("Selection", std::move(geometry_set));
|
||||
}
|
||||
if (params.output_is_required("Inverted")) {
|
||||
separate_geometry_maybe_recursively(second_set, fn::invert_boolean_field(selection_field));
|
||||
separate_geometry_maybe_recursively(second_set,
|
||||
fn::invert_boolean_field(selection_field),
|
||||
params.get_output_propagation_info("Inverted"));
|
||||
params.set_output("Inverted", std::move(second_set));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ static std::optional<TextLayout> get_text_layout(GeoNodeExecParams ¶ms)
|
||||
}
|
||||
}
|
||||
|
||||
if (params.output_is_required("Line")) {
|
||||
if (params.anonymous_attribute_output_is_required("Line")) {
|
||||
layout.line_numbers.reinitialize(layout.positions.size());
|
||||
for (const int i : layout.positions.index_range()) {
|
||||
CharTrans &ct = chartransdata[i];
|
||||
@@ -278,7 +278,7 @@ static Map<int, int> create_curve_instances(GeoNodeExecParams ¶ms,
|
||||
{
|
||||
VFont *vfont = reinterpret_cast<VFont *>(params.node().id);
|
||||
Map<int, int> handles;
|
||||
bool pivot_required = params.output_is_required("Pivot Point");
|
||||
bool pivot_required = params.anonymous_attribute_output_is_required("Pivot Point");
|
||||
|
||||
for (int i : layout.char_codes.index_range()) {
|
||||
if (handles.contains(layout.char_codes[i])) {
|
||||
@@ -341,10 +341,10 @@ static void create_attributes(GeoNodeExecParams ¶ms,
|
||||
{
|
||||
MutableAttributeAccessor attributes = instances.attributes_for_write();
|
||||
|
||||
if (params.output_is_required("Line")) {
|
||||
StrongAnonymousAttributeID line_id = StrongAnonymousAttributeID("Line");
|
||||
if (AutoAnonymousAttributeID line_id = params.get_output_anonymous_attribute_id_if_needed(
|
||||
"Line")) {
|
||||
SpanAttributeWriter<int> line_attribute = attributes.lookup_or_add_for_write_only_span<int>(
|
||||
line_id.get(), ATTR_DOMAIN_INSTANCE);
|
||||
*line_id, ATTR_DOMAIN_INSTANCE);
|
||||
line_attribute.span.copy_from(layout.line_numbers);
|
||||
line_attribute.finish();
|
||||
params.set_output("Line",
|
||||
@@ -352,10 +352,10 @@ static void create_attributes(GeoNodeExecParams ¶ms,
|
||||
params.attribute_producer_name()));
|
||||
}
|
||||
|
||||
if (params.output_is_required("Pivot Point")) {
|
||||
StrongAnonymousAttributeID pivot_id = StrongAnonymousAttributeID("Pivot");
|
||||
if (AutoAnonymousAttributeID pivot_id = params.get_output_anonymous_attribute_id_if_needed(
|
||||
"Pivot Point")) {
|
||||
SpanAttributeWriter<float3> pivot_attribute =
|
||||
attributes.lookup_or_add_for_write_only_span<float3>(pivot_id.get(), ATTR_DOMAIN_INSTANCE);
|
||||
attributes.lookup_or_add_for_write_only_span<float3>(*pivot_id, ATTR_DOMAIN_INSTANCE);
|
||||
|
||||
for (const int i : layout.char_codes.index_range()) {
|
||||
pivot_attribute.span[i] = layout.pivot_points.lookup(layout.char_codes[i]);
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#include "NOD_node_declaration.hh"
|
||||
|
||||
#include "BLI_cpp_types.hh"
|
||||
#include "BLI_dot_export.hh"
|
||||
#include "BLI_hash.h"
|
||||
#include "BLI_lazy_threading.hh"
|
||||
#include "BLI_map.hh"
|
||||
|
||||
@@ -111,6 +113,16 @@ class LazyFunctionForGeometryNode : public LazyFunction {
|
||||
const bNode &node_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Index of a boolean input that indicates whether the output socket is used.
|
||||
*/
|
||||
Map<StringRef, int> lf_input_for_output_bsocket_usage_;
|
||||
/**
|
||||
* Index of an attribute set input that indicates which anonymous attributes should be
|
||||
* propagated to the output.
|
||||
*/
|
||||
Map<StringRef, int> lf_input_for_attribute_propagation_to_output_;
|
||||
|
||||
LazyFunctionForGeometryNode(const bNode &node,
|
||||
Vector<const bNodeSocket *> &r_used_inputs,
|
||||
Vector<const bNodeSocket *> &r_used_outputs)
|
||||
@@ -119,6 +131,32 @@ class LazyFunctionForGeometryNode : public LazyFunction {
|
||||
BLI_assert(node.typeinfo->geometry_node_execute != nullptr);
|
||||
debug_name_ = node.name;
|
||||
lazy_function_interface_from_node(node, r_used_inputs, r_used_outputs, inputs_, outputs_);
|
||||
|
||||
const NodeDeclaration &node_decl = *node.declaration();
|
||||
const aal::RelationsInNode *relations = node_decl.anonymous_attribute_relations();
|
||||
if (relations == nullptr) {
|
||||
return;
|
||||
}
|
||||
Vector<const bNodeSocket *> handled_field_outputs;
|
||||
for (const aal::AvailableRelation &relation : relations->available_relations) {
|
||||
const bNodeSocket &output_bsocket = node.output_socket(relation.field_output);
|
||||
if (output_bsocket.is_available() && !handled_field_outputs.contains(&output_bsocket)) {
|
||||
handled_field_outputs.append(&output_bsocket);
|
||||
const int lf_index = inputs_.append_and_get_index_as("Output Used", CPPType::get<bool>());
|
||||
lf_input_for_output_bsocket_usage_.add(output_bsocket.identifier, lf_index);
|
||||
}
|
||||
}
|
||||
|
||||
Vector<const bNodeSocket *> handled_geometry_outputs;
|
||||
for (const aal::PropagateRelation &relation : relations->propagate_relations) {
|
||||
const bNodeSocket &output_bsocket = node.output_socket(relation.to_geometry_output);
|
||||
if (output_bsocket.is_available() && !handled_geometry_outputs.contains(&output_bsocket)) {
|
||||
handled_geometry_outputs.append(&output_bsocket);
|
||||
const int lf_index = inputs_.append_and_get_index_as(
|
||||
"Propagate to Output", CPPType::get<bke::AnonymousAttributeSet>());
|
||||
lf_input_for_attribute_propagation_to_output_.add(output_bsocket.identifier, lf_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void execute_impl(lf::Params ¶ms, const lf::Context &context) const override
|
||||
@@ -126,7 +164,11 @@ class LazyFunctionForGeometryNode : public LazyFunction {
|
||||
GeoNodesLFUserData *user_data = dynamic_cast<GeoNodesLFUserData *>(context.user_data);
|
||||
BLI_assert(user_data != nullptr);
|
||||
|
||||
GeoNodeExecParams geo_params{node_, params, context};
|
||||
GeoNodeExecParams geo_params{node_,
|
||||
params,
|
||||
context,
|
||||
lf_input_for_output_bsocket_usage_,
|
||||
lf_input_for_attribute_propagation_to_output_};
|
||||
|
||||
geo_eval_log::TimePoint start_time = geo_eval_log::Clock::now();
|
||||
node_.typeinfo->geometry_node_execute(geo_params);
|
||||
@@ -138,6 +180,27 @@ class LazyFunctionForGeometryNode : public LazyFunction {
|
||||
tree_logger.node_execution_times.append({node_.identifier, start_time, end_time});
|
||||
}
|
||||
}
|
||||
|
||||
std::string input_name(const int index) const override
|
||||
{
|
||||
for (const auto [identifier, lf_index] : lf_input_for_output_bsocket_usage_.items()) {
|
||||
if (index == lf_index) {
|
||||
return "Use Output '" + identifier + "'";
|
||||
}
|
||||
}
|
||||
for (const auto [identifier, lf_index] :
|
||||
lf_input_for_attribute_propagation_to_output_.items()) {
|
||||
if (index == lf_index) {
|
||||
return "Propagate to '" + identifier + "'";
|
||||
}
|
||||
}
|
||||
return inputs_[index].debug_name;
|
||||
}
|
||||
|
||||
std::string output_name(const int index) const override
|
||||
{
|
||||
return outputs_[index].debug_name;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -588,6 +651,35 @@ class LazyFunctionForViewerNode : public LazyFunction {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Outputs true when a specific viewer node is used in the current context and false otherwise.
|
||||
*/
|
||||
class LazyFunctionForViewerInputUsage : public LazyFunction {
|
||||
private:
|
||||
const lf::FunctionNode &lf_viewer_node_;
|
||||
|
||||
public:
|
||||
LazyFunctionForViewerInputUsage(const lf::FunctionNode &lf_viewer_node)
|
||||
: lf_viewer_node_(lf_viewer_node)
|
||||
{
|
||||
debug_name_ = "Viewer Input Usage";
|
||||
outputs_.append_as("Viewer is Used", CPPType::get<bool>());
|
||||
}
|
||||
|
||||
void execute_impl(lf::Params ¶ms, const lf::Context &context) const override
|
||||
{
|
||||
GeoNodesLFUserData *user_data = dynamic_cast<GeoNodesLFUserData *>(context.user_data);
|
||||
BLI_assert(user_data != nullptr);
|
||||
const ComputeContextHash &context_hash = user_data->compute_context->hash();
|
||||
const GeoNodesModifierData &modifier_data = *user_data->modifier_data;
|
||||
const Span<const lf::FunctionNode *> nodes_with_side_effects =
|
||||
modifier_data.side_effect_nodes->lookup(context_hash);
|
||||
|
||||
const bool viewer_is_used = nodes_with_side_effects.contains(&lf_viewer_node_);
|
||||
params.set_output(0, viewer_is_used);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This lazy-function wraps a group node. Internally it just executes the lazy-function graph of
|
||||
* the referenced group.
|
||||
@@ -596,7 +688,6 @@ class LazyFunctionForGroupNode : public LazyFunction {
|
||||
private:
|
||||
const bNode &group_node_;
|
||||
bool has_many_nodes_ = false;
|
||||
bool use_fallback_outputs_ = false;
|
||||
std::optional<GeometryNodesLazyFunctionLogger> lf_logger_;
|
||||
std::optional<GeometryNodesLazyFunctionSideEffectProvider> lf_side_effect_provider_;
|
||||
std::optional<lf::GraphExecutor> graph_executor_;
|
||||
@@ -608,40 +699,71 @@ class LazyFunctionForGroupNode : public LazyFunction {
|
||||
};
|
||||
|
||||
public:
|
||||
/**
|
||||
* For every input bsocket there is a corresponding boolean output that indicates whether that
|
||||
* input is used.
|
||||
*/
|
||||
Map<int, int> lf_output_for_input_bsocket_usage_;
|
||||
/**
|
||||
* For every output bsocket there is a corresponding boolean input that indicates whether the
|
||||
* output is used.
|
||||
*/
|
||||
Map<int, int> lf_input_for_output_bsocket_usage_;
|
||||
/**
|
||||
* For every geometry output that can propagate attributes from an input, there is an attribute
|
||||
* set input. It indicates which attributes should be propagated to the output.
|
||||
*/
|
||||
Map<int, int> lf_input_for_attribute_propagation_to_output_;
|
||||
|
||||
LazyFunctionForGroupNode(const bNode &group_node,
|
||||
const GeometryNodesLazyFunctionGraphInfo &lf_graph_info,
|
||||
Vector<const bNodeSocket *> &r_used_inputs,
|
||||
Vector<const bNodeSocket *> &r_used_outputs)
|
||||
const GeometryNodesLazyFunctionGraphInfo &lf_graph_info)
|
||||
: group_node_(group_node)
|
||||
{
|
||||
debug_name_ = group_node.name;
|
||||
lazy_function_interface_from_node(
|
||||
group_node, r_used_inputs, r_used_outputs, inputs_, outputs_);
|
||||
allow_missing_requested_inputs_ = true;
|
||||
|
||||
bNodeTree *group_btree = reinterpret_cast<bNodeTree *>(group_node_.id);
|
||||
BLI_assert(group_btree != nullptr);
|
||||
Vector<const bNodeSocket *> tmp_inputs;
|
||||
Vector<const bNodeSocket *> tmp_outputs;
|
||||
lazy_function_interface_from_node(group_node, tmp_inputs, tmp_outputs, inputs_, outputs_);
|
||||
|
||||
has_many_nodes_ = lf_graph_info.num_inline_nodes_approximate > 1000;
|
||||
|
||||
Vector<const lf::OutputSocket *> graph_inputs;
|
||||
for (const lf::OutputSocket *socket : lf_graph_info.mapping.group_input_sockets) {
|
||||
if (socket != nullptr) {
|
||||
graph_inputs.append(socket);
|
||||
}
|
||||
/* Add inputs that also exist on the bnode. */
|
||||
graph_inputs.extend(lf_graph_info.mapping.group_input_sockets);
|
||||
|
||||
/* Add a boolean input for every output bsocket that indicates whether that socket is used. */
|
||||
for (const int i : group_node.output_sockets().index_range()) {
|
||||
lf_input_for_output_bsocket_usage_.add_new(
|
||||
i,
|
||||
graph_inputs.append_and_get_index(lf_graph_info.mapping.group_output_used_sockets[i]));
|
||||
inputs_.append_as("Output is Used", CPPType::get<bool>(), lf::ValueUsage::Maybe);
|
||||
}
|
||||
graph_inputs.extend(lf_graph_info.mapping.group_output_used_sockets);
|
||||
|
||||
/* Add an attribute set input for every output geometry socket that can propagate attributes
|
||||
* from inputs. */
|
||||
for (auto [output_index, lf_socket] :
|
||||
lf_graph_info.mapping.attribute_set_by_geometry_output.items()) {
|
||||
const int lf_index = inputs_.append_and_get_index_as(
|
||||
"Attribute Set", CPPType::get<bke::AnonymousAttributeSet>(), lf::ValueUsage::Maybe);
|
||||
graph_inputs.append(lf_socket);
|
||||
lf_input_for_attribute_propagation_to_output_.add(output_index, lf_index);
|
||||
}
|
||||
|
||||
Vector<const lf::InputSocket *> graph_outputs;
|
||||
if (const bNode *group_output_bnode = group_btree->group_output_node()) {
|
||||
for (const bNodeSocket *bsocket : group_output_bnode->input_sockets().drop_back(1)) {
|
||||
const lf::Socket *socket = lf_graph_info.mapping.dummy_socket_map.lookup_default(bsocket,
|
||||
nullptr);
|
||||
if (socket != nullptr) {
|
||||
graph_outputs.append(&socket->as_input());
|
||||
}
|
||||
/* Add outputs that also exist on the bnode. */
|
||||
graph_outputs.extend(lf_graph_info.mapping.standard_group_output_sockets);
|
||||
/* Add a boolean output for every input bsocket that indicates whether that socket is used. */
|
||||
for (const int i : group_node.input_sockets().index_range()) {
|
||||
const InputUsageHint &input_usage_hint = lf_graph_info.mapping.group_input_usage_hints[i];
|
||||
if (input_usage_hint.type == InputUsageHintType::DynamicSocket) {
|
||||
const lf::InputSocket *lf_socket = lf_graph_info.mapping.group_input_usage_sockets[i];
|
||||
lf_output_for_input_bsocket_usage_.add_new(i,
|
||||
graph_outputs.append_and_get_index(lf_socket));
|
||||
outputs_.append_as("Input is Used", CPPType::get<bool>());
|
||||
}
|
||||
}
|
||||
else {
|
||||
use_fallback_outputs_ = true;
|
||||
}
|
||||
|
||||
lf_logger_.emplace(lf_graph_info);
|
||||
lf_side_effect_provider_.emplace();
|
||||
@@ -662,11 +784,6 @@ class LazyFunctionForGroupNode : public LazyFunction {
|
||||
* if every individual node is very small. */
|
||||
lazy_threading::send_hint();
|
||||
}
|
||||
if (use_fallback_outputs_) {
|
||||
/* The node group itself does not have an output node, so use default values as outputs.
|
||||
* The group should still be executed in case it has side effects. */
|
||||
params.set_default_remaining_outputs();
|
||||
}
|
||||
|
||||
Storage *storage = static_cast<Storage *>(context.storage);
|
||||
|
||||
@@ -702,6 +819,53 @@ class LazyFunctionForGroupNode : public LazyFunction {
|
||||
graph_executor_->destruct_storage(s->graph_executor_storage);
|
||||
std::destroy_at(s);
|
||||
}
|
||||
|
||||
std::string name() const override
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Group '" << (group_node_.id->name + 2) << "' (" << group_node_.name << ")";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string input_name(const int i) const override
|
||||
{
|
||||
if (i < group_node_.input_sockets().size()) {
|
||||
return group_node_.input_socket(i).name;
|
||||
}
|
||||
for (const auto [bsocket_index, lf_socket_index] :
|
||||
lf_input_for_output_bsocket_usage_.items()) {
|
||||
if (i == lf_socket_index) {
|
||||
std::stringstream ss;
|
||||
ss << "'" << group_node_.output_socket(bsocket_index).name << "' output is used";
|
||||
return ss.str();
|
||||
}
|
||||
}
|
||||
for (const auto [bsocket_index, lf_index] :
|
||||
lf_input_for_attribute_propagation_to_output_.items()) {
|
||||
if (i == lf_index) {
|
||||
std::stringstream ss;
|
||||
ss << "Propagate to '" << group_node_.output_socket(bsocket_index).name << "'";
|
||||
return ss.str();
|
||||
}
|
||||
}
|
||||
return inputs_[i].debug_name;
|
||||
}
|
||||
|
||||
std::string output_name(const int i) const override
|
||||
{
|
||||
if (i < group_node_.output_sockets().size()) {
|
||||
return group_node_.output_socket(i).name;
|
||||
}
|
||||
for (const auto [bsocket_index, lf_socket_index] :
|
||||
lf_output_for_input_bsocket_usage_.items()) {
|
||||
if (i == lf_socket_index) {
|
||||
std::stringstream ss;
|
||||
ss << "'" << group_node_.input_socket(bsocket_index).name << "' input is used";
|
||||
return ss.str();
|
||||
}
|
||||
}
|
||||
return outputs_[i].debug_name;
|
||||
}
|
||||
};
|
||||
|
||||
static GMutablePointer get_socket_default_value(LinearAllocator<> &allocator,
|
||||
@@ -747,6 +911,230 @@ class GroupOutputDebugInfo : public lf::DummyDebugInfo {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes the logical or of the inputs and supports short-circuit evaluation (i.e. if the first
|
||||
* input is true already, the other inputs are not checked).
|
||||
*/
|
||||
class LazyFunctionForLogicalOr : public lf::LazyFunction {
|
||||
public:
|
||||
LazyFunctionForLogicalOr(const int inputs_num)
|
||||
{
|
||||
debug_name_ = "Logical Or";
|
||||
for ([[maybe_unused]] const int i : IndexRange(inputs_num)) {
|
||||
inputs_.append_as("Input", CPPType::get<bool>(), lf::ValueUsage::Maybe);
|
||||
}
|
||||
outputs_.append_as("Output", CPPType::get<bool>());
|
||||
}
|
||||
|
||||
void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override
|
||||
{
|
||||
int first_unavailable_input = -1;
|
||||
for (const int i : inputs_.index_range()) {
|
||||
if (const bool *value = params.try_get_input_data_ptr<bool>(i)) {
|
||||
if (*value) {
|
||||
params.set_output(0, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
first_unavailable_input = i;
|
||||
}
|
||||
}
|
||||
if (first_unavailable_input == -1) {
|
||||
params.set_output(0, false);
|
||||
return;
|
||||
}
|
||||
params.try_get_input_data_ptr_or_request(first_unavailable_input);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Outputs booleans that indicate which inputs of a switch node are used. Note that it's possible
|
||||
* that both inputs are used when the condition is a field.
|
||||
*/
|
||||
class LazyFunctionForSwitchSocketUsage : public lf::LazyFunction {
|
||||
public:
|
||||
LazyFunctionForSwitchSocketUsage()
|
||||
{
|
||||
debug_name_ = "Switch Socket Usage";
|
||||
inputs_.append_as("Condition", CPPType::get<ValueOrField<bool>>());
|
||||
outputs_.append_as("False", CPPType::get<bool>());
|
||||
outputs_.append_as("True", CPPType::get<bool>());
|
||||
}
|
||||
|
||||
void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override
|
||||
{
|
||||
const ValueOrField<bool> &condition = params.get_input<ValueOrField<bool>>(0);
|
||||
if (condition.is_field()) {
|
||||
params.set_output(0, true);
|
||||
params.set_output(1, true);
|
||||
}
|
||||
else {
|
||||
const bool value = condition.as_value();
|
||||
params.set_output(0, !value);
|
||||
params.set_output(1, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a field as input and extracts the set of anonymous attributes that it references.
|
||||
*/
|
||||
class LazyFunctionForAnonymousAttributeSetExtract : public lf::LazyFunction {
|
||||
private:
|
||||
const ValueOrFieldCPPType &type_;
|
||||
|
||||
public:
|
||||
LazyFunctionForAnonymousAttributeSetExtract(const ValueOrFieldCPPType &type) : type_(type)
|
||||
{
|
||||
debug_name_ = "Extract Attribute Set";
|
||||
inputs_.append_as("Field", type.self);
|
||||
outputs_.append_as("Attributes", CPPType::get<bke::AnonymousAttributeSet>());
|
||||
}
|
||||
|
||||
void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override
|
||||
{
|
||||
const void *value_or_field = params.try_get_input_data_ptr(0);
|
||||
bke::AnonymousAttributeSet attributes;
|
||||
if (type_.is_field(value_or_field)) {
|
||||
const GField &field = *type_.get_field_ptr(value_or_field);
|
||||
field.node().for_each_field_input_recursive([&](const FieldInput &field_input) {
|
||||
if (const auto *attr_field_input = dynamic_cast<const AnonymousAttributeFieldInput *>(
|
||||
&field_input)) {
|
||||
if (!attributes.names) {
|
||||
attributes.names = std::make_shared<Set<std::string>>();
|
||||
}
|
||||
attributes.names->add_as(attr_field_input->anonymous_id()->name());
|
||||
}
|
||||
});
|
||||
}
|
||||
params.set_output(0, std::move(attributes));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Conditionally joines multiple attribute sets. Each input attribute set can be disabled with a
|
||||
* corresponding boolean input.
|
||||
*/
|
||||
class LazyFunctionForAnonymousAttributeSetJoin : public lf::LazyFunction {
|
||||
const int amount_;
|
||||
|
||||
public:
|
||||
LazyFunctionForAnonymousAttributeSetJoin(const int amount) : amount_(amount)
|
||||
{
|
||||
debug_name_ = "Join Attribute Sets";
|
||||
for ([[maybe_unused]] const int i : IndexRange(amount)) {
|
||||
inputs_.append_as("Use", CPPType::get<bool>());
|
||||
inputs_.append_as(
|
||||
"Attribute Set", CPPType::get<bke::AnonymousAttributeSet>(), lf::ValueUsage::Maybe);
|
||||
}
|
||||
outputs_.append_as("Attribute Set", CPPType::get<bke::AnonymousAttributeSet>());
|
||||
}
|
||||
|
||||
void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override
|
||||
{
|
||||
Vector<bke::AnonymousAttributeSet *> sets;
|
||||
bool set_is_missing = false;
|
||||
for (const int i : IndexRange(amount_)) {
|
||||
if (params.get_input<bool>(this->get_use_input(i))) {
|
||||
if (bke::AnonymousAttributeSet *set =
|
||||
params.try_get_input_data_ptr_or_request<bke::AnonymousAttributeSet>(
|
||||
this->get_attribute_set_input(i))) {
|
||||
sets.append(set);
|
||||
}
|
||||
else {
|
||||
set_is_missing = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (set_is_missing) {
|
||||
return;
|
||||
}
|
||||
bke::AnonymousAttributeSet joined_set;
|
||||
if (sets.is_empty()) {
|
||||
/* Nothing to do. */
|
||||
}
|
||||
else if (sets.size() == 1) {
|
||||
joined_set.names = std::move(sets[0]->names);
|
||||
}
|
||||
else {
|
||||
joined_set.names = std::make_shared<Set<std::string>>();
|
||||
for (const bke::AnonymousAttributeSet *set : sets) {
|
||||
if (set->names) {
|
||||
for (const std::string &name : *set->names) {
|
||||
joined_set.names->add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
params.set_output(0, std::move(joined_set));
|
||||
}
|
||||
|
||||
int get_use_input(const int i) const
|
||||
{
|
||||
return 2 * i;
|
||||
}
|
||||
|
||||
int get_attribute_set_input(const int i) const
|
||||
{
|
||||
return 2 * i + 1;
|
||||
}
|
||||
};
|
||||
|
||||
enum class AttributeReferenceKeyType {
|
||||
/** Attribute referenced by a field passed into the group. */
|
||||
InputField,
|
||||
/** Attributes referenced on the output geometry outside of the current group. */
|
||||
OutputGeometry,
|
||||
/** Attribute referenced by a field created within the current group. */
|
||||
Socket,
|
||||
};
|
||||
|
||||
/**
|
||||
* Identifier for something that can reference anonymous attributes that should be propagated.
|
||||
*/
|
||||
struct AttributeReferenceKey {
|
||||
AttributeReferenceKeyType type;
|
||||
/* Used when type is InputField or OutputGeometry. */
|
||||
int index = 0;
|
||||
/* Used when type is Socket. */
|
||||
const bNodeSocket *bsocket = nullptr;
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash_3(this->type, this->bsocket, this->index);
|
||||
}
|
||||
|
||||
friend bool operator==(const AttributeReferenceKey &a, const AttributeReferenceKey &b)
|
||||
{
|
||||
return a.type == b.type && a.bsocket == b.bsocket && a.index == b.index;
|
||||
}
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &stream, const AttributeReferenceKey &value)
|
||||
{
|
||||
if (value.type == AttributeReferenceKeyType::InputField) {
|
||||
stream << "Input Field: " << value.index;
|
||||
}
|
||||
else if (value.type == AttributeReferenceKeyType::OutputGeometry) {
|
||||
stream << "Output Geometry: " << value.index;
|
||||
}
|
||||
else {
|
||||
stream << "Socket: " << value.bsocket->owner_node().name << " -> " << value.bsocket->name;
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Additional information that corresponds to an #AttributeReferenceKey.
|
||||
*/
|
||||
struct AttributeReferenceInfo {
|
||||
/** Output socket that contains an attribute set containing the referenced attributes. */
|
||||
lf::OutputSocket *lf_attribute_set_socket = nullptr;
|
||||
/** Geometry sockets that contain the referenced attributes. */
|
||||
Vector<const bNodeSocket *> initial_geometry_sockets;
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility class to build a lazy-function graph based on a geometry nodes tree.
|
||||
* This is mainly a separate class because it makes it easier to have variables that can be
|
||||
@@ -762,12 +1150,34 @@ struct GeometryNodesLazyFunctionGraphBuilder {
|
||||
Map<const bNodeSocket *, lf::OutputSocket *> output_socket_map_;
|
||||
Map<const bNodeSocket *, lf::Node *> multi_input_socket_nodes_;
|
||||
const bke::DataTypeConversions *conversions_;
|
||||
/**
|
||||
* Maps bsockets to boolean sockets in the graph whereby each boolean socket indicates whether
|
||||
* the bsocket is used. Sockets not contained in this map are not used.
|
||||
* This is indexed by `bNodeSocket::index_in_tree()`.
|
||||
*/
|
||||
Array<lf::OutputSocket *> socket_is_used_map_;
|
||||
/**
|
||||
* Some built-in nodes get additional boolean inputs that indicate whether certain outputs are
|
||||
* used (field output sockets that contain new anonymous attribute references).
|
||||
*/
|
||||
Vector<std::pair<const bNodeSocket *, lf::InputSocket *>> output_used_sockets_for_builtin_nodes_;
|
||||
/**
|
||||
* Maps from output geometry sockets to corresponding attribute set inputs.
|
||||
*/
|
||||
Map<const bNodeSocket *, lf::InputSocket *> attribute_set_propagation_map_;
|
||||
/**
|
||||
* Boolean inputs that tell a node if some socket (of the same or another node) is used. If this
|
||||
* socket is in a link-cycle, its input can become a constant true.
|
||||
*/
|
||||
Set<const lf::InputSocket *> socket_usage_inputs_;
|
||||
|
||||
/**
|
||||
* All group input nodes are combined into one dummy node in the lazy-function graph.
|
||||
*/
|
||||
lf::DummyNode *group_input_lf_node_;
|
||||
|
||||
friend class UsedSocketVisualizeOptions;
|
||||
|
||||
public:
|
||||
GeometryNodesLazyFunctionGraphBuilder(const bNodeTree &btree,
|
||||
GeometryNodesLazyFunctionGraphInfo &lf_graph_info)
|
||||
@@ -783,12 +1193,28 @@ struct GeometryNodesLazyFunctionGraphBuilder {
|
||||
mapping_ = &lf_graph_info_->mapping;
|
||||
conversions_ = &bke::get_implicit_type_conversions();
|
||||
|
||||
socket_is_used_map_.reinitialize(btree_.all_sockets().size());
|
||||
socket_is_used_map_.fill(nullptr);
|
||||
|
||||
this->prepare_node_multi_functions();
|
||||
this->build_group_input_node();
|
||||
if (btree_.group_output_node() == nullptr) {
|
||||
this->build_fallback_output_node();
|
||||
}
|
||||
this->handle_nodes();
|
||||
this->handle_links();
|
||||
this->add_default_inputs();
|
||||
|
||||
this->build_attribute_propagation_input_node();
|
||||
this->build_output_usage_input_node();
|
||||
this->build_input_usage_output_node();
|
||||
this->build_socket_usages();
|
||||
|
||||
this->build_attribute_propagation_sets();
|
||||
this->fix_link_cycles();
|
||||
|
||||
// this->print_graph();
|
||||
|
||||
lf_graph_->update_node_indices();
|
||||
lf_graph_info_->num_inline_nodes_approximate += lf_graph_->nodes().size();
|
||||
}
|
||||
@@ -818,6 +1244,29 @@ struct GeometryNodesLazyFunctionGraphBuilder {
|
||||
lf_graph_info_->dummy_debug_infos_.append(std::move(debug_info));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an output node that just outputs default values in the case when there is no Group
|
||||
* Output node in the tree.
|
||||
*/
|
||||
void build_fallback_output_node()
|
||||
{
|
||||
Vector<const CPPType *, 16> output_cpp_types;
|
||||
auto debug_info = std::make_unique<GroupOutputDebugInfo>();
|
||||
for (const bNodeSocket *interface_output : btree_.interface_outputs()) {
|
||||
output_cpp_types.append(interface_output->typeinfo->geometry_nodes_cpp_type);
|
||||
debug_info->socket_names.append(interface_output->name);
|
||||
}
|
||||
|
||||
lf::Node &lf_node = lf_graph_->add_dummy(output_cpp_types, {}, debug_info.get());
|
||||
for (lf::InputSocket *lf_socket : lf_node.inputs()) {
|
||||
const CPPType &type = lf_socket->type();
|
||||
lf_socket->set_default_value(type.default_value());
|
||||
}
|
||||
mapping_->standard_group_output_sockets = lf_node.inputs();
|
||||
|
||||
lf_graph_info_->dummy_debug_infos_.append(std::move(debug_info));
|
||||
}
|
||||
|
||||
void handle_nodes()
|
||||
{
|
||||
/* Insert all nodes into the lazy function graph. */
|
||||
@@ -935,22 +1384,25 @@ struct GeometryNodesLazyFunctionGraphBuilder {
|
||||
void handle_group_output_node(const bNode &bnode)
|
||||
{
|
||||
Vector<const CPPType *, 16> output_cpp_types;
|
||||
const Span<const bNodeSocket *> interface_outputs = btree_.interface_outputs();
|
||||
for (const bNodeSocket *interface_input : interface_outputs) {
|
||||
auto debug_info = std::make_unique<GroupOutputDebugInfo>();
|
||||
for (const bNodeSocket *interface_input : btree_.interface_outputs()) {
|
||||
output_cpp_types.append(interface_input->typeinfo->geometry_nodes_cpp_type);
|
||||
debug_info->socket_names.append(interface_input->name);
|
||||
}
|
||||
|
||||
auto debug_info = std::make_unique<GroupOutputDebugInfo>();
|
||||
lf::DummyNode &group_output_lf_node = lf_graph_->add_dummy(
|
||||
output_cpp_types, {}, debug_info.get());
|
||||
|
||||
for (const int i : interface_outputs.index_range()) {
|
||||
for (const int i : group_output_lf_node.inputs().index_range()) {
|
||||
const bNodeSocket &bsocket = bnode.input_socket(i);
|
||||
lf::InputSocket &lf_socket = group_output_lf_node.input(i);
|
||||
input_socket_map_.add(&bsocket, &lf_socket);
|
||||
mapping_->dummy_socket_map.add(&bsocket, &lf_socket);
|
||||
mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket);
|
||||
debug_info->socket_names.append(interface_outputs[i]->name);
|
||||
}
|
||||
|
||||
if (&bnode == btree_.group_output_node()) {
|
||||
mapping_->standard_group_output_sockets = group_output_lf_node.inputs();
|
||||
}
|
||||
|
||||
lf_graph_info_->dummy_debug_infos_.append(std::move(debug_info));
|
||||
@@ -968,21 +1420,18 @@ struct GeometryNodesLazyFunctionGraphBuilder {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<const bNodeSocket *> used_inputs;
|
||||
Vector<const bNodeSocket *> used_outputs;
|
||||
auto lazy_function = std::make_unique<LazyFunctionForGroupNode>(
|
||||
bnode, *group_lf_graph_info, used_inputs, used_outputs);
|
||||
auto lazy_function = std::make_unique<LazyFunctionForGroupNode>(bnode, *group_lf_graph_info);
|
||||
lf::FunctionNode &lf_node = lf_graph_->add_function(*lazy_function);
|
||||
lf_graph_info_->functions.append(std::move(lazy_function));
|
||||
for (const int i : used_inputs.index_range()) {
|
||||
const bNodeSocket &bsocket = *used_inputs[i];
|
||||
|
||||
for (const int i : bnode.input_sockets().index_range()) {
|
||||
const bNodeSocket &bsocket = bnode.input_socket(i);
|
||||
BLI_assert(!bsocket.is_multi_input());
|
||||
lf::InputSocket &lf_socket = lf_node.input(i);
|
||||
input_socket_map_.add(&bsocket, &lf_socket);
|
||||
mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket);
|
||||
}
|
||||
for (const int i : used_outputs.index_range()) {
|
||||
const bNodeSocket &bsocket = *used_outputs[i];
|
||||
for (const int i : bnode.output_sockets().index_range()) {
|
||||
const bNodeSocket &bsocket = bnode.output_socket(i);
|
||||
lf::OutputSocket &lf_socket = lf_node.output(i);
|
||||
output_socket_map_.add_new(&bsocket, &lf_socket);
|
||||
mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket);
|
||||
@@ -990,6 +1439,18 @@ struct GeometryNodesLazyFunctionGraphBuilder {
|
||||
mapping_->group_node_map.add(&bnode, &lf_node);
|
||||
lf_graph_info_->num_inline_nodes_approximate +=
|
||||
group_lf_graph_info->num_inline_nodes_approximate;
|
||||
static const bool static_false = false;
|
||||
for (const int i : lazy_function->lf_input_for_output_bsocket_usage_.values()) {
|
||||
lf_node.input(i).set_default_value(&static_false);
|
||||
socket_usage_inputs_.add(&lf_node.input(i));
|
||||
}
|
||||
/* Keep track of attribute set inputs that need to be populated later. */
|
||||
for (const auto [output_index, lf_input_index] :
|
||||
lazy_function->lf_input_for_attribute_propagation_to_output_.items()) {
|
||||
attribute_set_propagation_map_.add(&bnode.output_socket(output_index),
|
||||
&lf_node.input(lf_input_index));
|
||||
}
|
||||
lf_graph_info_->functions.append(std::move(lazy_function));
|
||||
}
|
||||
|
||||
void handle_geometry_node(const bNode &bnode)
|
||||
@@ -999,7 +1460,6 @@ struct GeometryNodesLazyFunctionGraphBuilder {
|
||||
auto lazy_function = std::make_unique<LazyFunctionForGeometryNode>(
|
||||
bnode, used_inputs, used_outputs);
|
||||
lf::Node &lf_node = lf_graph_->add_function(*lazy_function);
|
||||
lf_graph_info_->functions.append(std::move(lazy_function));
|
||||
|
||||
for (const int i : used_inputs.index_range()) {
|
||||
const bNodeSocket &bsocket = *used_inputs[i];
|
||||
@@ -1026,6 +1486,21 @@ struct GeometryNodesLazyFunctionGraphBuilder {
|
||||
output_socket_map_.add_new(&bsocket, &lf_socket);
|
||||
mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket);
|
||||
}
|
||||
|
||||
for (const auto [identifier, lf_input_index] :
|
||||
lazy_function->lf_input_for_output_bsocket_usage_.items()) {
|
||||
output_used_sockets_for_builtin_nodes_.append_as(&bnode.output_by_identifier(identifier),
|
||||
&lf_node.input(lf_input_index));
|
||||
socket_usage_inputs_.add_new(&lf_node.input(lf_input_index));
|
||||
}
|
||||
/* Keep track of attribute set inputs that need to be populated later. */
|
||||
for (const auto [identifier, lf_input_index] :
|
||||
lazy_function->lf_input_for_attribute_propagation_to_output_.items()) {
|
||||
attribute_set_propagation_map_.add(&bnode.output_by_identifier(identifier),
|
||||
&lf_node.input(lf_input_index));
|
||||
}
|
||||
|
||||
lf_graph_info_->functions.append(std::move(lazy_function));
|
||||
}
|
||||
|
||||
void handle_multi_function_node(const bNode &bnode, const NodeMultiFunctions::Item &fn_item)
|
||||
@@ -1269,8 +1744,985 @@ struct GeometryNodesLazyFunctionGraphBuilder {
|
||||
lf_graph_->add_link(lf_node.output(0), input_lf_socket);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Every output geometry socket that may propagate attributes has to know which attributes should
|
||||
* be propagated. Therefore, every one of these outputs gets a corresponding attribute set input.
|
||||
*/
|
||||
void build_attribute_propagation_input_node()
|
||||
{
|
||||
const aal::RelationsInNode &tree_relations = *btree_.runtime->anonymous_attribute_relations;
|
||||
Vector<int> output_indices;
|
||||
for (const aal::PropagateRelation &relation : tree_relations.propagate_relations) {
|
||||
output_indices.append_non_duplicates(relation.to_geometry_output);
|
||||
}
|
||||
Vector<const CPPType *> cpp_types;
|
||||
auto debug_info = std::make_unique<lf::SimpleDummyDebugInfo>();
|
||||
debug_info->name = "Attributes to Propagate to Output";
|
||||
cpp_types.append_n_times(&CPPType::get<bke::AnonymousAttributeSet>(), output_indices.size());
|
||||
lf::Node &lf_node = lf_graph_->add_dummy({}, cpp_types, debug_info.get());
|
||||
for (const int i : output_indices.index_range()) {
|
||||
const int output_index = output_indices[i];
|
||||
mapping_->attribute_set_by_geometry_output.add(output_index, &lf_node.output(i));
|
||||
debug_info->output_names.append(btree_.interface_outputs()[output_index]->name);
|
||||
}
|
||||
lf_graph_info_->dummy_debug_infos_.append(std::move(debug_info));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build new boolean group inputs that indicate which group outputs are used.
|
||||
*/
|
||||
void build_output_usage_input_node()
|
||||
{
|
||||
const Span<const bNodeSocket *> interface_outputs = btree_.interface_outputs();
|
||||
|
||||
Vector<const CPPType *> cpp_types;
|
||||
cpp_types.append_n_times(&CPPType::get<bool>(), interface_outputs.size());
|
||||
auto debug_info = std::make_unique<lf::SimpleDummyDebugInfo>();
|
||||
debug_info->name = "Output Socket Usage";
|
||||
lf::Node &lf_node = lf_graph_->add_dummy({}, cpp_types, debug_info.get());
|
||||
for (const int i : interface_outputs.index_range()) {
|
||||
mapping_->group_output_used_sockets.append(&lf_node.output(i));
|
||||
debug_info->output_names.append(interface_outputs[i]->name);
|
||||
}
|
||||
lf_graph_info_->dummy_debug_infos_.append(std::move(debug_info));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build new boolean group outputs that indicate which group inputs are used depending on other
|
||||
* group inputs.
|
||||
*/
|
||||
void build_input_usage_output_node()
|
||||
{
|
||||
const Span<const bNodeSocket *> interface_inputs = btree_.interface_inputs();
|
||||
|
||||
Vector<const CPPType *> cpp_types;
|
||||
cpp_types.append_n_times(&CPPType::get<bool>(), interface_inputs.size());
|
||||
auto debug_info = std::make_unique<lf::SimpleDummyDebugInfo>();
|
||||
debug_info->name = "Input Socket Usage";
|
||||
lf::Node &lf_node = lf_graph_->add_dummy(cpp_types, {}, debug_info.get());
|
||||
for (const int i : interface_inputs.index_range()) {
|
||||
mapping_->group_input_usage_sockets.append(&lf_node.input(i));
|
||||
debug_info->input_names.append(interface_inputs[i]->name);
|
||||
}
|
||||
lf_graph_info_->dummy_debug_infos_.append(std::move(debug_info));
|
||||
}
|
||||
|
||||
/**
|
||||
* For every socket we want to determine if it will be used depending on the inputs of the node
|
||||
* group (just static analysis is not enough when there are e.g. Switch nodes). This function
|
||||
* populates #socket_is_used_map_ with that information.
|
||||
*/
|
||||
void build_socket_usages()
|
||||
{
|
||||
OrSocketUsagesCache or_socket_usages_cache;
|
||||
|
||||
if (const bNode *group_output_bnode = btree_.group_output_node()) {
|
||||
/* Whether a group output is used is determined by a group input that has been created
|
||||
* exactly for this purpose. */
|
||||
for (const bNodeSocket *bsocket : group_output_bnode->input_sockets().drop_back(1)) {
|
||||
const int index = bsocket->index();
|
||||
socket_is_used_map_[bsocket->index_in_tree()] = const_cast<lf::OutputSocket *>(
|
||||
mapping_->group_output_used_sockets[index]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Iterate over all nodes from right to left to determine when which sockets are used. */
|
||||
for (const bNode *bnode : btree_.toposort_right_to_left()) {
|
||||
const bNodeType *node_type = bnode->typeinfo;
|
||||
if (node_type == nullptr) {
|
||||
/* Ignore. */
|
||||
continue;
|
||||
}
|
||||
|
||||
this->build_output_socket_usages(*bnode, or_socket_usages_cache);
|
||||
|
||||
if (bnode->is_muted()) {
|
||||
this->build_muted_node_usages(*bnode, or_socket_usages_cache);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (node_type->type) {
|
||||
case NODE_GROUP_OUTPUT: {
|
||||
/* Handled before this loop already. */
|
||||
break;
|
||||
}
|
||||
case NODE_GROUP_INPUT: {
|
||||
/* Handled after this loop. */
|
||||
break;
|
||||
}
|
||||
case NODE_FRAME: {
|
||||
/* Ignored. */
|
||||
break;
|
||||
}
|
||||
case NODE_REROUTE: {
|
||||
/* The input is used exactly when the output is used. */
|
||||
socket_is_used_map_[bnode->input_socket(0).index_in_tree()] =
|
||||
socket_is_used_map_[bnode->output_socket(0).index_in_tree()];
|
||||
break;
|
||||
}
|
||||
case GEO_NODE_SWITCH: {
|
||||
this->build_switch_node_socket_usage(*bnode);
|
||||
break;
|
||||
}
|
||||
case GEO_NODE_VIEWER: {
|
||||
this->build_viewer_node_socket_usage(*bnode);
|
||||
break;
|
||||
}
|
||||
case NODE_GROUP:
|
||||
case NODE_CUSTOM_GROUP: {
|
||||
this->build_group_node_socket_usage(*bnode, or_socket_usages_cache);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
this->build_standard_node_input_socket_usage(*bnode, or_socket_usages_cache);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->build_group_input_usages(or_socket_usages_cache);
|
||||
this->link_output_used_sockets_for_builtin_nodes();
|
||||
}
|
||||
|
||||
using OrSocketUsagesCache = Map<Vector<lf::OutputSocket *>, lf::OutputSocket *>;
|
||||
|
||||
/**
|
||||
* Combine multiple socket usages with a logical or. Inserts a new node for that purpose if
|
||||
* necessary.
|
||||
*/
|
||||
lf::OutputSocket *or_socket_usages(MutableSpan<lf::OutputSocket *> usages,
|
||||
OrSocketUsagesCache &cache)
|
||||
{
|
||||
if (usages.is_empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
if (usages.size() == 1) {
|
||||
return usages[0];
|
||||
}
|
||||
|
||||
std::sort(usages.begin(), usages.end());
|
||||
return cache.lookup_or_add_cb_as(usages, [&]() {
|
||||
auto logical_or_fn = std::make_unique<LazyFunctionForLogicalOr>(usages.size());
|
||||
lf::Node &logical_or_node = lf_graph_->add_function(*logical_or_fn);
|
||||
lf_graph_info_->functions.append(std::move(logical_or_fn));
|
||||
|
||||
for (const int i : usages.index_range()) {
|
||||
lf_graph_->add_link(*usages[i], logical_or_node.input(i));
|
||||
}
|
||||
return &logical_or_node.output(0);
|
||||
});
|
||||
}
|
||||
|
||||
void build_output_socket_usages(const bNode &bnode, OrSocketUsagesCache &or_socket_usages_cache)
|
||||
{
|
||||
/* Output sockets are used when any of their linked inputs are used. */
|
||||
for (const bNodeSocket *socket : bnode.output_sockets()) {
|
||||
if (!socket->is_available()) {
|
||||
continue;
|
||||
}
|
||||
/* Determine when linked target sockets are used. */
|
||||
Vector<lf::OutputSocket *> target_usages;
|
||||
for (const bNodeLink *link : socket->directly_linked_links()) {
|
||||
if (!link->is_used()) {
|
||||
continue;
|
||||
}
|
||||
const bNodeSocket &target_socket = *link->tosock;
|
||||
if (lf::OutputSocket *is_used_socket =
|
||||
socket_is_used_map_[target_socket.index_in_tree()]) {
|
||||
target_usages.append_non_duplicates(is_used_socket);
|
||||
}
|
||||
}
|
||||
/* Combine target socket usages into the usage of the current socket. */
|
||||
socket_is_used_map_[socket->index_in_tree()] = this->or_socket_usages(
|
||||
target_usages, or_socket_usages_cache);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An input of a muted node is used when any of its internally linked outputs is used.
|
||||
*/
|
||||
void build_muted_node_usages(const bNode &bnode, OrSocketUsagesCache &or_socket_usages_cache)
|
||||
{
|
||||
/* Find all outputs that use a specific input. */
|
||||
MultiValueMap<const bNodeSocket *, const bNodeSocket *> outputs_by_input;
|
||||
for (const bNodeLink *blink : bnode.internal_links()) {
|
||||
outputs_by_input.add(blink->fromsock, blink->tosock);
|
||||
}
|
||||
for (const auto item : outputs_by_input.items()) {
|
||||
const bNodeSocket &input_bsocket = *item.key;
|
||||
const Span<const bNodeSocket *> output_bsockets = item.value;
|
||||
|
||||
/* The input is used if any of the internally linked outputs is used. */
|
||||
Vector<lf::OutputSocket *> lf_socket_usages;
|
||||
for (const bNodeSocket *output_bsocket : output_bsockets) {
|
||||
if (lf::OutputSocket *lf_socket = socket_is_used_map_[output_bsocket->index_in_tree()]) {
|
||||
lf_socket_usages.append(lf_socket);
|
||||
}
|
||||
}
|
||||
socket_is_used_map_[input_bsocket.index_in_tree()] = this->or_socket_usages(
|
||||
lf_socket_usages, or_socket_usages_cache);
|
||||
}
|
||||
}
|
||||
|
||||
void build_switch_node_socket_usage(const bNode &bnode)
|
||||
{
|
||||
const bNodeSocket *switch_input_bsocket = nullptr;
|
||||
const bNodeSocket *false_input_bsocket = nullptr;
|
||||
const bNodeSocket *true_input_bsocket = nullptr;
|
||||
const bNodeSocket *output_bsocket = nullptr;
|
||||
for (const bNodeSocket *socket : bnode.input_sockets()) {
|
||||
if (!socket->is_available()) {
|
||||
continue;
|
||||
}
|
||||
if (socket->name == StringRef("Switch")) {
|
||||
switch_input_bsocket = socket;
|
||||
}
|
||||
else if (socket->name == StringRef("False")) {
|
||||
false_input_bsocket = socket;
|
||||
}
|
||||
else if (socket->name == StringRef("True")) {
|
||||
true_input_bsocket = socket;
|
||||
}
|
||||
}
|
||||
for (const bNodeSocket *socket : bnode.output_sockets()) {
|
||||
if (socket->is_available()) {
|
||||
output_bsocket = socket;
|
||||
break;
|
||||
}
|
||||
}
|
||||
lf::OutputSocket *output_is_used_socket = socket_is_used_map_[output_bsocket->index_in_tree()];
|
||||
if (output_is_used_socket == nullptr) {
|
||||
return;
|
||||
}
|
||||
socket_is_used_map_[switch_input_bsocket->index_in_tree()] = output_is_used_socket;
|
||||
lf::InputSocket *lf_switch_input = input_socket_map_.lookup(switch_input_bsocket)[0];
|
||||
if (lf::OutputSocket *lf_switch_origin = lf_switch_input->origin()) {
|
||||
/* The condition input is dynamic, so the usage of the other inputs is as well. */
|
||||
static const LazyFunctionForSwitchSocketUsage switch_socket_usage_fn;
|
||||
lf::Node &lf_node = lf_graph_->add_function(switch_socket_usage_fn);
|
||||
lf_graph_->add_link(*lf_switch_origin, lf_node.input(0));
|
||||
socket_is_used_map_[false_input_bsocket->index_in_tree()] = &lf_node.output(0);
|
||||
socket_is_used_map_[true_input_bsocket->index_in_tree()] = &lf_node.output(1);
|
||||
}
|
||||
else {
|
||||
if (switch_input_bsocket->default_value_typed<bNodeSocketValueBoolean>()->value) {
|
||||
socket_is_used_map_[true_input_bsocket->index_in_tree()] = output_is_used_socket;
|
||||
}
|
||||
else {
|
||||
socket_is_used_map_[false_input_bsocket->index_in_tree()] = output_is_used_socket;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void build_viewer_node_socket_usage(const bNode &bnode)
|
||||
{
|
||||
const lf::FunctionNode &lf_viewer_node = *mapping_->viewer_node_map.lookup(&bnode);
|
||||
auto lazy_function = std::make_unique<LazyFunctionForViewerInputUsage>(lf_viewer_node);
|
||||
lf::Node &lf_node = lf_graph_->add_function(*lazy_function);
|
||||
lf_graph_info_->functions.append(std::move(lazy_function));
|
||||
|
||||
for (const bNodeSocket *bsocket : bnode.input_sockets()) {
|
||||
if (bsocket->is_available()) {
|
||||
socket_is_used_map_[bsocket->index_in_tree()] = &lf_node.output(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void build_group_node_socket_usage(const bNode &bnode,
|
||||
OrSocketUsagesCache &or_socket_usages_cache)
|
||||
{
|
||||
const bNodeTree *bgroup = reinterpret_cast<const bNodeTree *>(bnode.id);
|
||||
if (bgroup == nullptr) {
|
||||
return;
|
||||
}
|
||||
const GeometryNodesLazyFunctionGraphInfo *group_lf_graph_info =
|
||||
ensure_geometry_nodes_lazy_function_graph(*bgroup);
|
||||
if (group_lf_graph_info == nullptr) {
|
||||
return;
|
||||
}
|
||||
lf::FunctionNode &lf_group_node = const_cast<lf::FunctionNode &>(
|
||||
*mapping_->group_node_map.lookup(&bnode));
|
||||
const auto &fn = static_cast<const LazyFunctionForGroupNode &>(lf_group_node.function());
|
||||
|
||||
for (const bNodeSocket *input_bsocket : bnode.input_sockets()) {
|
||||
const int input_index = input_bsocket->index();
|
||||
const InputUsageHint &input_usage_hint =
|
||||
group_lf_graph_info->mapping.group_input_usage_hints[input_index];
|
||||
switch (input_usage_hint.type) {
|
||||
case InputUsageHintType::Never: {
|
||||
/* Nothing to do. */
|
||||
break;
|
||||
}
|
||||
case InputUsageHintType::DependsOnOutput: {
|
||||
Vector<lf::OutputSocket *> output_usages;
|
||||
for (const int i : input_usage_hint.output_dependencies) {
|
||||
if (lf::OutputSocket *lf_socket =
|
||||
socket_is_used_map_[bnode.output_socket(i).index_in_tree()]) {
|
||||
output_usages.append(lf_socket);
|
||||
}
|
||||
}
|
||||
socket_is_used_map_[input_bsocket->index_in_tree()] = this->or_socket_usages(
|
||||
output_usages, or_socket_usages_cache);
|
||||
break;
|
||||
}
|
||||
case InputUsageHintType::DynamicSocket: {
|
||||
socket_is_used_map_[input_bsocket->index_in_tree()] = &const_cast<lf::OutputSocket &>(
|
||||
lf_group_node.output(fn.lf_output_for_input_bsocket_usage_.lookup(input_index)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const bNodeSocket *output_bsocket : bnode.output_sockets()) {
|
||||
const int output_index = output_bsocket->index();
|
||||
const int lf_input_index = fn.lf_input_for_output_bsocket_usage_.lookup(output_index);
|
||||
lf::InputSocket &lf_socket = lf_group_node.input(lf_input_index);
|
||||
if (lf::OutputSocket *lf_output_is_used =
|
||||
socket_is_used_map_[output_bsocket->index_in_tree()]) {
|
||||
lf_graph_->add_link(*lf_output_is_used, lf_socket);
|
||||
}
|
||||
else {
|
||||
static const bool static_false = false;
|
||||
lf_socket.set_default_value(&static_false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void build_standard_node_input_socket_usage(const bNode &bnode,
|
||||
OrSocketUsagesCache &or_socket_usages_cache)
|
||||
{
|
||||
if (bnode.input_sockets().is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<lf::OutputSocket *> output_usages;
|
||||
for (const bNodeSocket *output_socket : bnode.output_sockets()) {
|
||||
if (!output_socket->is_available()) {
|
||||
continue;
|
||||
}
|
||||
if (lf::OutputSocket *is_used_socket = socket_is_used_map_[output_socket->index_in_tree()]) {
|
||||
output_usages.append_non_duplicates(is_used_socket);
|
||||
}
|
||||
}
|
||||
|
||||
/* Assume every input is used when any output is used. */
|
||||
lf::OutputSocket *lf_usage = this->or_socket_usages(output_usages, or_socket_usages_cache);
|
||||
if (lf_usage == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const bNodeSocket *input_socket : bnode.input_sockets()) {
|
||||
if (input_socket->is_available()) {
|
||||
socket_is_used_map_[input_socket->index_in_tree()] = lf_usage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void build_group_input_usages(OrSocketUsagesCache &or_socket_usages_cache)
|
||||
{
|
||||
const Span<const bNode *> group_input_nodes = btree_.group_input_nodes();
|
||||
for (const int i : btree_.interface_inputs().index_range()) {
|
||||
Vector<lf::OutputSocket *> target_usages;
|
||||
for (const bNode *group_input_node : group_input_nodes) {
|
||||
if (lf::OutputSocket *lf_socket =
|
||||
socket_is_used_map_[group_input_node->output_socket(i).index_in_tree()]) {
|
||||
target_usages.append_non_duplicates(lf_socket);
|
||||
}
|
||||
}
|
||||
|
||||
lf::OutputSocket *lf_socket = this->or_socket_usages(target_usages, or_socket_usages_cache);
|
||||
lf::InputSocket *lf_group_output = const_cast<lf::InputSocket *>(
|
||||
mapping_->group_input_usage_sockets[i]);
|
||||
InputUsageHint input_usage_hint;
|
||||
if (lf_socket == nullptr) {
|
||||
static const bool static_false = false;
|
||||
lf_group_output->set_default_value(&static_false);
|
||||
input_usage_hint.type = InputUsageHintType::Never;
|
||||
}
|
||||
else {
|
||||
lf_graph_->add_link(*lf_socket, *lf_group_output);
|
||||
if (lf_socket->node().is_dummy()) {
|
||||
/* Can support slightly more complex cases where it depends on more than one output in
|
||||
* the future. */
|
||||
input_usage_hint.type = InputUsageHintType::DependsOnOutput;
|
||||
input_usage_hint.output_dependencies = {
|
||||
mapping_->group_output_used_sockets.first_index_of(lf_socket)};
|
||||
}
|
||||
else {
|
||||
input_usage_hint.type = InputUsageHintType::DynamicSocket;
|
||||
}
|
||||
}
|
||||
lf_graph_info_->mapping.group_input_usage_hints.append(std::move(input_usage_hint));
|
||||
}
|
||||
}
|
||||
|
||||
void link_output_used_sockets_for_builtin_nodes()
|
||||
{
|
||||
for (const auto &[output_bsocket, lf_input] : output_used_sockets_for_builtin_nodes_) {
|
||||
if (lf::OutputSocket *lf_is_used = socket_is_used_map_[output_bsocket->index_in_tree()]) {
|
||||
lf_graph_->add_link(*lf_is_used, *lf_input);
|
||||
}
|
||||
else {
|
||||
static const bool static_false = false;
|
||||
lf_input->set_default_value(&static_false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void build_attribute_propagation_sets()
|
||||
{
|
||||
ResourceScope scope;
|
||||
const Array<const aal::RelationsInNode *> relations_by_node =
|
||||
bke::anonymous_attribute_inferencing::get_relations_by_node(btree_, scope);
|
||||
|
||||
VectorSet<AttributeReferenceKey> attribute_reference_keys;
|
||||
/* Indexed by reference key index. */
|
||||
Vector<AttributeReferenceInfo> attribute_reference_infos;
|
||||
this->build_attribute_references(
|
||||
relations_by_node, attribute_reference_keys, attribute_reference_infos);
|
||||
|
||||
MultiValueMap<const bNodeSocket *, int> referenced_by_field_socket;
|
||||
MultiValueMap<const bNodeSocket *, int> propagated_to_geometry_socket;
|
||||
this->gather_referenced_and_potentially_propagated_data(relations_by_node,
|
||||
attribute_reference_keys,
|
||||
attribute_reference_infos,
|
||||
referenced_by_field_socket,
|
||||
propagated_to_geometry_socket);
|
||||
|
||||
MultiValueMap<const bNodeSocket *, int> required_propagated_to_geometry_socket;
|
||||
this->gather_required_propagated_data(relations_by_node,
|
||||
attribute_reference_keys,
|
||||
referenced_by_field_socket,
|
||||
propagated_to_geometry_socket,
|
||||
required_propagated_to_geometry_socket);
|
||||
|
||||
this->build_attribute_sets_to_propagate(attribute_reference_keys,
|
||||
attribute_reference_infos,
|
||||
required_propagated_to_geometry_socket);
|
||||
}
|
||||
|
||||
void build_attribute_references(const Span<const aal::RelationsInNode *> relations_by_node,
|
||||
VectorSet<AttributeReferenceKey> &r_attribute_reference_keys,
|
||||
Vector<AttributeReferenceInfo> &r_attribute_reference_infos)
|
||||
{
|
||||
auto add_get_attributes_node = [&](lf::OutputSocket &lf_field_socket) -> lf::OutputSocket & {
|
||||
const ValueOrFieldCPPType &type = *ValueOrFieldCPPType::get_from_self(
|
||||
lf_field_socket.type());
|
||||
auto lazy_function = std::make_unique<LazyFunctionForAnonymousAttributeSetExtract>(type);
|
||||
lf::Node &lf_node = lf_graph_->add_function(*lazy_function);
|
||||
lf_graph_->add_link(lf_field_socket, lf_node.input(0));
|
||||
lf_graph_info_->functions.append(std::move(lazy_function));
|
||||
return lf_node.output(0);
|
||||
};
|
||||
|
||||
/* Find nodes that create new anonymous attributes. */
|
||||
for (const bNode *node : btree_.all_nodes()) {
|
||||
const aal::RelationsInNode &relations = *relations_by_node[node->index()];
|
||||
for (const aal::AvailableRelation &relation : relations.available_relations) {
|
||||
const bNodeSocket &geometry_bsocket = node->output_socket(relation.geometry_output);
|
||||
const bNodeSocket &field_bsocket = node->output_socket(relation.field_output);
|
||||
if (!field_bsocket.is_available()) {
|
||||
continue;
|
||||
}
|
||||
if (!field_bsocket.is_directly_linked()) {
|
||||
continue;
|
||||
}
|
||||
AttributeReferenceKey key;
|
||||
key.type = AttributeReferenceKeyType::Socket;
|
||||
key.bsocket = &field_bsocket;
|
||||
const int key_index = r_attribute_reference_keys.index_of_or_add(key);
|
||||
if (key_index >= r_attribute_reference_infos.size()) {
|
||||
AttributeReferenceInfo info;
|
||||
lf::OutputSocket &lf_field_socket = *output_socket_map_.lookup(&field_bsocket);
|
||||
info.lf_attribute_set_socket = &add_get_attributes_node(lf_field_socket);
|
||||
r_attribute_reference_infos.append(info);
|
||||
}
|
||||
AttributeReferenceInfo &info = r_attribute_reference_infos[key_index];
|
||||
if (geometry_bsocket.is_available()) {
|
||||
info.initial_geometry_sockets.append(&geometry_bsocket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Find field group inputs that are evaluated within this node tree. */
|
||||
const aal::RelationsInNode &tree_relations = *btree_.runtime->anonymous_attribute_relations;
|
||||
for (const aal::EvalRelation &relation : tree_relations.eval_relations) {
|
||||
AttributeReferenceKey key;
|
||||
key.type = AttributeReferenceKeyType::InputField;
|
||||
key.index = relation.field_input;
|
||||
r_attribute_reference_keys.add_new(key);
|
||||
AttributeReferenceInfo info;
|
||||
lf::OutputSocket &lf_field_socket = *const_cast<lf::OutputSocket *>(
|
||||
mapping_->group_input_sockets[relation.field_input]);
|
||||
info.lf_attribute_set_socket = &add_get_attributes_node(lf_field_socket);
|
||||
for (const bNode *bnode : btree_.group_input_nodes()) {
|
||||
info.initial_geometry_sockets.append(&bnode->output_socket(relation.geometry_input));
|
||||
}
|
||||
r_attribute_reference_infos.append(std::move(info));
|
||||
}
|
||||
/* Find group outputs that attributes need to be propagated to. */
|
||||
for (const aal::PropagateRelation &relation : tree_relations.propagate_relations) {
|
||||
AttributeReferenceKey key;
|
||||
key.type = AttributeReferenceKeyType::OutputGeometry;
|
||||
key.index = relation.to_geometry_output;
|
||||
const int key_index = r_attribute_reference_keys.index_of_or_add(key);
|
||||
if (key_index >= r_attribute_reference_infos.size()) {
|
||||
AttributeReferenceInfo info;
|
||||
info.lf_attribute_set_socket = const_cast<lf::OutputSocket *>(
|
||||
mapping_->attribute_set_by_geometry_output.lookup(relation.to_geometry_output));
|
||||
r_attribute_reference_infos.append(info);
|
||||
}
|
||||
AttributeReferenceInfo &info = r_attribute_reference_infos[key_index];
|
||||
for (const bNode *bnode : btree_.group_input_nodes()) {
|
||||
info.initial_geometry_sockets.append(&bnode->output_socket(relation.from_geometry_input));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For every field socket, figure out which anonymous attributes it may reference.
|
||||
* For every geometry socket, figure out which anonymous attributes may be propagated to it.
|
||||
*/
|
||||
void gather_referenced_and_potentially_propagated_data(
|
||||
const Span<const aal::RelationsInNode *> relations_by_node,
|
||||
const Span<AttributeReferenceKey> attribute_reference_keys,
|
||||
const Span<AttributeReferenceInfo> attribute_reference_infos,
|
||||
MultiValueMap<const bNodeSocket *, int> &r_referenced_by_field_socket,
|
||||
MultiValueMap<const bNodeSocket *, int> &r_propagated_to_geometry_socket)
|
||||
{
|
||||
/* Initialize maps. */
|
||||
for (const int key_index : attribute_reference_keys.index_range()) {
|
||||
const AttributeReferenceKey &key = attribute_reference_keys[key_index];
|
||||
const AttributeReferenceInfo &info = attribute_reference_infos[key_index];
|
||||
switch (key.type) {
|
||||
case AttributeReferenceKeyType::InputField: {
|
||||
for (const bNode *bnode : btree_.group_input_nodes()) {
|
||||
const bNodeSocket &bsocket = bnode->output_socket(key.index);
|
||||
r_referenced_by_field_socket.add(&bsocket, key_index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AttributeReferenceKeyType::OutputGeometry: {
|
||||
break;
|
||||
}
|
||||
case AttributeReferenceKeyType::Socket: {
|
||||
r_referenced_by_field_socket.add(key.bsocket, key_index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const bNodeSocket *geometry_bsocket : info.initial_geometry_sockets) {
|
||||
r_propagated_to_geometry_socket.add(geometry_bsocket, key_index);
|
||||
}
|
||||
}
|
||||
/* Propagate attribute usages from left to right. */
|
||||
for (const bNode *bnode : btree_.toposort_left_to_right()) {
|
||||
for (const bNodeSocket *bsocket : bnode->input_sockets()) {
|
||||
if (bsocket->is_available()) {
|
||||
Vector<int> referenced_keys;
|
||||
Vector<int> propagated_keys;
|
||||
for (const bNodeLink *blink : bsocket->directly_linked_links()) {
|
||||
if (blink->is_used()) {
|
||||
referenced_keys.extend_non_duplicates(
|
||||
r_referenced_by_field_socket.lookup(blink->fromsock));
|
||||
propagated_keys.extend_non_duplicates(
|
||||
r_propagated_to_geometry_socket.lookup(blink->fromsock));
|
||||
}
|
||||
}
|
||||
if (!referenced_keys.is_empty()) {
|
||||
r_referenced_by_field_socket.add_multiple(bsocket, referenced_keys);
|
||||
}
|
||||
if (!propagated_keys.is_empty()) {
|
||||
r_propagated_to_geometry_socket.add_multiple(bsocket, propagated_keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
const aal::RelationsInNode &relations = *relations_by_node[bnode->index()];
|
||||
for (const aal::ReferenceRelation &relation : relations.reference_relations) {
|
||||
const bNodeSocket &input_bsocket = bnode->input_socket(relation.from_field_input);
|
||||
const bNodeSocket &output_bsocket = bnode->output_socket(relation.to_field_output);
|
||||
if (!input_bsocket.is_available() || !output_bsocket.is_available()) {
|
||||
continue;
|
||||
}
|
||||
r_referenced_by_field_socket.add_multiple(
|
||||
&output_bsocket, Vector<int>(r_referenced_by_field_socket.lookup(&input_bsocket)));
|
||||
}
|
||||
for (const aal::PropagateRelation &relation : relations.propagate_relations) {
|
||||
const bNodeSocket &input_bsocket = bnode->input_socket(relation.from_geometry_input);
|
||||
const bNodeSocket &output_bsocket = bnode->output_socket(relation.to_geometry_output);
|
||||
if (!input_bsocket.is_available() || !output_bsocket.is_available()) {
|
||||
continue;
|
||||
}
|
||||
r_propagated_to_geometry_socket.add_multiple(
|
||||
&output_bsocket, Vector<int>(r_propagated_to_geometry_socket.lookup(&input_bsocket)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which anonymous attributes should be propagated to which geometry sockets.
|
||||
*/
|
||||
void gather_required_propagated_data(
|
||||
const Span<const aal::RelationsInNode *> relations_by_node,
|
||||
const VectorSet<AttributeReferenceKey> &attribute_reference_keys,
|
||||
const MultiValueMap<const bNodeSocket *, int> &referenced_by_field_socket,
|
||||
const MultiValueMap<const bNodeSocket *, int> &propagated_to_geometry_socket,
|
||||
MultiValueMap<const bNodeSocket *, int> &r_required_propagated_to_geometry_socket)
|
||||
{
|
||||
const aal::RelationsInNode &tree_relations = *btree_.runtime->anonymous_attribute_relations;
|
||||
MultiValueMap<const bNodeSocket *, int> required_by_geometry_socket;
|
||||
|
||||
/* Initialize required attributes at group output. */
|
||||
if (const bNode *group_output_bnode = btree_.group_output_node()) {
|
||||
for (const aal::PropagateRelation &relation : tree_relations.propagate_relations) {
|
||||
AttributeReferenceKey key;
|
||||
key.type = AttributeReferenceKeyType::OutputGeometry;
|
||||
key.index = relation.to_geometry_output;
|
||||
const int key_index = attribute_reference_keys.index_of(key);
|
||||
required_by_geometry_socket.add(
|
||||
&group_output_bnode->input_socket(relation.to_geometry_output), key_index);
|
||||
}
|
||||
for (const aal::AvailableRelation &relation : tree_relations.available_relations) {
|
||||
const bNodeSocket &geometry_bsocket = group_output_bnode->input_socket(
|
||||
relation.geometry_output);
|
||||
const bNodeSocket &field_bsocket = group_output_bnode->input_socket(relation.field_output);
|
||||
required_by_geometry_socket.add_multiple(
|
||||
&geometry_bsocket, referenced_by_field_socket.lookup(&field_bsocket));
|
||||
}
|
||||
}
|
||||
|
||||
/* Propagate attribute usages from right to left. */
|
||||
for (const bNode *bnode : btree_.toposort_right_to_left()) {
|
||||
const aal::RelationsInNode &relations = *relations_by_node[bnode->index()];
|
||||
for (const bNodeSocket *bsocket : bnode->output_sockets()) {
|
||||
if (!bsocket->is_available()) {
|
||||
continue;
|
||||
}
|
||||
Vector<int> required_attributes;
|
||||
for (const bNodeLink *blink : bsocket->directly_linked_links()) {
|
||||
if (blink->is_used()) {
|
||||
const bNodeSocket &to_socket = *blink->tosock;
|
||||
required_attributes.extend_non_duplicates(
|
||||
required_by_geometry_socket.lookup(&to_socket));
|
||||
}
|
||||
}
|
||||
const Span<int> available_attributes = propagated_to_geometry_socket.lookup(bsocket);
|
||||
for (const int key_index : required_attributes) {
|
||||
if (available_attributes.contains(key_index)) {
|
||||
required_by_geometry_socket.add(bsocket, key_index);
|
||||
|
||||
const AttributeReferenceKey &key = attribute_reference_keys[key_index];
|
||||
if (key.type != AttributeReferenceKeyType::Socket ||
|
||||
&key.bsocket->owner_node() != bnode) {
|
||||
r_required_propagated_to_geometry_socket.add(bsocket, key_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const bNodeSocket *bsocket : bnode->input_sockets()) {
|
||||
if (!bsocket->is_available()) {
|
||||
continue;
|
||||
}
|
||||
Vector<int> required_attributes;
|
||||
for (const aal::PropagateRelation &relation : relations.propagate_relations) {
|
||||
if (relation.from_geometry_input == bsocket->index()) {
|
||||
const bNodeSocket &output_bsocket = bnode->output_socket(relation.to_geometry_output);
|
||||
required_attributes.extend_non_duplicates(
|
||||
required_by_geometry_socket.lookup(&output_bsocket));
|
||||
}
|
||||
}
|
||||
for (const aal::EvalRelation &relation : relations.eval_relations) {
|
||||
if (relation.geometry_input == bsocket->index()) {
|
||||
const bNodeSocket &field_bsocket = bnode->input_socket(relation.field_input);
|
||||
if (field_bsocket.is_available()) {
|
||||
required_attributes.extend_non_duplicates(
|
||||
referenced_by_field_socket.lookup(&field_bsocket));
|
||||
}
|
||||
}
|
||||
}
|
||||
const Span<int> available_attributes = propagated_to_geometry_socket.lookup(bsocket);
|
||||
for (const int key_index : required_attributes) {
|
||||
if (available_attributes.contains(key_index)) {
|
||||
required_by_geometry_socket.add(bsocket, key_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For every node that propagates attributes, prepare an attribute set containing information
|
||||
* about which attributes should be propagated.
|
||||
*/
|
||||
void build_attribute_sets_to_propagate(
|
||||
const Span<AttributeReferenceKey> attribute_reference_keys,
|
||||
const Span<AttributeReferenceInfo> attribute_reference_infos,
|
||||
const MultiValueMap<const bNodeSocket *, int> &required_propagated_to_geometry_socket)
|
||||
{
|
||||
JoinAttibuteSetsCache join_attribute_sets_cache;
|
||||
|
||||
for (const auto [geometry_output_bsocket, lf_attribute_set_input] :
|
||||
attribute_set_propagation_map_.items()) {
|
||||
const Span<int> required = required_propagated_to_geometry_socket.lookup(
|
||||
geometry_output_bsocket);
|
||||
|
||||
Vector<lf::OutputSocket *> attribute_set_sockets;
|
||||
Vector<lf::OutputSocket *> used_sockets;
|
||||
|
||||
for (const int i : required.index_range()) {
|
||||
const int key_index = required[i];
|
||||
const AttributeReferenceKey &key = attribute_reference_keys[key_index];
|
||||
const AttributeReferenceInfo &info = attribute_reference_infos[key_index];
|
||||
lf::OutputSocket *lf_socket_usage = nullptr;
|
||||
switch (key.type) {
|
||||
case AttributeReferenceKeyType::InputField: {
|
||||
lf_socket_usage = const_cast<lf::InputSocket *>(
|
||||
mapping_->group_input_usage_sockets[key.index])
|
||||
->origin();
|
||||
break;
|
||||
}
|
||||
case AttributeReferenceKeyType::OutputGeometry: {
|
||||
lf_socket_usage = const_cast<lf::OutputSocket *>(
|
||||
mapping_->group_output_used_sockets[key.index]);
|
||||
break;
|
||||
}
|
||||
case AttributeReferenceKeyType::Socket: {
|
||||
lf_socket_usage = socket_is_used_map_[key.bsocket->index_in_tree()];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lf_socket_usage) {
|
||||
attribute_set_sockets.append(info.lf_attribute_set_socket);
|
||||
used_sockets.append(lf_socket_usage);
|
||||
}
|
||||
}
|
||||
if (lf::OutputSocket *joined_attribute_set = this->join_attribute_sets(
|
||||
attribute_set_sockets, used_sockets, join_attribute_sets_cache)) {
|
||||
lf_graph_->add_link(*joined_attribute_set, *lf_attribute_set_input);
|
||||
}
|
||||
else {
|
||||
static const bke::AnonymousAttributeSet empty_set;
|
||||
lf_attribute_set_input->set_default_value(&empty_set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using JoinAttibuteSetsCache = Map<Vector<lf::OutputSocket *>, lf::OutputSocket *>;
|
||||
|
||||
/**
|
||||
* Join multiple attributes set into a single attribute set that can be passed into a node.
|
||||
*/
|
||||
lf::OutputSocket *join_attribute_sets(const Span<lf::OutputSocket *> attribute_set_sockets,
|
||||
const Span<lf::OutputSocket *> used_sockets,
|
||||
JoinAttibuteSetsCache &cache)
|
||||
{
|
||||
BLI_assert(attribute_set_sockets.size() == used_sockets.size());
|
||||
if (attribute_set_sockets.is_empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
if (attribute_set_sockets.size() == 1) {
|
||||
return attribute_set_sockets[0];
|
||||
}
|
||||
|
||||
Vector<lf::OutputSocket *, 16> key;
|
||||
key.extend(attribute_set_sockets);
|
||||
key.extend(used_sockets);
|
||||
std::sort(key.begin(), key.end());
|
||||
return cache.lookup_or_add_cb(key, [&]() {
|
||||
auto lazy_function = std::make_unique<LazyFunctionForAnonymousAttributeSetJoin>(
|
||||
attribute_set_sockets.size());
|
||||
lf::Node &lf_node = lf_graph_->add_function(*lazy_function);
|
||||
for (const int i : attribute_set_sockets.index_range()) {
|
||||
lf::InputSocket &lf_use_input = lf_node.input(lazy_function->get_use_input(i));
|
||||
socket_usage_inputs_.add(&lf_use_input);
|
||||
lf::InputSocket &lf_attributes_input = lf_node.input(
|
||||
lazy_function->get_attribute_set_input(i));
|
||||
lf_graph_->add_link(*used_sockets[i], lf_use_input);
|
||||
lf_graph_->add_link(*attribute_set_sockets[i], lf_attributes_input);
|
||||
}
|
||||
lf_graph_info_->functions.append(std::move(lazy_function));
|
||||
return &lf_node.output(0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* By depending on "the future" (whether a specific socket is used in the future), it is possible
|
||||
* to introduce cycles in the graph. This function finds those cycles and breaks them by removing
|
||||
* specific links.
|
||||
*
|
||||
* Example for a cycle: There is a `Distribute Points on Faces` node and its `Normal` output is
|
||||
* only used when the number of generated points is larger than 1000 because of some switch node
|
||||
* later in the tree. In this case, to know whether the `Normal` output is needed, one first has
|
||||
* to compute the points, but for that one has to know whether the normal information has to be
|
||||
* added to the points. The fix is to always add the normal information in this case.
|
||||
*/
|
||||
void fix_link_cycles()
|
||||
{
|
||||
lf_graph_->update_socket_indices();
|
||||
const int sockets_num = lf_graph_->socket_num();
|
||||
|
||||
struct SocketState {
|
||||
bool done = false;
|
||||
bool in_stack = false;
|
||||
};
|
||||
|
||||
Array<SocketState> socket_states(sockets_num);
|
||||
|
||||
Stack<lf::Socket *> lf_sockets_to_check;
|
||||
for (lf::Node *lf_node : lf_graph_->nodes()) {
|
||||
if (lf_node->is_function()) {
|
||||
for (lf::OutputSocket *lf_socket : lf_node->outputs()) {
|
||||
if (lf_socket->targets().is_empty()) {
|
||||
lf_sockets_to_check.push(lf_socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lf_node->outputs().is_empty()) {
|
||||
for (lf::InputSocket *lf_socket : lf_node->inputs()) {
|
||||
lf_sockets_to_check.push(lf_socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vector<lf::Socket *> lf_socket_stack;
|
||||
while (!lf_sockets_to_check.is_empty()) {
|
||||
lf::Socket *lf_inout_socket = lf_sockets_to_check.peek();
|
||||
lf::Node &lf_node = lf_inout_socket->node();
|
||||
SocketState &state = socket_states[lf_inout_socket->index_in_graph()];
|
||||
lf_socket_stack.append(lf_inout_socket);
|
||||
state.in_stack = true;
|
||||
|
||||
Vector<lf::Socket *, 16> lf_origin_sockets;
|
||||
if (lf_inout_socket->is_input()) {
|
||||
lf::InputSocket &lf_input_socket = lf_inout_socket->as_input();
|
||||
if (lf::OutputSocket *lf_origin_socket = lf_input_socket.origin()) {
|
||||
lf_origin_sockets.append(lf_origin_socket);
|
||||
}
|
||||
}
|
||||
else {
|
||||
lf::OutputSocket &lf_output_socket = lf_inout_socket->as_output();
|
||||
if (lf_node.is_function()) {
|
||||
lf::FunctionNode &lf_function_node = static_cast<lf::FunctionNode &>(lf_node);
|
||||
const lf::LazyFunction &fn = lf_function_node.function();
|
||||
fn.possible_output_dependencies(
|
||||
lf_output_socket.index(), [&](const Span<int> input_indices) {
|
||||
for (const int input_index : input_indices) {
|
||||
lf_origin_sockets.append(&lf_node.input(input_index));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bool pushed_socket = false;
|
||||
for (lf::Socket *lf_origin_socket : lf_origin_sockets) {
|
||||
if (socket_states[lf_origin_socket->index_in_graph()].in_stack) {
|
||||
const Span<lf::Socket *> cycle = lf_socket_stack.as_span().drop_front(
|
||||
lf_socket_stack.first_index_of(lf_origin_socket));
|
||||
|
||||
bool broke_cycle = false;
|
||||
for (lf::Socket *lf_cycle_socket : cycle) {
|
||||
if (lf_cycle_socket->is_input() &&
|
||||
socket_usage_inputs_.contains(&lf_cycle_socket->as_input())) {
|
||||
lf::InputSocket &lf_cycle_input_socket = lf_cycle_socket->as_input();
|
||||
lf_graph_->clear_origin(lf_cycle_input_socket);
|
||||
static const bool static_true = true;
|
||||
lf_cycle_input_socket.set_default_value(&static_true);
|
||||
broke_cycle = true;
|
||||
}
|
||||
}
|
||||
if (!broke_cycle) {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
else if (!socket_states[lf_origin_socket->index_in_graph()].done) {
|
||||
lf_sockets_to_check.push(lf_origin_socket);
|
||||
pushed_socket = true;
|
||||
}
|
||||
}
|
||||
if (pushed_socket) {
|
||||
continue;
|
||||
}
|
||||
|
||||
state.done = true;
|
||||
state.in_stack = false;
|
||||
lf_sockets_to_check.pop();
|
||||
lf_socket_stack.pop_last();
|
||||
}
|
||||
}
|
||||
|
||||
void print_graph();
|
||||
};
|
||||
|
||||
class UsedSocketVisualizeOptions : public lf::Graph::ToDotOptions {
|
||||
private:
|
||||
const GeometryNodesLazyFunctionGraphBuilder &builder_;
|
||||
Map<const lf::Socket *, std::string> socket_font_colors_;
|
||||
Map<const lf::Socket *, std::string> socket_name_suffixes_;
|
||||
|
||||
public:
|
||||
UsedSocketVisualizeOptions(const GeometryNodesLazyFunctionGraphBuilder &builder)
|
||||
: builder_(builder)
|
||||
{
|
||||
VectorSet<lf::OutputSocket *> found;
|
||||
for (const int bsocket_index : builder_.socket_is_used_map_.index_range()) {
|
||||
const bNodeSocket *bsocket = builder_.btree_.all_sockets()[bsocket_index];
|
||||
lf::OutputSocket *lf_used_socket = builder_.socket_is_used_map_[bsocket_index];
|
||||
if (lf_used_socket == nullptr) {
|
||||
continue;
|
||||
}
|
||||
const float hue = BLI_hash_int_01(uintptr_t(lf_used_socket));
|
||||
std::stringstream ss;
|
||||
ss.precision(3);
|
||||
ss << hue << " 0.9 0.5";
|
||||
const std::string color_str = ss.str();
|
||||
const std::string suffix = " (" + std::to_string(found.index_of_or_add(lf_used_socket)) +
|
||||
")";
|
||||
socket_font_colors_.add(lf_used_socket, color_str);
|
||||
socket_name_suffixes_.add(lf_used_socket, suffix);
|
||||
|
||||
if (bsocket->is_input()) {
|
||||
for (const lf::InputSocket *lf_socket : builder_.input_socket_map_.lookup(bsocket)) {
|
||||
socket_font_colors_.add(lf_socket, color_str);
|
||||
socket_name_suffixes_.add(lf_socket, suffix);
|
||||
}
|
||||
}
|
||||
else if (lf::OutputSocket *lf_socket = builder_.output_socket_map_.lookup(bsocket)) {
|
||||
socket_font_colors_.add(lf_socket, color_str);
|
||||
socket_name_suffixes_.add(lf_socket, suffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> socket_font_color(const lf::Socket &socket) const override
|
||||
{
|
||||
if (const std::string *color = socket_font_colors_.lookup_ptr(&socket)) {
|
||||
return *color;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string socket_name(const lf::Socket &socket) const override
|
||||
{
|
||||
return socket.name() + socket_name_suffixes_.lookup_default(&socket, "");
|
||||
}
|
||||
|
||||
void add_edge_attributes(const lf::OutputSocket & /*from*/,
|
||||
const lf::InputSocket &to,
|
||||
dot::DirectedEdge &dot_edge) const
|
||||
{
|
||||
if (builder_.socket_usage_inputs_.contains_as(&to)) {
|
||||
// dot_edge.attributes.set("constraint", "false");
|
||||
dot_edge.attributes.set("color", "#00000055");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void GeometryNodesLazyFunctionGraphBuilder::print_graph()
|
||||
{
|
||||
UsedSocketVisualizeOptions options{*this};
|
||||
std::cout << "\n\n" << lf_graph_->to_dot(options) << "\n\n";
|
||||
}
|
||||
|
||||
const GeometryNodesLazyFunctionGraphInfo *ensure_geometry_nodes_lazy_function_graph(
|
||||
const bNodeTree &btree)
|
||||
{
|
||||
|
||||
@@ -70,7 +70,7 @@ GeometryInfoLog::GeometryInfoLog(const GeometrySet &geometry_set)
|
||||
[&](const bke::AttributeIDRef &attribute_id,
|
||||
const bke::AttributeMetaData &meta_data,
|
||||
const GeometryComponent & /*component*/) {
|
||||
if (attribute_id.is_named() && names.add(attribute_id.name())) {
|
||||
if (!attribute_id.is_anonymous() && names.add(attribute_id.name())) {
|
||||
this->attributes.append({attribute_id.name(), meta_data.domain, meta_data.data_type});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,10 +9,33 @@
|
||||
|
||||
#include "NOD_geometry_exec.hh"
|
||||
|
||||
#include "BLI_hash_md5.h"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
NodeAnonymousAttributeID::NodeAnonymousAttributeID(const Object &object,
|
||||
const ComputeContext &compute_context,
|
||||
const bNode &bnode,
|
||||
const StringRef identifier)
|
||||
{
|
||||
const ComputeContextHash &hash = compute_context.hash();
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << hash << "_" << object.id.name << "_" << bnode.identifier << "_" << identifier;
|
||||
long_name_ = ss.str();
|
||||
}
|
||||
{
|
||||
uint64_t hash_result[2];
|
||||
BLI_hash_md5_buffer(long_name_.data(), long_name_.size(), hash_result);
|
||||
std::stringstream ss;
|
||||
ss << ".a_" << std::hex << hash_result[0] << hash_result[1];
|
||||
name_ = ss.str();
|
||||
BLI_assert(name_.size() < MAX_CUSTOMDATA_LAYER_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
void GeoNodeExecParams::error_message_add(const NodeWarningType type,
|
||||
const StringRef message) const
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user