Compare commits
221 Commits
draw-viewp
...
temp-geome
Author | SHA1 | Date | |
---|---|---|---|
29704854df | |||
015bde6145 | |||
8d19ceaee7 | |||
d62cf98164 | |||
655223e4c6 | |||
54cc128a31 | |||
fa5a9b0f6a | |||
cffd4a7ccf | |||
2012469acc | |||
713da0fcc4 | |||
8ba889a4be | |||
5cd7e557ae | |||
584af77101 | |||
d53e400454 | |||
64d1fe3450 | |||
4b091f2eb6 | |||
e70f9b0d0e | |||
d2d10e63c9 | |||
b51f68d780 | |||
c3adfd6d5d | |||
edac56b414 | |||
8c76ca6a53 | |||
0bd2b314e8 | |||
2963be588d | |||
507f2628fd | |||
c227b4da17 | |||
605f145aba | |||
deb7cd5783 | |||
cb0a78cb8f | |||
70e3b6d11f | |||
e041389c68 | |||
38574e37cc | |||
8186dddec1 | |||
47fa00ffef | |||
62f8bb87b7 | |||
5617cb5500 | |||
a4d4188c8d | |||
f487dbae97 | |||
94b98d3f88 | |||
32f101c603 | |||
2feed5ce33 | |||
f7b9114eec | |||
7f017a51bb | |||
efbe1ea8c7 | |||
2b38770ebd | |||
5a96096c5e | |||
ce1519d804 | |||
bd1375f9cb | |||
af641d0912 | |||
6c0ae5e890 | |||
fac0723e92 | |||
68a8a89c49 | |||
8c9432be6a | |||
6e08de6028 | |||
00ed599fe4 | |||
769c7521b5 | |||
e23ccfe913 | |||
7398076be2 | |||
ea98f53dcd | |||
70cbbd614f | |||
95288eba59 | |||
01037308d5 | |||
1765e1b20a | |||
7cb4665bee | |||
d6bddbef2d | |||
fcfec33cf7 | |||
e338ebb720 | |||
741deadff4 | |||
c1cab4aa68 | |||
bf6b04bf89 | |||
a275572e0b | |||
9ec7e23c44 | |||
3c5681c212 | |||
28f1e71e2e | |||
149fd7b65f | |||
a55c230b8a | |||
1f1dc4ade3 | |||
d445ee4c73 | |||
73e52ab55f | |||
3fec225931 | |||
f0443b8859 | |||
b73f692919 | |||
4aeb9dc996 | |||
3b12594054 | |||
0597e93e5e | |||
5f003515a1 | |||
26f8647fea | |||
12f296d800 | |||
75ec632b61 | |||
d6519d4305 | |||
54cf7eaf92 | |||
d0b1e739b1 | |||
b04e2e8877 | |||
fb0d5124f2 | |||
6146a679c9 | |||
84660c44f0 | |||
c398cad059 | |||
35bf6b9790 | |||
e5a59e876e | |||
d464816c37 | |||
007651129a | |||
1968c9b702 | |||
eb54741226 | |||
ce86a518b9 | |||
09a5ea059f | |||
b3cc26bf35 | |||
5f29552be7 | |||
6afa55b7e4 | |||
37d717fe14 | |||
f193cf66d4 | |||
68efa935da | |||
1a0fed5d8e | |||
c3206bd2a0 | |||
26e7fef920 | |||
92e1c8744b | |||
cfe8832dd3 | |||
6a5b048658 | |||
ad0dbde653 | |||
f25c1b4950 | |||
469f752b80 | |||
55f2867db3 | |||
80429002d7 | |||
0910b76be3 | |||
1ccfd6842b | |||
0a0360c8cd | |||
be0201259a | |||
ef2a48329d | |||
81a0c384da | |||
70eaba3cb1 | |||
7e39e78259 | |||
c2122c39ae | |||
fb26ee8a7e | |||
001072721f | |||
65a1ec89ba | |||
ef9fbf258b | |||
a448949f25 | |||
e3232f987a | |||
7ebc3140bb | |||
c42ceef040 | |||
acc2e8afa9 | |||
04bb1bda32 | |||
eed93aaa07 | |||
fc0bb6cdee | |||
10f2ad1556 | |||
6d77b87b13 | |||
2101b46802 | |||
34f6765630 | |||
c58d1acba8 | |||
0ee79f304e | |||
e642de3d6f | |||
eca5a8b695 | |||
79c79f3c70 | |||
a9970d3cb9 | |||
b812f289f5 | |||
215ce0fb57 | |||
5154598845 | |||
1ee80d792c | |||
95284d2f1e | |||
7281f3eb56 | |||
607ef8f6c5 | |||
1ce640cc0b | |||
7bed18fdb1 | |||
c827b50d40 | |||
3c7e3c8e44 | |||
98e38ce4f3 | |||
132cf268c0 | |||
fd7edc9b05 | |||
ecf7c90840 | |||
d78a530af1 | |||
3ebe61db9f | |||
86c2f139c6 | |||
3596c348eb | |||
41a81474e4 | |||
aa2822d137 | |||
55b333d3e3 | |||
00cfad8578 | |||
1891c956e5 | |||
249c050757 | |||
a0081046b6 | |||
635f73b7f1 | |||
74fcd50e2f | |||
b04a2a7be7 | |||
083671e8ac | |||
78ea401e19 | |||
f3ca987bce | |||
2245add9f8 | |||
31004d7fac | |||
3d3f66ed41 | |||
c9c0195da5 | |||
70c0403858 | |||
8d4de82c7f | |||
22c51c2d51 | |||
158bd7c6a0 | |||
4a28d0b583 | |||
0501e6e693 | |||
8450ac09c1 | |||
1af00015e8 | |||
b44c3a3125 | |||
d729f1ca37 | |||
0fc9f00c14 | |||
6c9b339af7 | |||
b7a976af01 | |||
a689037917 | |||
313403c1f1 | |||
60409b8823 | |||
773dc2ec94 | |||
2a98c5d06b | |||
6d1b4ce3c6 | |||
0b2d961b70 | |||
d553b70470 | |||
6954f2cdd7 | |||
8cc832110a | |||
7b8c54b5a1 | |||
e850d175b5 | |||
326f79d59b | |||
ec4954ece2 | |||
b30e782c82 | |||
e34fe5d28e | |||
8581a062f1 | |||
b43971e5e9 | |||
855382170e |
@@ -2254,6 +2254,7 @@ class USERPREF_PT_experimental_new_features(ExperimentalPanel, Panel):
|
||||
({"property": "use_sculpt_tools_tilt"}, "T82877"),
|
||||
({"property": "use_extended_asset_browser"}, ("project/view/130/", "Project Page")),
|
||||
({"property": "use_override_templates"}, ("T73318", "Milestone 4")),
|
||||
({"property": "use_geometry_nodes_fields"}, "T91274"),
|
||||
),
|
||||
)
|
||||
|
||||
|
@@ -180,6 +180,10 @@ def object_eevee_cycles_shader_nodes_poll(context):
|
||||
eevee_cycles_shader_nodes_poll(context))
|
||||
|
||||
|
||||
def geometry_nodes_fields_poll(context):
|
||||
return context.preferences.experimental.use_geometry_nodes_fields
|
||||
|
||||
|
||||
# All standard node categories currently used in nodes.
|
||||
|
||||
shader_node_categories = [
|
||||
@@ -478,6 +482,7 @@ geometry_node_categories = [
|
||||
GeometryNodeCategory("GEO_ATTRIBUTE", "Attribute", items=[
|
||||
NodeItem("GeometryNodeAttributeRandomize"),
|
||||
NodeItem("GeometryNodeAttributeMath"),
|
||||
NodeItem("GeometryNodeAttributeCapture", poll=geometry_nodes_fields_poll),
|
||||
NodeItem("GeometryNodeAttributeClamp"),
|
||||
NodeItem("GeometryNodeAttributeCompare"),
|
||||
NodeItem("GeometryNodeAttributeConvert"),
|
||||
@@ -534,6 +539,7 @@ geometry_node_categories = [
|
||||
NodeItem("GeometryNodeJoinGeometry"),
|
||||
NodeItem("GeometryNodeSeparateComponents"),
|
||||
NodeItem("GeometryNodeRaycast"),
|
||||
NodeItem("GeometryNodeSetPosition", poll=geometry_nodes_fields_poll),
|
||||
]),
|
||||
GeometryNodeCategory("GEO_INPUT", "Input", items=[
|
||||
NodeItem("GeometryNodeObjectInfo"),
|
||||
@@ -544,6 +550,9 @@ geometry_node_categories = [
|
||||
NodeItem("FunctionNodeInputVector"),
|
||||
NodeItem("GeometryNodeInputMaterial"),
|
||||
NodeItem("GeometryNodeIsViewport"),
|
||||
NodeItem("GeometryNodeInputPosition", poll=geometry_nodes_fields_poll),
|
||||
NodeItem("GeometryNodeInputIndex", poll=geometry_nodes_fields_poll),
|
||||
NodeItem("GeometryNodeInputNormal", poll=geometry_nodes_fields_poll),
|
||||
]),
|
||||
GeometryNodeCategory("GEO_MATERIAL", "Material", items=[
|
||||
NodeItem("GeometryNodeMaterialAssign"),
|
||||
|
43
source/blender/blenkernel/BKE_anonymous_attribute.h
Normal file
43
source/blender/blenkernel/BKE_anonymous_attribute.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#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
|
169
source/blender/blenkernel/BKE_anonymous_attribute.hh
Normal file
169
source/blender/blenkernel/BKE_anonymous_attribute.hh
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#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 onwership 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
|
@@ -22,6 +22,7 @@
|
||||
#include "FN_generic_span.hh"
|
||||
#include "FN_generic_virtual_array.hh"
|
||||
|
||||
#include "BKE_anonymous_attribute.hh"
|
||||
#include "BKE_attribute.h"
|
||||
|
||||
#include "BLI_color.hh"
|
||||
@@ -29,6 +30,81 @@
|
||||
#include "BLI_float3.hh"
|
||||
#include "BLI_function_ref.hh"
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
/**
|
||||
* Identifies an attribute that is either named or anonymous.
|
||||
* It does not own the identifier, so it is just a reference.
|
||||
*/
|
||||
class AttributeIDRef {
|
||||
private:
|
||||
StringRef name_;
|
||||
const AnonymousAttributeID *anonymous_id_ = nullptr;
|
||||
|
||||
public:
|
||||
AttributeIDRef() = default;
|
||||
|
||||
AttributeIDRef(StringRef name) : name_(name)
|
||||
{
|
||||
}
|
||||
|
||||
AttributeIDRef(StringRefNull name) : name_(name)
|
||||
{
|
||||
}
|
||||
|
||||
AttributeIDRef(const char *name) : name_(name)
|
||||
{
|
||||
}
|
||||
|
||||
AttributeIDRef(const std::string &name) : name_(name)
|
||||
{
|
||||
}
|
||||
|
||||
/* The anonymous id is only borrowed, the caller has to keep a reference to it. */
|
||||
AttributeIDRef(const AnonymousAttributeID *anonymous_id) : anonymous_id_(anonymous_id)
|
||||
{
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return this->is_named() || this->is_anonymous();
|
||||
}
|
||||
|
||||
friend bool operator==(const AttributeIDRef &a, const AttributeIDRef &b)
|
||||
{
|
||||
return a.anonymous_id_ == b.anonymous_id_ && a.name_ == b.name_;
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash_2(name_, anonymous_id_);
|
||||
}
|
||||
|
||||
bool is_named() const
|
||||
{
|
||||
return !name_.is_empty();
|
||||
}
|
||||
|
||||
bool is_anonymous() const
|
||||
{
|
||||
return anonymous_id_ != nullptr;
|
||||
}
|
||||
|
||||
StringRef name() const
|
||||
{
|
||||
BLI_assert(this->is_named());
|
||||
return name_;
|
||||
}
|
||||
|
||||
const AnonymousAttributeID &anonymous_id() const
|
||||
{
|
||||
BLI_assert(this->is_anonymous());
|
||||
return *anonymous_id_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
/**
|
||||
* Contains information about an attribute in a geometry component.
|
||||
* More information can be added in the future. E.g. whether the attribute is builtin and how it is
|
||||
@@ -104,8 +180,8 @@ struct AttributeInitMove : public AttributeInit {
|
||||
};
|
||||
|
||||
/* Returns false when the iteration should be stopped. */
|
||||
using AttributeForeachCallback = blender::FunctionRef<bool(blender::StringRefNull attribute_name,
|
||||
const AttributeMetaData &meta_data)>;
|
||||
using AttributeForeachCallback = blender::FunctionRef<bool(
|
||||
const blender::bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data)>;
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
@@ -333,26 +409,28 @@ class CustomDataAttributes {
|
||||
|
||||
void reallocate(const int size);
|
||||
|
||||
std::optional<blender::fn::GSpan> get_for_read(const blender::StringRef name) const;
|
||||
std::optional<blender::fn::GSpan> get_for_read(const AttributeIDRef &attribute_id) const;
|
||||
|
||||
blender::fn::GVArrayPtr get_for_read(const StringRef name,
|
||||
blender::fn::GVArrayPtr get_for_read(const AttributeIDRef &attribute_id,
|
||||
const CustomDataType data_type,
|
||||
const void *default_value) const;
|
||||
|
||||
template<typename T>
|
||||
blender::fn::GVArray_Typed<T> get_for_read(const blender::StringRef name,
|
||||
blender::fn::GVArray_Typed<T> get_for_read(const AttributeIDRef &attribute_id,
|
||||
const T &default_value) const
|
||||
{
|
||||
const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>();
|
||||
const CustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type);
|
||||
GVArrayPtr varray = this->get_for_read(name, type, &default_value);
|
||||
GVArrayPtr varray = this->get_for_read(attribute_id, type, &default_value);
|
||||
return blender::fn::GVArray_Typed<T>(std::move(varray));
|
||||
}
|
||||
|
||||
std::optional<blender::fn::GMutableSpan> get_for_write(const blender::StringRef name);
|
||||
bool create(const blender::StringRef name, const CustomDataType data_type);
|
||||
bool create_by_move(const blender::StringRef name, const CustomDataType data_type, void *buffer);
|
||||
bool remove(const blender::StringRef name);
|
||||
std::optional<blender::fn::GMutableSpan> get_for_write(const AttributeIDRef &attribute_id);
|
||||
bool create(const AttributeIDRef &attribute_id, const CustomDataType data_type);
|
||||
bool create_by_move(const AttributeIDRef &attribute_id,
|
||||
const CustomDataType data_type,
|
||||
void *buffer);
|
||||
bool remove(const AttributeIDRef &attribute_id);
|
||||
|
||||
bool foreach_attribute(const AttributeForeachCallback callback,
|
||||
const AttributeDomain domain) const;
|
||||
|
@@ -33,6 +33,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct AnonymousAttributeID;
|
||||
struct BMesh;
|
||||
struct BlendDataReader;
|
||||
struct BlendWriter;
|
||||
@@ -193,6 +194,12 @@ void *CustomData_add_layer_named(struct CustomData *data,
|
||||
void *layer,
|
||||
int totelem,
|
||||
const char *name);
|
||||
void *CustomData_add_layer_anonymous(struct CustomData *data,
|
||||
int type,
|
||||
eCDAllocType alloctype,
|
||||
void *layer,
|
||||
int totelem,
|
||||
const struct AnonymousAttributeID *anonymous_id);
|
||||
|
||||
/* frees the active or first data layer with the give type.
|
||||
* returns 1 on success, 0 if no layer with the given type is found
|
||||
@@ -231,6 +238,11 @@ void *CustomData_duplicate_referenced_layer_named(struct CustomData *data,
|
||||
const int type,
|
||||
const char *name,
|
||||
const int totelem);
|
||||
void *CustomData_duplicate_referenced_layer_anonymous(
|
||||
CustomData *data,
|
||||
const int type,
|
||||
const struct AnonymousAttributeID *anonymous_id,
|
||||
const int totelem);
|
||||
bool CustomData_is_referenced_layer(struct CustomData *data, int type);
|
||||
|
||||
/* Duplicate all the layers with flag NOFREE, and remove the flag from duplicated layers. */
|
||||
|
@@ -31,9 +31,12 @@
|
||||
#include "BLI_user_counter.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "BKE_anonymous_attribute.hh"
|
||||
#include "BKE_attribute_access.hh"
|
||||
#include "BKE_geometry_set.h"
|
||||
|
||||
#include "FN_field.hh"
|
||||
|
||||
struct Collection;
|
||||
struct Curve;
|
||||
struct CurveEval;
|
||||
@@ -88,11 +91,11 @@ class GeometryComponent {
|
||||
GeometryComponentType type() const;
|
||||
|
||||
/* Return true when any attribute with this name exists, including built in attributes. */
|
||||
bool attribute_exists(const blender::StringRef attribute_name) const;
|
||||
bool attribute_exists(const blender::bke::AttributeIDRef &attribute_id) const;
|
||||
|
||||
/* Return the data type and domain of an attribute with the given name if it exists. */
|
||||
std::optional<AttributeMetaData> attribute_get_meta_data(
|
||||
const blender::StringRef attribute_name) const;
|
||||
const blender::bke::AttributeIDRef &attribute_id) const;
|
||||
|
||||
/* Returns true when the geometry component supports this attribute domain. */
|
||||
bool attribute_domain_supported(const AttributeDomain domain) const;
|
||||
@@ -104,12 +107,12 @@ class GeometryComponent {
|
||||
/* Get read-only access to the highest priority attribute with the given name.
|
||||
* Returns null if the attribute does not exist. */
|
||||
blender::bke::ReadAttributeLookup attribute_try_get_for_read(
|
||||
const blender::StringRef attribute_name) const;
|
||||
const blender::bke::AttributeIDRef &attribute_id) const;
|
||||
|
||||
/* Get read and write access to the highest priority attribute with the given name.
|
||||
* Returns null if the attribute does not exist. */
|
||||
blender::bke::WriteAttributeLookup attribute_try_get_for_write(
|
||||
const blender::StringRef attribute_name);
|
||||
const blender::bke::AttributeIDRef &attribute_id);
|
||||
|
||||
/* Get a read-only attribute for the domain based on the given attribute. This can be used to
|
||||
* interpolate from one domain to another.
|
||||
@@ -120,10 +123,10 @@ class GeometryComponent {
|
||||
const AttributeDomain to_domain) const;
|
||||
|
||||
/* Returns true when the attribute has been deleted. */
|
||||
bool attribute_try_delete(const blender::StringRef attribute_name);
|
||||
bool attribute_try_delete(const blender::bke::AttributeIDRef &attribute_id);
|
||||
|
||||
/* Returns true when the attribute has been created. */
|
||||
bool attribute_try_create(const blender::StringRef attribute_name,
|
||||
bool attribute_try_create(const blender::bke::AttributeIDRef &attribute_id,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const AttributeInit &initializer);
|
||||
@@ -133,7 +136,7 @@ class GeometryComponent {
|
||||
bool attribute_try_create_builtin(const blender::StringRef attribute_name,
|
||||
const AttributeInit &initializer);
|
||||
|
||||
blender::Set<std::string> attribute_names() const;
|
||||
blender::Set<blender::bke::AttributeIDRef> attribute_ids() const;
|
||||
bool attribute_foreach(const AttributeForeachCallback callback) const;
|
||||
|
||||
virtual bool is_empty() const;
|
||||
@@ -142,7 +145,7 @@ class GeometryComponent {
|
||||
* Returns null when the attribute does not exist or cannot be converted to the requested domain
|
||||
* and data type. */
|
||||
std::unique_ptr<blender::fn::GVArray> attribute_try_get_for_read(
|
||||
const blender::StringRef attribute_name,
|
||||
const blender::bke::AttributeIDRef &attribute_id,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type) const;
|
||||
|
||||
@@ -150,18 +153,18 @@ class GeometryComponent {
|
||||
* left unchanged. Returns null when the attribute does not exist or cannot be adapted to the
|
||||
* requested domain. */
|
||||
std::unique_ptr<blender::fn::GVArray> attribute_try_get_for_read(
|
||||
const blender::StringRef attribute_name, const AttributeDomain domain) const;
|
||||
const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain) const;
|
||||
|
||||
/* Get a virtual array to read data of an attribute with the given data type. The domain is
|
||||
* left unchanged. Returns null when the attribute does not exist or cannot be converted to the
|
||||
* requested data type. */
|
||||
blender::bke::ReadAttributeLookup attribute_try_get_for_read(
|
||||
const blender::StringRef attribute_name, const CustomDataType data_type) const;
|
||||
const blender::bke::AttributeIDRef &attribute_id, const CustomDataType data_type) const;
|
||||
|
||||
/* Get a virtual array to read the data of an attribute. If that is not possible, the returned
|
||||
* virtual array will contain a default value. This never returns null. */
|
||||
std::unique_ptr<blender::fn::GVArray> attribute_get_for_read(
|
||||
const blender::StringRef attribute_name,
|
||||
const blender::bke::AttributeIDRef &attribute_id,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const void *default_value = nullptr) const;
|
||||
@@ -169,14 +172,15 @@ class GeometryComponent {
|
||||
/* Should be used instead of the method above when the requested data type is known at compile
|
||||
* time for better type safety. */
|
||||
template<typename T>
|
||||
blender::fn::GVArray_Typed<T> attribute_get_for_read(const blender::StringRef attribute_name,
|
||||
const AttributeDomain domain,
|
||||
const T &default_value) const
|
||||
blender::fn::GVArray_Typed<T> attribute_get_for_read(
|
||||
const blender::bke::AttributeIDRef &attribute_id,
|
||||
const AttributeDomain domain,
|
||||
const T &default_value) const
|
||||
{
|
||||
const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>();
|
||||
const CustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type);
|
||||
std::unique_ptr varray = this->attribute_get_for_read(
|
||||
attribute_name, domain, type, &default_value);
|
||||
attribute_id, domain, type, &default_value);
|
||||
return blender::fn::GVArray_Typed<T>(std::move(varray));
|
||||
}
|
||||
|
||||
@@ -191,7 +195,7 @@ class GeometryComponent {
|
||||
* is created that will overwrite the existing attribute in the end.
|
||||
*/
|
||||
blender::bke::OutputAttribute attribute_try_get_for_output(
|
||||
const blender::StringRef attribute_name,
|
||||
const blender::bke::AttributeIDRef &attribute_id,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const void *default_value = nullptr);
|
||||
@@ -200,28 +204,30 @@ class GeometryComponent {
|
||||
* attributes are not read, i.e. the attribute is used only for output. Since values are not read
|
||||
* from this attribute, no default value is necessary. */
|
||||
blender::bke::OutputAttribute attribute_try_get_for_output_only(
|
||||
const blender::StringRef attribute_name,
|
||||
const blender::bke::AttributeIDRef &attribute_id,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type);
|
||||
|
||||
/* Statically typed method corresponding to the equally named generic one. */
|
||||
template<typename T>
|
||||
blender::bke::OutputAttribute_Typed<T> attribute_try_get_for_output(
|
||||
const blender::StringRef attribute_name, const AttributeDomain domain, const T default_value)
|
||||
const blender::bke::AttributeIDRef &attribute_id,
|
||||
const AttributeDomain domain,
|
||||
const T default_value)
|
||||
{
|
||||
const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>();
|
||||
const CustomDataType data_type = blender::bke::cpp_type_to_custom_data_type(cpp_type);
|
||||
return this->attribute_try_get_for_output(attribute_name, domain, data_type, &default_value);
|
||||
return this->attribute_try_get_for_output(attribute_id, domain, data_type, &default_value);
|
||||
}
|
||||
|
||||
/* Statically typed method corresponding to the equally named generic one. */
|
||||
template<typename T>
|
||||
blender::bke::OutputAttribute_Typed<T> attribute_try_get_for_output_only(
|
||||
const blender::StringRef attribute_name, const AttributeDomain domain)
|
||||
const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain)
|
||||
{
|
||||
const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>();
|
||||
const CustomDataType data_type = blender::bke::cpp_type_to_custom_data_type(cpp_type);
|
||||
return this->attribute_try_get_for_output_only(attribute_name, domain, data_type);
|
||||
return this->attribute_try_get_for_output_only(attribute_id, domain, data_type);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -616,3 +622,69 @@ class VolumeComponent : public GeometryComponent {
|
||||
|
||||
static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_VOLUME;
|
||||
};
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
class GeometryComponentFieldContext : public fn::FieldContext {
|
||||
private:
|
||||
const GeometryComponent &component_;
|
||||
const AttributeDomain domain_;
|
||||
|
||||
public:
|
||||
GeometryComponentFieldContext(const GeometryComponent &component, const AttributeDomain domain)
|
||||
: component_(component), domain_(domain)
|
||||
{
|
||||
}
|
||||
|
||||
const GeometryComponent &geometry_component() const
|
||||
{
|
||||
return component_;
|
||||
}
|
||||
|
||||
AttributeDomain domain() const
|
||||
{
|
||||
return domain_;
|
||||
}
|
||||
};
|
||||
|
||||
class AttributeFieldInput : public fn::FieldInput {
|
||||
private:
|
||||
std::string name_;
|
||||
|
||||
public:
|
||||
AttributeFieldInput(std::string name, const CPPType &type)
|
||||
: fn::FieldInput(type, name), name_(std::move(name))
|
||||
{
|
||||
}
|
||||
|
||||
const GVArray *get_varray_for_context(const fn::FieldContext &context,
|
||||
IndexMask mask,
|
||||
ResourceScope &scope) const override;
|
||||
|
||||
uint64_t hash() const override;
|
||||
bool is_equal_to(const fn::FieldNode &other) const override;
|
||||
};
|
||||
|
||||
class AnonymousAttributeFieldInput : public fn::FieldInput {
|
||||
private:
|
||||
/**
|
||||
* A strong reference is required to make sure that the referenced attribute is not removed
|
||||
* automatically.
|
||||
*/
|
||||
StrongAnonymousAttributeID anonymous_id_;
|
||||
|
||||
public:
|
||||
AnonymousAttributeFieldInput(StrongAnonymousAttributeID anonymous_id, const CPPType &type)
|
||||
: fn::FieldInput(type, anonymous_id.debug_name()), anonymous_id_(std::move(anonymous_id))
|
||||
{
|
||||
}
|
||||
|
||||
const GVArray *get_varray_for_context(const fn::FieldContext &context,
|
||||
IndexMask mask,
|
||||
ResourceScope &scope) const override;
|
||||
|
||||
uint64_t hash() const override;
|
||||
bool is_equal_to(const fn::FieldNode &other) const override;
|
||||
};
|
||||
|
||||
} // namespace blender::bke
|
||||
|
@@ -59,9 +59,10 @@ struct AttributeKind {
|
||||
* will contain the highest complexity data type and the highest priority domain among every
|
||||
* attribute with the given name on all of the input components.
|
||||
*/
|
||||
void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> set_groups,
|
||||
Span<GeometryComponentType> component_types,
|
||||
const Set<std::string> &ignored_attributes,
|
||||
Map<std::string, AttributeKind> &r_attributes);
|
||||
void geometry_set_gather_instances_attribute_info(
|
||||
Span<GeometryInstanceGroup> set_groups,
|
||||
Span<GeometryComponentType> component_types,
|
||||
const Set<std::string> &ignored_attributes,
|
||||
Map<AttributeIDRef, AttributeKind> &r_attributes);
|
||||
|
||||
} // namespace blender::bke
|
||||
|
@@ -1484,6 +1484,11 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
|
||||
#define GEO_NODE_CURVE_SPLINE_TYPE 1073
|
||||
#define GEO_NODE_CURVE_SELECT_HANDLES 1074
|
||||
#define GEO_NODE_CURVE_FILL 1075
|
||||
#define GEO_NODE_INPUT_POSITION 1076
|
||||
#define GEO_NODE_SET_POSITION 1077
|
||||
#define GEO_NODE_INPUT_INDEX 1078
|
||||
#define GEO_NODE_INPUT_NORMAL 1079
|
||||
#define GEO_NODE_ATTRIBUTE_CAPTURE 1080
|
||||
|
||||
/** \} */
|
||||
|
||||
|
@@ -76,6 +76,7 @@ set(SRC
|
||||
intern/anim_path.c
|
||||
intern/anim_sys.c
|
||||
intern/anim_visualization.c
|
||||
intern/anonymous_attribute.cc
|
||||
intern/appdir.c
|
||||
intern/armature.c
|
||||
intern/armature_deform.c
|
||||
@@ -295,6 +296,8 @@ set(SRC
|
||||
BKE_anim_path.h
|
||||
BKE_anim_visualization.h
|
||||
BKE_animsys.h
|
||||
BKE_anonymous_attribute.h
|
||||
BKE_anonymous_attribute.hh
|
||||
BKE_appdir.h
|
||||
BKE_armature.h
|
||||
BKE_armature.hh
|
||||
|
118
source/blender/blenkernel/intern/anonymous_attribute.cc
Normal file
118
source/blender/blenkernel/intern/anonymous_attribute.cc
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#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 "anonymous_attribute_" + 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) {
|
||||
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();
|
||||
}
|
@@ -44,6 +44,8 @@ using blender::float3;
|
||||
using blender::Set;
|
||||
using blender::StringRef;
|
||||
using blender::StringRefNull;
|
||||
using blender::bke::AttributeIDRef;
|
||||
using blender::bke::OutputAttribute;
|
||||
using blender::fn::GMutableSpan;
|
||||
using blender::fn::GSpan;
|
||||
using blender::fn::GVArray_For_GSpan;
|
||||
@@ -334,8 +336,20 @@ bool BuiltinCustomDataLayerProvider::exists(const GeometryComponent &component)
|
||||
return data != nullptr;
|
||||
}
|
||||
|
||||
static bool custom_data_layer_matches_attribute_id(const CustomDataLayer &layer,
|
||||
const AttributeIDRef &attribute_id)
|
||||
{
|
||||
if (!attribute_id) {
|
||||
return false;
|
||||
}
|
||||
if (attribute_id.is_anonymous()) {
|
||||
return layer.anonymous_id == &attribute_id.anonymous_id();
|
||||
}
|
||||
return layer.name == attribute_id.name();
|
||||
}
|
||||
|
||||
ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read(
|
||||
const GeometryComponent &component, const StringRef attribute_name) const
|
||||
const GeometryComponent &component, const AttributeIDRef &attribute_id) const
|
||||
{
|
||||
const CustomData *custom_data = custom_data_access_.get_const_custom_data(component);
|
||||
if (custom_data == nullptr) {
|
||||
@@ -343,7 +357,7 @@ ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read(
|
||||
}
|
||||
const int domain_size = component.attribute_domain_size(domain_);
|
||||
for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) {
|
||||
if (layer.name != attribute_name) {
|
||||
if (!custom_data_layer_matches_attribute_id(layer, attribute_id)) {
|
||||
continue;
|
||||
}
|
||||
const CustomDataType data_type = (CustomDataType)layer.type;
|
||||
@@ -368,7 +382,7 @@ ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read(
|
||||
}
|
||||
|
||||
WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write(
|
||||
GeometryComponent &component, const StringRef attribute_name) const
|
||||
GeometryComponent &component, const AttributeIDRef &attribute_id) const
|
||||
{
|
||||
CustomData *custom_data = custom_data_access_.get_custom_data(component);
|
||||
if (custom_data == nullptr) {
|
||||
@@ -376,10 +390,17 @@ WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write(
|
||||
}
|
||||
const int domain_size = component.attribute_domain_size(domain_);
|
||||
for (CustomDataLayer &layer : MutableSpan(custom_data->layers, custom_data->totlayer)) {
|
||||
if (layer.name != attribute_name) {
|
||||
if (!custom_data_layer_matches_attribute_id(layer, attribute_id)) {
|
||||
continue;
|
||||
}
|
||||
CustomData_duplicate_referenced_layer_named(custom_data, layer.type, layer.name, domain_size);
|
||||
if (attribute_id.is_named()) {
|
||||
CustomData_duplicate_referenced_layer_named(
|
||||
custom_data, layer.type, layer.name, domain_size);
|
||||
}
|
||||
else {
|
||||
CustomData_duplicate_referenced_layer_anonymous(
|
||||
custom_data, layer.type, &attribute_id.anonymous_id(), domain_size);
|
||||
}
|
||||
const CustomDataType data_type = (CustomDataType)layer.type;
|
||||
switch (data_type) {
|
||||
case CD_PROP_FLOAT:
|
||||
@@ -402,7 +423,7 @@ WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write(
|
||||
}
|
||||
|
||||
bool CustomDataAttributeProvider::try_delete(GeometryComponent &component,
|
||||
const StringRef attribute_name) const
|
||||
const AttributeIDRef &attribute_id) const
|
||||
{
|
||||
CustomData *custom_data = custom_data_access_.get_custom_data(component);
|
||||
if (custom_data == nullptr) {
|
||||
@@ -411,7 +432,8 @@ bool CustomDataAttributeProvider::try_delete(GeometryComponent &component,
|
||||
const int domain_size = component.attribute_domain_size(domain_);
|
||||
for (const int i : IndexRange(custom_data->totlayer)) {
|
||||
const CustomDataLayer &layer = custom_data->layers[i];
|
||||
if (this->type_is_supported((CustomDataType)layer.type) && layer.name == attribute_name) {
|
||||
if (this->type_is_supported((CustomDataType)layer.type) &&
|
||||
custom_data_layer_matches_attribute_id(layer, attribute_id)) {
|
||||
CustomData_free_layer(custom_data, layer.type, domain_size, i);
|
||||
return true;
|
||||
}
|
||||
@@ -419,24 +441,39 @@ bool CustomDataAttributeProvider::try_delete(GeometryComponent &component,
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool add_named_custom_data_layer_from_attribute_init(const StringRef attribute_name,
|
||||
CustomData &custom_data,
|
||||
const CustomDataType data_type,
|
||||
const int domain_size,
|
||||
const AttributeInit &initializer)
|
||||
static void *add_generic_custom_data_layer(CustomData &custom_data,
|
||||
const CustomDataType data_type,
|
||||
const eCDAllocType alloctype,
|
||||
void *layer_data,
|
||||
const int domain_size,
|
||||
const AttributeIDRef &attribute_id)
|
||||
{
|
||||
char attribute_name_c[MAX_NAME];
|
||||
attribute_name.copy(attribute_name_c);
|
||||
if (attribute_id.is_named()) {
|
||||
char attribute_name_c[MAX_NAME];
|
||||
attribute_id.name().copy(attribute_name_c);
|
||||
return CustomData_add_layer_named(
|
||||
&custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c);
|
||||
}
|
||||
const AnonymousAttributeID &anonymous_id = attribute_id.anonymous_id();
|
||||
return CustomData_add_layer_anonymous(
|
||||
&custom_data, data_type, alloctype, layer_data, domain_size, &anonymous_id);
|
||||
}
|
||||
|
||||
static bool add_custom_data_layer_from_attribute_init(const AttributeIDRef &attribute_id,
|
||||
CustomData &custom_data,
|
||||
const CustomDataType data_type,
|
||||
const int domain_size,
|
||||
const AttributeInit &initializer)
|
||||
{
|
||||
switch (initializer.type) {
|
||||
case AttributeInit::Type::Default: {
|
||||
void *data = CustomData_add_layer_named(
|
||||
&custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c);
|
||||
void *data = add_generic_custom_data_layer(
|
||||
custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_id);
|
||||
return data != nullptr;
|
||||
}
|
||||
case AttributeInit::Type::VArray: {
|
||||
void *data = CustomData_add_layer_named(
|
||||
&custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c);
|
||||
void *data = add_generic_custom_data_layer(
|
||||
custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_id);
|
||||
if (data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
@@ -446,8 +483,8 @@ static bool add_named_custom_data_layer_from_attribute_init(const StringRef attr
|
||||
}
|
||||
case AttributeInit::Type::MoveArray: {
|
||||
void *source_data = static_cast<const AttributeInitMove &>(initializer).data;
|
||||
void *data = CustomData_add_layer_named(
|
||||
&custom_data, data_type, CD_ASSIGN, source_data, domain_size, attribute_name_c);
|
||||
void *data = add_generic_custom_data_layer(
|
||||
custom_data, data_type, CD_ASSIGN, source_data, domain_size, attribute_id);
|
||||
if (data == nullptr) {
|
||||
MEM_freeN(source_data);
|
||||
return false;
|
||||
@@ -461,7 +498,7 @@ static bool add_named_custom_data_layer_from_attribute_init(const StringRef attr
|
||||
}
|
||||
|
||||
bool CustomDataAttributeProvider::try_create(GeometryComponent &component,
|
||||
const StringRef attribute_name,
|
||||
const AttributeIDRef &attribute_id,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const AttributeInit &initializer) const
|
||||
@@ -477,13 +514,13 @@ bool CustomDataAttributeProvider::try_create(GeometryComponent &component,
|
||||
return false;
|
||||
}
|
||||
for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) {
|
||||
if (layer.name == attribute_name) {
|
||||
if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const int domain_size = component.attribute_domain_size(domain_);
|
||||
add_named_custom_data_layer_from_attribute_init(
|
||||
attribute_name, *custom_data, data_type, domain_size, initializer);
|
||||
add_custom_data_layer_from_attribute_init(
|
||||
attribute_id, *custom_data, data_type, domain_size, initializer);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -498,7 +535,14 @@ bool CustomDataAttributeProvider::foreach_attribute(const GeometryComponent &com
|
||||
const CustomDataType data_type = (CustomDataType)layer.type;
|
||||
if (this->type_is_supported(data_type)) {
|
||||
AttributeMetaData meta_data{domain_, data_type};
|
||||
if (!callback(layer.name, meta_data)) {
|
||||
AttributeIDRef attribute_id;
|
||||
if (layer.anonymous_id != nullptr) {
|
||||
attribute_id = layer.anonymous_id;
|
||||
}
|
||||
else {
|
||||
attribute_id = layer.name;
|
||||
}
|
||||
if (!callback(attribute_id, meta_data)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -507,7 +551,7 @@ bool CustomDataAttributeProvider::foreach_attribute(const GeometryComponent &com
|
||||
}
|
||||
|
||||
ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read(
|
||||
const GeometryComponent &component, const StringRef attribute_name) const
|
||||
const GeometryComponent &component, const AttributeIDRef &attribute_id) const
|
||||
{
|
||||
const CustomData *custom_data = custom_data_access_.get_const_custom_data(component);
|
||||
if (custom_data == nullptr) {
|
||||
@@ -515,7 +559,7 @@ ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read(
|
||||
}
|
||||
for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) {
|
||||
if (layer.type == stored_type_) {
|
||||
if (layer.name == attribute_name) {
|
||||
if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
|
||||
const int domain_size = component.attribute_domain_size(domain_);
|
||||
return {as_read_attribute_(layer.data, domain_size), domain_};
|
||||
}
|
||||
@@ -525,7 +569,7 @@ ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read(
|
||||
}
|
||||
|
||||
WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write(
|
||||
GeometryComponent &component, const StringRef attribute_name) const
|
||||
GeometryComponent &component, const AttributeIDRef &attribute_id) const
|
||||
{
|
||||
CustomData *custom_data = custom_data_access_.get_custom_data(component);
|
||||
if (custom_data == nullptr) {
|
||||
@@ -533,7 +577,7 @@ WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write(
|
||||
}
|
||||
for (CustomDataLayer &layer : MutableSpan(custom_data->layers, custom_data->totlayer)) {
|
||||
if (layer.type == stored_type_) {
|
||||
if (layer.name == attribute_name) {
|
||||
if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
|
||||
const int domain_size = component.attribute_domain_size(domain_);
|
||||
void *data_old = layer.data;
|
||||
void *data_new = CustomData_duplicate_referenced_layer_named(
|
||||
@@ -549,7 +593,7 @@ WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write(
|
||||
}
|
||||
|
||||
bool NamedLegacyCustomDataProvider::try_delete(GeometryComponent &component,
|
||||
const StringRef attribute_name) const
|
||||
const AttributeIDRef &attribute_id) const
|
||||
{
|
||||
CustomData *custom_data = custom_data_access_.get_custom_data(component);
|
||||
if (custom_data == nullptr) {
|
||||
@@ -558,7 +602,7 @@ bool NamedLegacyCustomDataProvider::try_delete(GeometryComponent &component,
|
||||
for (const int i : IndexRange(custom_data->totlayer)) {
|
||||
const CustomDataLayer &layer = custom_data->layers[i];
|
||||
if (layer.type == stored_type_) {
|
||||
if (layer.name == attribute_name) {
|
||||
if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
|
||||
const int domain_size = component.attribute_domain_size(domain_);
|
||||
CustomData_free_layer(custom_data, stored_type_, domain_size, i);
|
||||
custom_data_access_.update_custom_data_pointers(component);
|
||||
@@ -627,11 +671,11 @@ CustomDataAttributes &CustomDataAttributes::operator=(const CustomDataAttributes
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::optional<GSpan> CustomDataAttributes::get_for_read(const StringRef name) const
|
||||
std::optional<GSpan> CustomDataAttributes::get_for_read(const AttributeIDRef &attribute_id) const
|
||||
{
|
||||
BLI_assert(size_ != 0);
|
||||
for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) {
|
||||
if (layer.name == name) {
|
||||
if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
|
||||
const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type);
|
||||
BLI_assert(cpp_type != nullptr);
|
||||
return GSpan(*cpp_type, layer.data, size_);
|
||||
@@ -645,13 +689,13 @@ std::optional<GSpan> CustomDataAttributes::get_for_read(const StringRef name) co
|
||||
* value if the attribute doesn't exist. If no default value is provided, the default value for the
|
||||
* type will be used.
|
||||
*/
|
||||
GVArrayPtr CustomDataAttributes::get_for_read(const StringRef name,
|
||||
GVArrayPtr CustomDataAttributes::get_for_read(const AttributeIDRef &attribute_id,
|
||||
const CustomDataType data_type,
|
||||
const void *default_value) const
|
||||
{
|
||||
const CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type);
|
||||
|
||||
std::optional<GSpan> attribute = this->get_for_read(name);
|
||||
std::optional<GSpan> attribute = this->get_for_read(attribute_id);
|
||||
if (!attribute) {
|
||||
const int domain_size = this->size_;
|
||||
return std::make_unique<GVArray_For_SingleValue>(
|
||||
@@ -666,12 +710,12 @@ GVArrayPtr CustomDataAttributes::get_for_read(const StringRef name,
|
||||
return conversions.try_convert(std::make_unique<GVArray_For_GSpan>(*attribute), *type);
|
||||
}
|
||||
|
||||
std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const StringRef name)
|
||||
std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const AttributeIDRef &attribute_id)
|
||||
{
|
||||
/* If this assert hits, it most likely means that #reallocate was not called at some point. */
|
||||
BLI_assert(size_ != 0);
|
||||
for (CustomDataLayer &layer : MutableSpan(data.layers, data.totlayer)) {
|
||||
if (layer.name == name) {
|
||||
if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
|
||||
const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type);
|
||||
BLI_assert(cpp_type != nullptr);
|
||||
return GMutableSpan(*cpp_type, layer.data, size_);
|
||||
@@ -680,30 +724,29 @@ std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const StringRef
|
||||
return {};
|
||||
}
|
||||
|
||||
bool CustomDataAttributes::create(const StringRef name, const CustomDataType data_type)
|
||||
bool CustomDataAttributes::create(const AttributeIDRef &attribute_id,
|
||||
const CustomDataType data_type)
|
||||
{
|
||||
char name_c[MAX_NAME];
|
||||
name.copy(name_c);
|
||||
void *result = CustomData_add_layer_named(&data, data_type, CD_DEFAULT, nullptr, size_, name_c);
|
||||
void *result = add_generic_custom_data_layer(
|
||||
data, data_type, CD_DEFAULT, nullptr, size_, attribute_id);
|
||||
return result != nullptr;
|
||||
}
|
||||
|
||||
bool CustomDataAttributes::create_by_move(const blender::StringRef name,
|
||||
bool CustomDataAttributes::create_by_move(const AttributeIDRef &attribute_id,
|
||||
const CustomDataType data_type,
|
||||
void *buffer)
|
||||
{
|
||||
char name_c[MAX_NAME];
|
||||
name.copy(name_c);
|
||||
void *result = CustomData_add_layer_named(&data, data_type, CD_ASSIGN, buffer, size_, name_c);
|
||||
void *result = add_generic_custom_data_layer(
|
||||
data, data_type, CD_ASSIGN, buffer, size_, attribute_id);
|
||||
return result != nullptr;
|
||||
}
|
||||
|
||||
bool CustomDataAttributes::remove(const blender::StringRef name)
|
||||
bool CustomDataAttributes::remove(const AttributeIDRef &attribute_id)
|
||||
{
|
||||
bool result = false;
|
||||
for (const int i : IndexRange(data.totlayer)) {
|
||||
const CustomDataLayer &layer = data.layers[i];
|
||||
if (layer.name == name) {
|
||||
if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
|
||||
CustomData_free_layer(&data, layer.type, size_, i);
|
||||
result = true;
|
||||
}
|
||||
@@ -722,7 +765,14 @@ bool CustomDataAttributes::foreach_attribute(const AttributeForeachCallback call
|
||||
{
|
||||
for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) {
|
||||
AttributeMetaData meta_data{domain, (CustomDataType)layer.type};
|
||||
if (!callback(layer.name, meta_data)) {
|
||||
AttributeIDRef attribute_id;
|
||||
if (layer.anonymous_id != nullptr) {
|
||||
attribute_id = layer.anonymous_id;
|
||||
}
|
||||
else {
|
||||
attribute_id = layer.name;
|
||||
}
|
||||
if (!callback(attribute_id, meta_data)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -766,21 +816,23 @@ bool GeometryComponent::attribute_is_builtin(const blender::StringRef attribute_
|
||||
}
|
||||
|
||||
blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read(
|
||||
const StringRef attribute_name) const
|
||||
const AttributeIDRef &attribute_id) const
|
||||
{
|
||||
using namespace blender::bke;
|
||||
const ComponentAttributeProviders *providers = this->get_attribute_providers();
|
||||
if (providers == nullptr) {
|
||||
return {};
|
||||
}
|
||||
const BuiltinAttributeProvider *builtin_provider =
|
||||
providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
|
||||
if (builtin_provider != nullptr) {
|
||||
return {builtin_provider->try_get_for_read(*this), builtin_provider->domain()};
|
||||
if (attribute_id.is_named()) {
|
||||
const BuiltinAttributeProvider *builtin_provider =
|
||||
providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr);
|
||||
if (builtin_provider != nullptr) {
|
||||
return {builtin_provider->try_get_for_read(*this), builtin_provider->domain()};
|
||||
}
|
||||
}
|
||||
for (const DynamicAttributesProvider *dynamic_provider :
|
||||
providers->dynamic_attribute_providers()) {
|
||||
ReadAttributeLookup attribute = dynamic_provider->try_get_for_read(*this, attribute_name);
|
||||
ReadAttributeLookup attribute = dynamic_provider->try_get_for_read(*this, attribute_id);
|
||||
if (attribute) {
|
||||
return attribute;
|
||||
}
|
||||
@@ -800,21 +852,23 @@ std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_adapt_dom
|
||||
}
|
||||
|
||||
blender::bke::WriteAttributeLookup GeometryComponent::attribute_try_get_for_write(
|
||||
const StringRef attribute_name)
|
||||
const AttributeIDRef &attribute_id)
|
||||
{
|
||||
using namespace blender::bke;
|
||||
const ComponentAttributeProviders *providers = this->get_attribute_providers();
|
||||
if (providers == nullptr) {
|
||||
return {};
|
||||
}
|
||||
const BuiltinAttributeProvider *builtin_provider =
|
||||
providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
|
||||
if (builtin_provider != nullptr) {
|
||||
return {builtin_provider->try_get_for_write(*this), builtin_provider->domain()};
|
||||
if (attribute_id.is_named()) {
|
||||
const BuiltinAttributeProvider *builtin_provider =
|
||||
providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr);
|
||||
if (builtin_provider != nullptr) {
|
||||
return {builtin_provider->try_get_for_write(*this), builtin_provider->domain()};
|
||||
}
|
||||
}
|
||||
for (const DynamicAttributesProvider *dynamic_provider :
|
||||
providers->dynamic_attribute_providers()) {
|
||||
WriteAttributeLookup attribute = dynamic_provider->try_get_for_write(*this, attribute_name);
|
||||
WriteAttributeLookup attribute = dynamic_provider->try_get_for_write(*this, attribute_id);
|
||||
if (attribute) {
|
||||
return attribute;
|
||||
}
|
||||
@@ -822,53 +876,57 @@ blender::bke::WriteAttributeLookup GeometryComponent::attribute_try_get_for_writ
|
||||
return {};
|
||||
}
|
||||
|
||||
bool GeometryComponent::attribute_try_delete(const StringRef attribute_name)
|
||||
bool GeometryComponent::attribute_try_delete(const AttributeIDRef &attribute_id)
|
||||
{
|
||||
using namespace blender::bke;
|
||||
const ComponentAttributeProviders *providers = this->get_attribute_providers();
|
||||
if (providers == nullptr) {
|
||||
return {};
|
||||
}
|
||||
const BuiltinAttributeProvider *builtin_provider =
|
||||
providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
|
||||
if (builtin_provider != nullptr) {
|
||||
return builtin_provider->try_delete(*this);
|
||||
if (attribute_id.is_named()) {
|
||||
const BuiltinAttributeProvider *builtin_provider =
|
||||
providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr);
|
||||
if (builtin_provider != nullptr) {
|
||||
return builtin_provider->try_delete(*this);
|
||||
}
|
||||
}
|
||||
bool success = false;
|
||||
for (const DynamicAttributesProvider *dynamic_provider :
|
||||
providers->dynamic_attribute_providers()) {
|
||||
success = dynamic_provider->try_delete(*this, attribute_name) || success;
|
||||
success = dynamic_provider->try_delete(*this, attribute_id) || success;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool GeometryComponent::attribute_try_create(const StringRef attribute_name,
|
||||
bool GeometryComponent::attribute_try_create(const AttributeIDRef &attribute_id,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const AttributeInit &initializer)
|
||||
{
|
||||
using namespace blender::bke;
|
||||
if (attribute_name.is_empty()) {
|
||||
if (!attribute_id) {
|
||||
return false;
|
||||
}
|
||||
const ComponentAttributeProviders *providers = this->get_attribute_providers();
|
||||
if (providers == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const BuiltinAttributeProvider *builtin_provider =
|
||||
providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
|
||||
if (builtin_provider != nullptr) {
|
||||
if (builtin_provider->domain() != domain) {
|
||||
return false;
|
||||
if (attribute_id.is_named()) {
|
||||
const BuiltinAttributeProvider *builtin_provider =
|
||||
providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr);
|
||||
if (builtin_provider != nullptr) {
|
||||
if (builtin_provider->domain() != domain) {
|
||||
return false;
|
||||
}
|
||||
if (builtin_provider->data_type() != data_type) {
|
||||
return false;
|
||||
}
|
||||
return builtin_provider->try_create(*this, initializer);
|
||||
}
|
||||
if (builtin_provider->data_type() != data_type) {
|
||||
return false;
|
||||
}
|
||||
return builtin_provider->try_create(*this, initializer);
|
||||
}
|
||||
for (const DynamicAttributesProvider *dynamic_provider :
|
||||
providers->dynamic_attribute_providers()) {
|
||||
if (dynamic_provider->try_create(*this, attribute_name, domain, data_type, initializer)) {
|
||||
if (dynamic_provider->try_create(*this, attribute_id, domain, data_type, initializer)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -894,13 +952,14 @@ bool GeometryComponent::attribute_try_create_builtin(const blender::StringRef at
|
||||
return builtin_provider->try_create(*this, initializer);
|
||||
}
|
||||
|
||||
Set<std::string> GeometryComponent::attribute_names() const
|
||||
Set<AttributeIDRef> GeometryComponent::attribute_ids() const
|
||||
{
|
||||
Set<std::string> attributes;
|
||||
this->attribute_foreach([&](StringRefNull name, const AttributeMetaData &UNUSED(meta_data)) {
|
||||
attributes.add(name);
|
||||
return true;
|
||||
});
|
||||
Set<AttributeIDRef> attributes;
|
||||
this->attribute_foreach(
|
||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) {
|
||||
attributes.add(attribute_id);
|
||||
return true;
|
||||
});
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@@ -931,9 +990,9 @@ bool GeometryComponent::attribute_foreach(const AttributeForeachCallback callbac
|
||||
}
|
||||
for (const DynamicAttributesProvider *provider : providers->dynamic_attribute_providers()) {
|
||||
const bool continue_loop = provider->foreach_attribute(
|
||||
*this, [&](StringRefNull name, const AttributeMetaData &meta_data) {
|
||||
if (handled_attribute_names.add(name)) {
|
||||
return callback(name, meta_data);
|
||||
*this, [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
|
||||
if (attribute_id.is_anonymous() || handled_attribute_names.add(attribute_id.name())) {
|
||||
return callback(attribute_id, meta_data);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -945,9 +1004,9 @@ bool GeometryComponent::attribute_foreach(const AttributeForeachCallback callbac
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GeometryComponent::attribute_exists(const blender::StringRef attribute_name) const
|
||||
bool GeometryComponent::attribute_exists(const AttributeIDRef &attribute_id) const
|
||||
{
|
||||
blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
|
||||
blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id);
|
||||
if (attribute) {
|
||||
return true;
|
||||
}
|
||||
@@ -955,16 +1014,17 @@ bool GeometryComponent::attribute_exists(const blender::StringRef attribute_name
|
||||
}
|
||||
|
||||
std::optional<AttributeMetaData> GeometryComponent::attribute_get_meta_data(
|
||||
const StringRef attribute_name) const
|
||||
const AttributeIDRef &attribute_id) const
|
||||
{
|
||||
std::optional<AttributeMetaData> result{std::nullopt};
|
||||
this->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
|
||||
if (attribute_name == name) {
|
||||
result = meta_data;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
this->attribute_foreach(
|
||||
[&](const AttributeIDRef ¤t_attribute_id, const AttributeMetaData &meta_data) {
|
||||
if (attribute_id == current_attribute_id) {
|
||||
result = meta_data;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -977,11 +1037,11 @@ static std::unique_ptr<blender::fn::GVArray> try_adapt_data_type(
|
||||
}
|
||||
|
||||
std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_get_for_read(
|
||||
const StringRef attribute_name,
|
||||
const AttributeIDRef &attribute_id,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type) const
|
||||
{
|
||||
blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
|
||||
blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id);
|
||||
if (!attribute) {
|
||||
return {};
|
||||
}
|
||||
@@ -1007,13 +1067,13 @@ std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_get_for_r
|
||||
}
|
||||
|
||||
std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_try_get_for_read(
|
||||
const StringRef attribute_name, const AttributeDomain domain) const
|
||||
const AttributeIDRef &attribute_id, const AttributeDomain domain) const
|
||||
{
|
||||
if (!this->attribute_domain_supported(domain)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
|
||||
blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id);
|
||||
if (!attribute) {
|
||||
return {};
|
||||
}
|
||||
@@ -1026,9 +1086,9 @@ std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_try_get_for_
|
||||
}
|
||||
|
||||
blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read(
|
||||
const blender::StringRef attribute_name, const CustomDataType data_type) const
|
||||
const AttributeIDRef &attribute_id, const CustomDataType data_type) const
|
||||
{
|
||||
blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
|
||||
blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id);
|
||||
if (!attribute) {
|
||||
return {};
|
||||
}
|
||||
@@ -1043,13 +1103,13 @@ blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read(
|
||||
}
|
||||
|
||||
std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_get_for_read(
|
||||
const StringRef attribute_name,
|
||||
const AttributeIDRef &attribute_id,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const void *default_value) const
|
||||
{
|
||||
std::unique_ptr<blender::bke::GVArray> varray = this->attribute_try_get_for_read(
|
||||
attribute_name, domain, data_type);
|
||||
attribute_id, domain, data_type);
|
||||
if (varray) {
|
||||
return varray;
|
||||
}
|
||||
@@ -1065,15 +1125,22 @@ class GVMutableAttribute_For_OutputAttribute
|
||||
: public blender::fn::GVMutableArray_For_GMutableSpan {
|
||||
public:
|
||||
GeometryComponent *component;
|
||||
std::string final_name;
|
||||
std::string attribute_name;
|
||||
blender::bke::WeakAnonymousAttributeID anonymous_attribute_id;
|
||||
|
||||
GVMutableAttribute_For_OutputAttribute(GMutableSpan data,
|
||||
GeometryComponent &component,
|
||||
std::string final_name)
|
||||
: blender::fn::GVMutableArray_For_GMutableSpan(data),
|
||||
component(&component),
|
||||
final_name(std::move(final_name))
|
||||
const AttributeIDRef &attribute_id)
|
||||
: blender::fn::GVMutableArray_For_GMutableSpan(data), component(&component)
|
||||
{
|
||||
if (attribute_id.is_named()) {
|
||||
this->attribute_name = attribute_id.name();
|
||||
}
|
||||
else {
|
||||
const AnonymousAttributeID *anonymous_id = &attribute_id.anonymous_id();
|
||||
BKE_anonymous_attribute_id_increment_weak(anonymous_id);
|
||||
this->anonymous_attribute_id = blender::bke::WeakAnonymousAttributeID{anonymous_id};
|
||||
}
|
||||
}
|
||||
|
||||
~GVMutableAttribute_For_OutputAttribute() override
|
||||
@@ -1083,7 +1150,7 @@ class GVMutableAttribute_For_OutputAttribute
|
||||
}
|
||||
};
|
||||
|
||||
static void save_output_attribute(blender::bke::OutputAttribute &output_attribute)
|
||||
static void save_output_attribute(OutputAttribute &output_attribute)
|
||||
{
|
||||
using namespace blender;
|
||||
using namespace blender::fn;
|
||||
@@ -1093,21 +1160,28 @@ static void save_output_attribute(blender::bke::OutputAttribute &output_attribut
|
||||
dynamic_cast<GVMutableAttribute_For_OutputAttribute &>(output_attribute.varray());
|
||||
|
||||
GeometryComponent &component = *varray.component;
|
||||
const StringRefNull name = varray.final_name;
|
||||
AttributeIDRef attribute_id;
|
||||
if (!varray.attribute_name.empty()) {
|
||||
attribute_id = varray.attribute_name;
|
||||
}
|
||||
else {
|
||||
attribute_id = varray.anonymous_attribute_id.extract();
|
||||
}
|
||||
const AttributeDomain domain = output_attribute.domain();
|
||||
const CustomDataType data_type = output_attribute.custom_data_type();
|
||||
const CPPType &cpp_type = output_attribute.cpp_type();
|
||||
|
||||
component.attribute_try_delete(name);
|
||||
if (!component.attribute_try_create(
|
||||
varray.final_name, domain, data_type, AttributeInitDefault())) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Could not create the '%s' attribute with type '%s'.",
|
||||
name.c_str(),
|
||||
cpp_type.name().c_str());
|
||||
component.attribute_try_delete(attribute_id);
|
||||
if (!component.attribute_try_create(attribute_id, domain, data_type, AttributeInitDefault())) {
|
||||
if (!varray.attribute_name.empty()) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Could not create the '%s' attribute with type '%s'.",
|
||||
varray.attribute_name.c_str(),
|
||||
cpp_type.name().c_str());
|
||||
}
|
||||
return;
|
||||
}
|
||||
WriteAttributeLookup write_attribute = component.attribute_try_get_for_write(name);
|
||||
WriteAttributeLookup write_attribute = component.attribute_try_get_for_write(attribute_id);
|
||||
BUFFER_FOR_CPP_TYPE_VALUE(varray.type(), buffer);
|
||||
for (const int i : IndexRange(varray.size())) {
|
||||
varray.get(i, buffer);
|
||||
@@ -1115,19 +1189,18 @@ static void save_output_attribute(blender::bke::OutputAttribute &output_attribut
|
||||
}
|
||||
}
|
||||
|
||||
static blender::bke::OutputAttribute create_output_attribute(
|
||||
GeometryComponent &component,
|
||||
const blender::StringRef attribute_name,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const bool ignore_old_values,
|
||||
const void *default_value)
|
||||
static OutputAttribute create_output_attribute(GeometryComponent &component,
|
||||
const AttributeIDRef &attribute_id,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const bool ignore_old_values,
|
||||
const void *default_value)
|
||||
{
|
||||
using namespace blender;
|
||||
using namespace blender::fn;
|
||||
using namespace blender::bke;
|
||||
|
||||
if (attribute_name.is_empty()) {
|
||||
if (!attribute_id) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -1135,7 +1208,8 @@ static blender::bke::OutputAttribute create_output_attribute(
|
||||
BLI_assert(cpp_type != nullptr);
|
||||
const nodes::DataTypeConversions &conversions = nodes::get_implicit_type_conversions();
|
||||
|
||||
if (component.attribute_is_builtin(attribute_name)) {
|
||||
if (attribute_id.is_named() && component.attribute_is_builtin(attribute_id.name())) {
|
||||
const StringRef attribute_name = attribute_id.name();
|
||||
WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_name);
|
||||
if (!attribute) {
|
||||
if (default_value) {
|
||||
@@ -1169,18 +1243,18 @@ static blender::bke::OutputAttribute create_output_attribute(
|
||||
|
||||
const int domain_size = component.attribute_domain_size(domain);
|
||||
|
||||
WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_name);
|
||||
WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_id);
|
||||
if (!attribute) {
|
||||
if (default_value) {
|
||||
const GVArray_For_SingleValueRef default_varray{*cpp_type, domain_size, default_value};
|
||||
component.attribute_try_create(
|
||||
attribute_name, domain, data_type, AttributeInitVArray(&default_varray));
|
||||
attribute_id, domain, data_type, AttributeInitVArray(&default_varray));
|
||||
}
|
||||
else {
|
||||
component.attribute_try_create(attribute_name, domain, data_type, AttributeInitDefault());
|
||||
component.attribute_try_create(attribute_id, domain, data_type, AttributeInitDefault());
|
||||
}
|
||||
|
||||
attribute = component.attribute_try_get_for_write(attribute_name);
|
||||
attribute = component.attribute_try_get_for_write(attribute_id);
|
||||
if (!attribute) {
|
||||
/* Can't create the attribute. */
|
||||
return {};
|
||||
@@ -1202,28 +1276,88 @@ static blender::bke::OutputAttribute create_output_attribute(
|
||||
else {
|
||||
/* Fill the temporary array with values from the existing attribute. */
|
||||
GVArrayPtr old_varray = component.attribute_get_for_read(
|
||||
attribute_name, domain, data_type, default_value);
|
||||
attribute_id, domain, data_type, default_value);
|
||||
old_varray->materialize_to_uninitialized(IndexRange(domain_size), data);
|
||||
}
|
||||
GVMutableArrayPtr varray = std::make_unique<GVMutableAttribute_For_OutputAttribute>(
|
||||
GMutableSpan{*cpp_type, data, domain_size}, component, attribute_name);
|
||||
GMutableSpan{*cpp_type, data, domain_size}, component, attribute_id);
|
||||
|
||||
return OutputAttribute(std::move(varray), domain, save_output_attribute, true);
|
||||
}
|
||||
|
||||
blender::bke::OutputAttribute GeometryComponent::attribute_try_get_for_output(
|
||||
const StringRef attribute_name,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const void *default_value)
|
||||
OutputAttribute GeometryComponent::attribute_try_get_for_output(const AttributeIDRef &attribute_id,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const void *default_value)
|
||||
{
|
||||
return create_output_attribute(*this, attribute_name, domain, data_type, false, default_value);
|
||||
return create_output_attribute(*this, attribute_id, domain, data_type, false, default_value);
|
||||
}
|
||||
|
||||
blender::bke::OutputAttribute GeometryComponent::attribute_try_get_for_output_only(
|
||||
const blender::StringRef attribute_name,
|
||||
OutputAttribute GeometryComponent::attribute_try_get_for_output_only(
|
||||
const AttributeIDRef &attribute_id,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type)
|
||||
{
|
||||
return create_output_attribute(*this, attribute_name, domain, data_type, true, nullptr);
|
||||
return create_output_attribute(*this, attribute_id, domain, data_type, true, nullptr);
|
||||
}
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
const GVArray *AttributeFieldInput::get_varray_for_context(const fn::FieldContext &context,
|
||||
IndexMask UNUSED(mask),
|
||||
ResourceScope &scope) const
|
||||
{
|
||||
if (const GeometryComponentFieldContext *geometry_context =
|
||||
dynamic_cast<const GeometryComponentFieldContext *>(&context)) {
|
||||
const GeometryComponent &component = geometry_context->geometry_component();
|
||||
const AttributeDomain domain = geometry_context->domain();
|
||||
const CustomDataType data_type = cpp_type_to_custom_data_type(*type_);
|
||||
GVArrayPtr attribute = component.attribute_try_get_for_read(name_, domain, data_type);
|
||||
return scope.add(std::move(attribute), __func__);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint64_t AttributeFieldInput::hash() const
|
||||
{
|
||||
return get_default_hash_2(name_, type_);
|
||||
}
|
||||
|
||||
bool AttributeFieldInput::is_equal_to(const fn::FieldNode &other) const
|
||||
{
|
||||
if (const AttributeFieldInput *other_typed = dynamic_cast<const AttributeFieldInput *>(&other)) {
|
||||
return name_ == other_typed->name_ && type_ == other_typed->type_;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const GVArray *AnonymousAttributeFieldInput::get_varray_for_context(
|
||||
const fn::FieldContext &context, IndexMask UNUSED(mask), ResourceScope &scope) const
|
||||
{
|
||||
if (const GeometryComponentFieldContext *geometry_context =
|
||||
dynamic_cast<const GeometryComponentFieldContext *>(&context)) {
|
||||
const GeometryComponent &component = geometry_context->geometry_component();
|
||||
const AttributeDomain domain = geometry_context->domain();
|
||||
const CustomDataType data_type = cpp_type_to_custom_data_type(*type_);
|
||||
GVArrayPtr attribute = component.attribute_try_get_for_read(
|
||||
anonymous_id_.get(), domain, data_type);
|
||||
return scope.add(std::move(attribute), __func__);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint64_t AnonymousAttributeFieldInput::hash() const
|
||||
{
|
||||
return get_default_hash_2(anonymous_id_.get(), type_);
|
||||
}
|
||||
|
||||
bool AnonymousAttributeFieldInput::is_equal_to(const fn::FieldNode &other) const
|
||||
{
|
||||
if (const AnonymousAttributeFieldInput *other_typed =
|
||||
dynamic_cast<const AnonymousAttributeFieldInput *>(&other)) {
|
||||
return anonymous_id_.get() == other_typed->anonymous_id_.get() && type_ == other_typed->type_;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
@@ -116,12 +116,13 @@ class BuiltinAttributeProvider {
|
||||
class DynamicAttributesProvider {
|
||||
public:
|
||||
virtual ReadAttributeLookup try_get_for_read(const GeometryComponent &component,
|
||||
const StringRef attribute_name) const = 0;
|
||||
const AttributeIDRef &attribute_id) const = 0;
|
||||
virtual WriteAttributeLookup try_get_for_write(GeometryComponent &component,
|
||||
const StringRef attribute_name) const = 0;
|
||||
virtual bool try_delete(GeometryComponent &component, const StringRef attribute_name) const = 0;
|
||||
const AttributeIDRef &attribute_id) const = 0;
|
||||
virtual bool try_delete(GeometryComponent &component,
|
||||
const AttributeIDRef &attribute_id) const = 0;
|
||||
virtual bool try_create(GeometryComponent &UNUSED(component),
|
||||
const StringRef UNUSED(attribute_name),
|
||||
const AttributeIDRef &UNUSED(attribute_id),
|
||||
const AttributeDomain UNUSED(domain),
|
||||
const CustomDataType UNUSED(data_type),
|
||||
const AttributeInit &UNUSED(initializer)) const
|
||||
@@ -154,15 +155,15 @@ class CustomDataAttributeProvider final : public DynamicAttributesProvider {
|
||||
}
|
||||
|
||||
ReadAttributeLookup try_get_for_read(const GeometryComponent &component,
|
||||
const StringRef attribute_name) const final;
|
||||
const AttributeIDRef &attribute_id) const final;
|
||||
|
||||
WriteAttributeLookup try_get_for_write(GeometryComponent &component,
|
||||
const StringRef attribute_name) const final;
|
||||
const AttributeIDRef &attribute_id) const final;
|
||||
|
||||
bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final;
|
||||
bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final;
|
||||
|
||||
bool try_create(GeometryComponent &component,
|
||||
const StringRef attribute_name,
|
||||
const AttributeIDRef &attribute_id,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const AttributeInit &initializer) const final;
|
||||
@@ -231,10 +232,10 @@ class NamedLegacyCustomDataProvider final : public DynamicAttributesProvider {
|
||||
}
|
||||
|
||||
ReadAttributeLookup try_get_for_read(const GeometryComponent &component,
|
||||
const StringRef attribute_name) const final;
|
||||
const AttributeIDRef &attribute_id) const final;
|
||||
WriteAttributeLookup try_get_for_write(GeometryComponent &component,
|
||||
const StringRef attribute_name) const final;
|
||||
bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final;
|
||||
const AttributeIDRef &attribute_id) const final;
|
||||
bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final;
|
||||
bool foreach_attribute(const GeometryComponent &component,
|
||||
const AttributeForeachCallback callback) const final;
|
||||
void foreach_domain(const FunctionRef<void(AttributeDomain)> callback) const final;
|
||||
|
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "DNA_curve_types.h"
|
||||
|
||||
#include "BKE_anonymous_attribute.hh"
|
||||
#include "BKE_curve.h"
|
||||
#include "BKE_spline.hh"
|
||||
|
||||
@@ -37,6 +38,7 @@ using blender::MutableSpan;
|
||||
using blender::Span;
|
||||
using blender::StringRefNull;
|
||||
using blender::Vector;
|
||||
using blender::bke::AttributeIDRef;
|
||||
|
||||
blender::Span<SplinePtr> CurveEval::splines() const
|
||||
{
|
||||
@@ -330,13 +332,13 @@ void CurveEval::assert_valid_point_attributes() const
|
||||
return;
|
||||
}
|
||||
const int layer_len = splines_.first()->attributes.data.totlayer;
|
||||
Map<StringRefNull, AttributeMetaData> map;
|
||||
Map<AttributeIDRef, AttributeMetaData> map;
|
||||
for (const SplinePtr &spline : splines_) {
|
||||
BLI_assert(spline->attributes.data.totlayer == layer_len);
|
||||
spline->attributes.foreach_attribute(
|
||||
[&](StringRefNull name, const AttributeMetaData &meta_data) {
|
||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
|
||||
map.add_or_modify(
|
||||
name,
|
||||
attribute_id,
|
||||
[&](AttributeMetaData *map_data) {
|
||||
/* All unique attribute names should be added on the first spline. */
|
||||
BLI_assert(spline == splines_.first());
|
||||
|
@@ -46,6 +46,7 @@
|
||||
|
||||
#include "BLT_translation.h"
|
||||
|
||||
#include "BKE_anonymous_attribute.h"
|
||||
#include "BKE_customdata.h"
|
||||
#include "BKE_customdata_file.h"
|
||||
#include "BKE_deform.h"
|
||||
@@ -2127,6 +2128,11 @@ bool CustomData_merge(const struct CustomData *source,
|
||||
if (flag & CD_FLAG_NOCOPY) {
|
||||
continue;
|
||||
}
|
||||
if (layer->anonymous_id &&
|
||||
!BKE_anonymous_attribute_id_has_strong_references(layer->anonymous_id)) {
|
||||
/* This attribute is not referenced anymore, so it can be treated as if it didn't exist. */
|
||||
continue;
|
||||
}
|
||||
if (!(mask & CD_TYPE_AS_MASK(type))) {
|
||||
continue;
|
||||
}
|
||||
@@ -2166,6 +2172,11 @@ bool CustomData_merge(const struct CustomData *source,
|
||||
newlayer->active_mask = lastmask;
|
||||
newlayer->flag |= flag & (CD_FLAG_EXTERNAL | CD_FLAG_IN_MEMORY);
|
||||
changed = true;
|
||||
|
||||
if (layer->anonymous_id != NULL) {
|
||||
BKE_anonymous_attribute_id_increment_weak(layer->anonymous_id);
|
||||
newlayer->anonymous_id = layer->anonymous_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2206,6 +2217,10 @@ static void customData_free_layer__internal(CustomDataLayer *layer, int totelem)
|
||||
{
|
||||
const LayerTypeInfo *typeInfo;
|
||||
|
||||
if (layer->anonymous_id != NULL) {
|
||||
BKE_anonymous_attribute_id_decrement_weak(layer->anonymous_id);
|
||||
layer->anonymous_id = NULL;
|
||||
}
|
||||
if (!(layer->flag & CD_FLAG_NOFREE) && layer->data) {
|
||||
typeInfo = layerType_getInfo(layer->type);
|
||||
|
||||
@@ -2649,6 +2664,27 @@ void *CustomData_add_layer_named(CustomData *data,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *CustomData_add_layer_anonymous(struct CustomData *data,
|
||||
int type,
|
||||
eCDAllocType alloctype,
|
||||
void *layerdata,
|
||||
int totelem,
|
||||
const AnonymousAttributeID *anonymous_id)
|
||||
{
|
||||
const char *name = BKE_anonymous_attribute_id_internal_name(anonymous_id);
|
||||
CustomDataLayer *layer = customData_add_layer__internal(
|
||||
data, type, alloctype, layerdata, totelem, name);
|
||||
CustomData_update_typemap(data);
|
||||
|
||||
if (layer == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BKE_anonymous_attribute_id_increment_weak(anonymous_id);
|
||||
layer->anonymous_id = anonymous_id;
|
||||
return layer->data;
|
||||
}
|
||||
|
||||
bool CustomData_free_layer(CustomData *data, int type, int totelem, int index)
|
||||
{
|
||||
const int index_first = CustomData_get_layer_index(data, type);
|
||||
@@ -2812,6 +2848,20 @@ 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 UNUSED(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 NULL;
|
||||
}
|
||||
|
||||
void CustomData_duplicate_referenced_layers(CustomData *data, int totelem)
|
||||
{
|
||||
for (int i = 0; i < data->totlayer; i++) {
|
||||
@@ -4244,7 +4294,8 @@ void CustomData_blend_write_prepare(CustomData *data,
|
||||
|
||||
for (i = 0, j = 0; i < totlayer; i++) {
|
||||
CustomDataLayer *layer = &data->layers[i];
|
||||
if (layer->flag & CD_FLAG_NOCOPY) { /* Layers with this flag set are not written to file. */
|
||||
/* Layers with this flag set are not written to file. */
|
||||
if ((layer->flag & CD_FLAG_NOCOPY) || layer->anonymous_id != NULL) {
|
||||
data->totlayer--;
|
||||
// CLOG_WARN(&LOG, "skipping layer %p (%s)", layer, layer->name);
|
||||
}
|
||||
|
@@ -892,7 +892,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
|
||||
|
||||
public:
|
||||
ReadAttributeLookup try_get_for_read(const GeometryComponent &component,
|
||||
const StringRef attribute_name) const final
|
||||
const AttributeIDRef &attribute_id) const final
|
||||
{
|
||||
const CurveEval *curve = get_curve_from_component_for_read(component);
|
||||
if (curve == nullptr || curve->splines().size() == 0) {
|
||||
@@ -902,13 +902,13 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
|
||||
Span<SplinePtr> splines = curve->splines();
|
||||
Vector<GSpan> spans; /* GSpan has no default constructor. */
|
||||
spans.reserve(splines.size());
|
||||
std::optional<GSpan> first_span = splines[0]->attributes.get_for_read(attribute_name);
|
||||
std::optional<GSpan> first_span = splines[0]->attributes.get_for_read(attribute_id);
|
||||
if (!first_span) {
|
||||
return {};
|
||||
}
|
||||
spans.append(*first_span);
|
||||
for (const int i : IndexRange(1, splines.size() - 1)) {
|
||||
std::optional<GSpan> span = splines[i]->attributes.get_for_read(attribute_name);
|
||||
std::optional<GSpan> span = splines[i]->attributes.get_for_read(attribute_id);
|
||||
if (!span) {
|
||||
/* All splines should have the same set of data layers. It would be possible to recover
|
||||
* here and return partial data instead, but that would add a lot of complexity for a
|
||||
@@ -945,7 +945,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
|
||||
|
||||
/* This function is almost the same as #try_get_for_read, but without const. */
|
||||
WriteAttributeLookup try_get_for_write(GeometryComponent &component,
|
||||
const StringRef attribute_name) const final
|
||||
const AttributeIDRef &attribute_id) const final
|
||||
{
|
||||
CurveEval *curve = get_curve_from_component_for_write(component);
|
||||
if (curve == nullptr || curve->splines().size() == 0) {
|
||||
@@ -955,13 +955,13 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
|
||||
MutableSpan<SplinePtr> splines = curve->splines();
|
||||
Vector<GMutableSpan> spans; /* GMutableSpan has no default constructor. */
|
||||
spans.reserve(splines.size());
|
||||
std::optional<GMutableSpan> first_span = splines[0]->attributes.get_for_write(attribute_name);
|
||||
std::optional<GMutableSpan> first_span = splines[0]->attributes.get_for_write(attribute_id);
|
||||
if (!first_span) {
|
||||
return {};
|
||||
}
|
||||
spans.append(*first_span);
|
||||
for (const int i : IndexRange(1, splines.size() - 1)) {
|
||||
std::optional<GMutableSpan> span = splines[i]->attributes.get_for_write(attribute_name);
|
||||
std::optional<GMutableSpan> span = splines[i]->attributes.get_for_write(attribute_id);
|
||||
if (!span) {
|
||||
/* All splines should have the same set of data layers. It would be possible to recover
|
||||
* here and return partial data instead, but that would add a lot of complexity for a
|
||||
@@ -996,7 +996,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
|
||||
return attribute;
|
||||
}
|
||||
|
||||
bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final
|
||||
bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final
|
||||
{
|
||||
CurveEval *curve = get_curve_from_component_for_write(component);
|
||||
if (curve == nullptr) {
|
||||
@@ -1006,7 +1006,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
|
||||
/* Reuse the boolean for all splines; we expect all splines to have the same attributes. */
|
||||
bool layer_freed = false;
|
||||
for (SplinePtr &spline : curve->splines()) {
|
||||
layer_freed = spline->attributes.remove(attribute_name);
|
||||
layer_freed = spline->attributes.remove(attribute_id);
|
||||
}
|
||||
return layer_freed;
|
||||
}
|
||||
@@ -1034,7 +1034,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
|
||||
}
|
||||
|
||||
bool try_create(GeometryComponent &component,
|
||||
const StringRef attribute_name,
|
||||
const AttributeIDRef &attribute_id,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const AttributeInit &initializer) const final
|
||||
@@ -1053,7 +1053,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
|
||||
/* First check the one case that allows us to avoid copying the input data. */
|
||||
if (splines.size() == 1 && initializer.type == AttributeInit::Type::MoveArray) {
|
||||
void *source_data = static_cast<const AttributeInitMove &>(initializer).data;
|
||||
if (!splines[0]->attributes.create_by_move(attribute_name, data_type, source_data)) {
|
||||
if (!splines[0]->attributes.create_by_move(attribute_id, data_type, source_data)) {
|
||||
MEM_freeN(source_data);
|
||||
return false;
|
||||
}
|
||||
@@ -1062,7 +1062,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
|
||||
|
||||
/* Otherwise just create a custom data layer on each of the splines. */
|
||||
for (const int i : splines.index_range()) {
|
||||
if (!splines[i]->attributes.create(attribute_name, data_type)) {
|
||||
if (!splines[i]->attributes.create(attribute_id, data_type)) {
|
||||
/* If attribute creation fails on one of the splines, we cannot leave the custom data
|
||||
* layers in the previous splines around, so delete them before returning. However,
|
||||
* this is not an expected case. */
|
||||
@@ -1076,7 +1076,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
|
||||
return true;
|
||||
}
|
||||
|
||||
WriteAttributeLookup write_attribute = this->try_get_for_write(component, attribute_name);
|
||||
WriteAttributeLookup write_attribute = this->try_get_for_write(component, attribute_id);
|
||||
/* We just created the attribute, it should exist. */
|
||||
BLI_assert(write_attribute);
|
||||
|
||||
|
@@ -818,16 +818,20 @@ class VArray_For_VertexWeights final : public VArray<float> {
|
||||
class VertexGroupsAttributeProvider final : public DynamicAttributesProvider {
|
||||
public:
|
||||
ReadAttributeLookup try_get_for_read(const GeometryComponent &component,
|
||||
const StringRef attribute_name) const final
|
||||
const AttributeIDRef &attribute_id) const final
|
||||
{
|
||||
BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH);
|
||||
if (!attribute_id.is_named()) {
|
||||
return {};
|
||||
}
|
||||
const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component);
|
||||
const Mesh *mesh = mesh_component.get_for_read();
|
||||
if (mesh == nullptr) {
|
||||
return {};
|
||||
}
|
||||
const std::string name = attribute_id.name();
|
||||
const int vertex_group_index = BLI_findstringindex(
|
||||
&mesh->vertex_group_names, attribute_name.data(), offsetof(bDeformGroup, name));
|
||||
&mesh->vertex_group_names, name.c_str(), offsetof(bDeformGroup, name));
|
||||
if (vertex_group_index < 0) {
|
||||
return {};
|
||||
}
|
||||
@@ -843,17 +847,21 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider {
|
||||
}
|
||||
|
||||
WriteAttributeLookup try_get_for_write(GeometryComponent &component,
|
||||
const StringRef attribute_name) const final
|
||||
const AttributeIDRef &attribute_id) const final
|
||||
{
|
||||
BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH);
|
||||
if (!attribute_id.is_named()) {
|
||||
return {};
|
||||
}
|
||||
MeshComponent &mesh_component = static_cast<MeshComponent &>(component);
|
||||
Mesh *mesh = mesh_component.get_for_write();
|
||||
if (mesh == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::string name = attribute_id.name();
|
||||
const int vertex_group_index = BLI_findstringindex(
|
||||
&mesh->vertex_group_names, attribute_name.data(), offsetof(bDeformGroup, name));
|
||||
&mesh->vertex_group_names, name.c_str(), offsetof(bDeformGroup, name));
|
||||
if (vertex_group_index < 0) {
|
||||
return {};
|
||||
}
|
||||
@@ -872,17 +880,21 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider {
|
||||
ATTR_DOMAIN_POINT};
|
||||
}
|
||||
|
||||
bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final
|
||||
bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final
|
||||
{
|
||||
BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH);
|
||||
if (!attribute_id.is_named()) {
|
||||
return false;
|
||||
}
|
||||
MeshComponent &mesh_component = static_cast<MeshComponent &>(component);
|
||||
Mesh *mesh = mesh_component.get_for_write();
|
||||
if (mesh == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string name = attribute_id.name();
|
||||
const int vertex_group_index = BLI_findstringindex(
|
||||
&mesh->vertex_group_names, attribute_name.data(), offsetof(bDeformGroup, name));
|
||||
&mesh->vertex_group_names, name.c_str(), offsetof(bDeformGroup, name));
|
||||
if (vertex_group_index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
@@ -331,7 +331,7 @@ void geometry_set_instances_attribute_foreach(const GeometrySet &geometry_set,
|
||||
void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> set_groups,
|
||||
Span<GeometryComponentType> component_types,
|
||||
const Set<std::string> &ignored_attributes,
|
||||
Map<std::string, AttributeKind> &r_attributes)
|
||||
Map<AttributeIDRef, AttributeKind> &r_attributes)
|
||||
{
|
||||
for (const GeometryInstanceGroup &set_group : set_groups) {
|
||||
const GeometrySet &set = set_group.geometry_set;
|
||||
@@ -341,23 +341,24 @@ void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> se
|
||||
}
|
||||
const GeometryComponent &component = *set.get_component_for_read(component_type);
|
||||
|
||||
component.attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
|
||||
if (ignored_attributes.contains(name)) {
|
||||
return true;
|
||||
}
|
||||
auto add_info = [&](AttributeKind *attribute_kind) {
|
||||
attribute_kind->domain = meta_data.domain;
|
||||
attribute_kind->data_type = meta_data.data_type;
|
||||
};
|
||||
auto modify_info = [&](AttributeKind *attribute_kind) {
|
||||
attribute_kind->domain = meta_data.domain; /* TODO: Use highest priority domain. */
|
||||
attribute_kind->data_type = bke::attribute_data_type_highest_complexity(
|
||||
{attribute_kind->data_type, meta_data.data_type});
|
||||
};
|
||||
component.attribute_foreach(
|
||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
|
||||
if (attribute_id.is_named() && ignored_attributes.contains(attribute_id.name())) {
|
||||
return true;
|
||||
}
|
||||
auto add_info = [&](AttributeKind *attribute_kind) {
|
||||
attribute_kind->domain = meta_data.domain;
|
||||
attribute_kind->data_type = meta_data.data_type;
|
||||
};
|
||||
auto modify_info = [&](AttributeKind *attribute_kind) {
|
||||
attribute_kind->domain = meta_data.domain; /* TODO: Use highest priority domain. */
|
||||
attribute_kind->data_type = bke::attribute_data_type_highest_complexity(
|
||||
{attribute_kind->data_type, meta_data.data_type});
|
||||
};
|
||||
|
||||
r_attributes.add_or_modify(name, add_info, modify_info);
|
||||
return true;
|
||||
});
|
||||
r_attributes.add_or_modify(attribute_id, add_info, modify_info);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -512,11 +513,11 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou
|
||||
|
||||
static void join_attributes(Span<GeometryInstanceGroup> set_groups,
|
||||
Span<GeometryComponentType> component_types,
|
||||
const Map<std::string, AttributeKind> &attribute_info,
|
||||
const Map<AttributeIDRef, AttributeKind> &attribute_info,
|
||||
GeometryComponent &result)
|
||||
{
|
||||
for (Map<std::string, AttributeKind>::Item entry : attribute_info.items()) {
|
||||
StringRef name = entry.key;
|
||||
for (Map<AttributeIDRef, AttributeKind>::Item entry : attribute_info.items()) {
|
||||
const AttributeIDRef attribute_id = entry.key;
|
||||
const AttributeDomain domain_output = entry.value.domain;
|
||||
const CustomDataType data_type_output = entry.value.data_type;
|
||||
const CPPType *cpp_type = bke::custom_data_type_to_cpp_type(data_type_output);
|
||||
@@ -524,7 +525,7 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups,
|
||||
|
||||
result.attribute_try_create(
|
||||
entry.key, domain_output, data_type_output, AttributeInitDefault());
|
||||
WriteAttributeLookup write_attribute = result.attribute_try_get_for_write(name);
|
||||
WriteAttributeLookup write_attribute = result.attribute_try_get_for_write(attribute_id);
|
||||
if (!write_attribute || &write_attribute.varray->type() != cpp_type ||
|
||||
write_attribute.domain != domain_output) {
|
||||
continue;
|
||||
@@ -543,7 +544,7 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups,
|
||||
continue; /* Domain size is 0, so no need to increment the offset. */
|
||||
}
|
||||
GVArrayPtr source_attribute = component.attribute_try_get_for_read(
|
||||
name, domain_output, data_type_output);
|
||||
attribute_id, domain_output, data_type_output);
|
||||
|
||||
if (source_attribute) {
|
||||
fn::GVArray_GSpan src_span{*source_attribute};
|
||||
@@ -653,7 +654,7 @@ static void join_instance_groups_mesh(Span<GeometryInstanceGroup> set_groups,
|
||||
}
|
||||
|
||||
/* Don't copy attributes that are stored directly in the mesh data structs. */
|
||||
Map<std::string, AttributeKind> attributes;
|
||||
Map<AttributeIDRef, AttributeKind> attributes;
|
||||
geometry_set_gather_instances_attribute_info(
|
||||
set_groups,
|
||||
component_types,
|
||||
@@ -674,7 +675,7 @@ static void join_instance_groups_pointcloud(Span<GeometryInstanceGroup> set_grou
|
||||
PointCloudComponent &dst_component = result.get_component_for_write<PointCloudComponent>();
|
||||
dst_component.replace(new_pointcloud);
|
||||
|
||||
Map<std::string, AttributeKind> attributes;
|
||||
Map<AttributeIDRef, AttributeKind> attributes;
|
||||
geometry_set_gather_instances_attribute_info(
|
||||
set_groups, {GEO_COMPONENT_TYPE_POINT_CLOUD}, {"position"}, attributes);
|
||||
join_attributes(set_groups,
|
||||
@@ -708,7 +709,7 @@ static void join_instance_groups_curve(Span<GeometryInstanceGroup> set_groups, G
|
||||
CurveComponent &dst_component = result.get_component_for_write<CurveComponent>();
|
||||
dst_component.replace(curve);
|
||||
|
||||
Map<std::string, AttributeKind> attributes;
|
||||
Map<AttributeIDRef, AttributeKind> attributes;
|
||||
geometry_set_gather_instances_attribute_info(
|
||||
set_groups,
|
||||
{GEO_COMPONENT_TYPE_CURVE},
|
||||
|
@@ -5140,6 +5140,7 @@ static void registerGeometryNodes()
|
||||
register_node_type_geo_attribute_convert();
|
||||
register_node_type_geo_attribute_curve_map();
|
||||
register_node_type_geo_attribute_fill();
|
||||
register_node_type_geo_attribute_capture();
|
||||
register_node_type_geo_attribute_map_range();
|
||||
register_node_type_geo_attribute_math();
|
||||
register_node_type_geo_attribute_mix();
|
||||
@@ -5174,7 +5175,10 @@ static void registerGeometryNodes()
|
||||
register_node_type_geo_curve_trim();
|
||||
register_node_type_geo_delete_geometry();
|
||||
register_node_type_geo_edge_split();
|
||||
register_node_type_geo_input_index();
|
||||
register_node_type_geo_input_material();
|
||||
register_node_type_geo_input_normal();
|
||||
register_node_type_geo_input_position();
|
||||
register_node_type_geo_is_viewport();
|
||||
register_node_type_geo_join_geometry();
|
||||
register_node_type_geo_material_assign();
|
||||
@@ -5202,6 +5206,7 @@ static void registerGeometryNodes()
|
||||
register_node_type_geo_select_by_handle_type();
|
||||
register_node_type_geo_select_by_material();
|
||||
register_node_type_geo_separate_components();
|
||||
register_node_type_geo_set_position();
|
||||
register_node_type_geo_subdivision_surface();
|
||||
register_node_type_geo_switch();
|
||||
register_node_type_geo_transform();
|
||||
|
@@ -45,15 +45,20 @@ namespace blender::ed::spreadsheet {
|
||||
void GeometryDataSource::foreach_default_column_ids(
|
||||
FunctionRef<void(const SpreadsheetColumnID &)> fn) const
|
||||
{
|
||||
component_->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
|
||||
if (meta_data.domain != domain_) {
|
||||
return true;
|
||||
}
|
||||
SpreadsheetColumnID column_id;
|
||||
column_id.name = (char *)name.c_str();
|
||||
fn(column_id);
|
||||
return true;
|
||||
});
|
||||
component_->attribute_foreach(
|
||||
[&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
|
||||
if (meta_data.domain != domain_) {
|
||||
return true;
|
||||
}
|
||||
if (attribute_id.is_anonymous()) {
|
||||
return true;
|
||||
}
|
||||
SpreadsheetColumnID column_id;
|
||||
std::string name = attribute_id.name();
|
||||
column_id.name = (char *)name.c_str();
|
||||
fn(column_id);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values(
|
||||
|
@@ -28,14 +28,20 @@ set(INC_SYS
|
||||
|
||||
set(SRC
|
||||
intern/cpp_types.cc
|
||||
intern/field.cc
|
||||
intern/generic_vector_array.cc
|
||||
intern/generic_virtual_array.cc
|
||||
intern/generic_virtual_vector_array.cc
|
||||
intern/multi_function.cc
|
||||
intern/multi_function_builder.cc
|
||||
intern/multi_function_procedure.cc
|
||||
intern/multi_function_procedure_builder.cc
|
||||
intern/multi_function_procedure_executor.cc
|
||||
|
||||
FN_cpp_type.hh
|
||||
FN_cpp_type_make.hh
|
||||
FN_field.hh
|
||||
FN_field_cpp_type.hh
|
||||
FN_generic_pointer.hh
|
||||
FN_generic_span.hh
|
||||
FN_generic_value_map.hh
|
||||
@@ -48,6 +54,9 @@ set(SRC
|
||||
FN_multi_function_data_type.hh
|
||||
FN_multi_function_param_type.hh
|
||||
FN_multi_function_params.hh
|
||||
FN_multi_function_procedure.hh
|
||||
FN_multi_function_procedure_builder.hh
|
||||
FN_multi_function_procedure_executor.hh
|
||||
FN_multi_function_signature.hh
|
||||
)
|
||||
|
||||
@@ -60,8 +69,10 @@ blender_add_lib(bf_functions "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
if(WITH_GTESTS)
|
||||
set(TEST_SRC
|
||||
tests/FN_cpp_type_test.cc
|
||||
tests/FN_field_test.cc
|
||||
tests/FN_generic_span_test.cc
|
||||
tests/FN_generic_vector_array_test.cc
|
||||
tests/FN_multi_function_procedure_test.cc
|
||||
tests/FN_multi_function_test.cc
|
||||
)
|
||||
set(TEST_LIB
|
||||
|
456
source/blender/functions/FN_field.hh
Normal file
456
source/blender/functions/FN_field.hh
Normal file
@@ -0,0 +1,456 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup fn
|
||||
*
|
||||
* A #Field represents a function that outputs a value based on an arbitrary number of inputs. The
|
||||
* inputs for a specific field evaluation are provided by a #FieldContext.
|
||||
*
|
||||
* A typical example is a field that computes a displacement vector for every vertex on a mesh
|
||||
* based on its position.
|
||||
*
|
||||
* Fields can be build, composed and evaluated at run-time. They are stored in a directed tree
|
||||
* graph data structure, whereby each node is a #FieldNode and edges are dependencies. A #FieldNode
|
||||
* has an arbitrary number of inputs and at least one output and a #Field references a specific
|
||||
* output of a #FieldNode. The inputs of a #FieldNode are other fields.
|
||||
*
|
||||
* There are two different types of field nodes:
|
||||
* - #FieldInput: Has no input and exactly one output. It represents an input to the entire field
|
||||
* when it is evaluated. During evaluation, the value of this input is based on a #FieldContext.
|
||||
* - #FieldOperation: Has an arbitrary number of field inputs and at least one output. Its main
|
||||
* use is to compose multiple existing fields into new fields.
|
||||
*
|
||||
* When fields are evaluated, they are converted into a multi-function procedure which allows
|
||||
* efficient compution. In the future, we might support different field evaluation mechanisms for
|
||||
* e.g. the following scenarios:
|
||||
* - Latency of a single evaluation is more important than throughput.
|
||||
* - Evaluation should happen on other hardware like GPUs.
|
||||
*
|
||||
* Whenever possible, multiple fields should be evaluated together to avoid duplicate work when
|
||||
* they share common sub-fields and a common context.
|
||||
*/
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "FN_generic_virtual_array.hh"
|
||||
#include "FN_multi_function_builder.hh"
|
||||
#include "FN_multi_function_procedure.hh"
|
||||
#include "FN_multi_function_procedure_builder.hh"
|
||||
#include "FN_multi_function_procedure_executor.hh"
|
||||
|
||||
namespace blender::fn {
|
||||
|
||||
/**
|
||||
* A node in a field-tree. It has at least one output that can be referenced by fields.
|
||||
*/
|
||||
class FieldNode {
|
||||
private:
|
||||
bool is_input_;
|
||||
|
||||
public:
|
||||
FieldNode(bool is_input) : is_input_(is_input)
|
||||
{
|
||||
}
|
||||
|
||||
~FieldNode() = default;
|
||||
|
||||
virtual const CPPType &output_cpp_type(int output_index) const = 0;
|
||||
|
||||
bool is_input() const
|
||||
{
|
||||
return is_input_;
|
||||
}
|
||||
|
||||
bool is_operation() const
|
||||
{
|
||||
return !is_input_;
|
||||
}
|
||||
|
||||
virtual uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(this);
|
||||
}
|
||||
|
||||
friend bool operator==(const FieldNode &a, const FieldNode &b)
|
||||
{
|
||||
return a.is_equal_to(b);
|
||||
}
|
||||
|
||||
virtual bool is_equal_to(const FieldNode &other) const
|
||||
{
|
||||
return this == &other;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Common base class for fields to avoid declaring the same methods for #GField and #GFieldRef.
|
||||
*/
|
||||
template<typename NodePtr> class GFieldBase {
|
||||
protected:
|
||||
NodePtr node_ = nullptr;
|
||||
int node_output_index_ = 0;
|
||||
|
||||
GFieldBase(NodePtr node, const int node_output_index)
|
||||
: node_(std::move(node)), node_output_index_(node_output_index)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
GFieldBase() = default;
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return node_ != nullptr;
|
||||
}
|
||||
|
||||
friend bool operator==(const GFieldBase &a, const GFieldBase &b)
|
||||
{
|
||||
return &*a.node_ == &*b.node_ && a.node_output_index_ == b.node_output_index_;
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash_2(node_, node_output_index_);
|
||||
}
|
||||
|
||||
const fn::CPPType &cpp_type() const
|
||||
{
|
||||
return node_->output_cpp_type(node_output_index_);
|
||||
}
|
||||
|
||||
const FieldNode &node() const
|
||||
{
|
||||
return *node_;
|
||||
}
|
||||
|
||||
int node_output_index() const
|
||||
{
|
||||
return node_output_index_;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A field whose output type is only known at run-time.
|
||||
*/
|
||||
class GField : public GFieldBase<std::shared_ptr<FieldNode>> {
|
||||
public:
|
||||
GField() = default;
|
||||
|
||||
GField(std::shared_ptr<FieldNode> node, const int node_output_index = 0)
|
||||
: GFieldBase<std::shared_ptr<FieldNode>>(std::move(node), node_output_index)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as #GField but is cheaper to copy/move around, because it does not contain a
|
||||
* #std::shared_ptr.
|
||||
*/
|
||||
class GFieldRef : public GFieldBase<const FieldNode *> {
|
||||
public:
|
||||
GFieldRef() = default;
|
||||
|
||||
GFieldRef(const GField &field)
|
||||
: GFieldBase<const FieldNode *>(&field.node(), field.node_output_index())
|
||||
{
|
||||
}
|
||||
|
||||
GFieldRef(const FieldNode &node, const int node_output_index = 0)
|
||||
: GFieldBase<const FieldNode *>(&node, node_output_index)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A typed version of #GField. It has the same memory layout as #GField.
|
||||
*/
|
||||
template<typename T> class Field : public GField {
|
||||
public:
|
||||
Field() = default;
|
||||
|
||||
Field(GField field) : GField(std::move(field))
|
||||
{
|
||||
BLI_assert(this->cpp_type().template is<T>());
|
||||
}
|
||||
|
||||
Field(std::shared_ptr<FieldNode> node, const int node_output_index = 0)
|
||||
: Field(GField(std::move(node), node_output_index))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A #FieldNode that allows composing existing fields into new fields.
|
||||
*/
|
||||
class FieldOperation : public FieldNode {
|
||||
/**
|
||||
* The multi-function used by this node. It is optionally owned.
|
||||
* Multi-functions with mutable or vector parameters are not supported currently.
|
||||
*/
|
||||
std::unique_ptr<const MultiFunction> owned_function_;
|
||||
const MultiFunction *function_;
|
||||
|
||||
/** Inputs to the operation. */
|
||||
blender::Vector<GField> inputs_;
|
||||
|
||||
public:
|
||||
FieldOperation(std::unique_ptr<const MultiFunction> function, Vector<GField> inputs = {})
|
||||
: FieldNode(false), owned_function_(std::move(function)), inputs_(std::move(inputs))
|
||||
{
|
||||
function_ = owned_function_.get();
|
||||
}
|
||||
|
||||
FieldOperation(const MultiFunction &function, Vector<GField> inputs = {})
|
||||
: FieldNode(false), function_(&function), inputs_(std::move(inputs))
|
||||
{
|
||||
}
|
||||
|
||||
Span<GField> inputs() const
|
||||
{
|
||||
return inputs_;
|
||||
}
|
||||
|
||||
const MultiFunction &multi_function() const
|
||||
{
|
||||
return *function_;
|
||||
}
|
||||
|
||||
const CPPType &output_cpp_type(int output_index) const override
|
||||
{
|
||||
int output_counter = 0;
|
||||
for (const int param_index : function_->param_indices()) {
|
||||
MFParamType param_type = function_->param_type(param_index);
|
||||
if (param_type.is_output()) {
|
||||
if (output_counter == output_index) {
|
||||
return param_type.data_type().single_type();
|
||||
}
|
||||
output_counter++;
|
||||
}
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
return CPPType::get<float>();
|
||||
}
|
||||
};
|
||||
|
||||
class FieldContext;
|
||||
|
||||
/**
|
||||
* A #FieldNode that represents an input to the entire field-tree.
|
||||
*/
|
||||
class FieldInput : public FieldNode {
|
||||
protected:
|
||||
const CPPType *type_;
|
||||
std::string debug_name_;
|
||||
|
||||
public:
|
||||
FieldInput(const CPPType &type, std::string debug_name = "")
|
||||
: FieldNode(true), type_(&type), debug_name_(std::move(debug_name))
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of this specific input based on the given context. The returned virtual array,
|
||||
* should live at least as long as the passed in #scope. May return null.
|
||||
*/
|
||||
virtual const GVArray *get_varray_for_context(const FieldContext &context,
|
||||
IndexMask mask,
|
||||
ResourceScope &scope) const = 0;
|
||||
|
||||
blender::StringRef debug_name() const
|
||||
{
|
||||
return debug_name_;
|
||||
}
|
||||
|
||||
const CPPType &cpp_type() const
|
||||
{
|
||||
return *type_;
|
||||
}
|
||||
|
||||
const CPPType &output_cpp_type(int output_index) const override
|
||||
{
|
||||
BLI_assert(output_index == 0);
|
||||
UNUSED_VARS_NDEBUG(output_index);
|
||||
return *type_;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides inputs for a specific field evaluation.
|
||||
*/
|
||||
class FieldContext {
|
||||
public:
|
||||
~FieldContext() = default;
|
||||
|
||||
virtual const GVArray *get_varray_for_input(const FieldInput &field_input,
|
||||
IndexMask mask,
|
||||
ResourceScope &scope) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility class that makes it easier to evaluate fields.
|
||||
*/
|
||||
class FieldEvaluator : NonMovable, NonCopyable {
|
||||
private:
|
||||
struct OutputPointerInfo {
|
||||
void *dst = nullptr;
|
||||
/* When a destination virtual array is provided for an input, this is
|
||||
* unnecessary, otherwise this is used to construct the required virtual array. */
|
||||
void (*set)(void *dst, const GVArray &varray, ResourceScope &scope) = nullptr;
|
||||
};
|
||||
|
||||
ResourceScope scope_;
|
||||
const FieldContext &context_;
|
||||
const IndexMask mask_;
|
||||
Vector<GField> fields_to_evaluate_;
|
||||
Vector<GVMutableArray *> dst_varrays_;
|
||||
Vector<const GVArray *> evaluated_varrays_;
|
||||
Vector<OutputPointerInfo> output_pointer_infos_;
|
||||
bool is_evaluated_ = false;
|
||||
|
||||
public:
|
||||
/** Takes #mask by pointer because the mask has to live longer than the evaluator. */
|
||||
FieldEvaluator(const FieldContext &context, const IndexMask *mask)
|
||||
: context_(context), mask_(*mask)
|
||||
{
|
||||
}
|
||||
|
||||
/** Construct a field evaluator for all indices less than #size. */
|
||||
FieldEvaluator(const FieldContext &context, const int64_t size) : context_(context), mask_(size)
|
||||
{
|
||||
}
|
||||
|
||||
~FieldEvaluator()
|
||||
{
|
||||
/* While this assert isn't strictly necessary, and could be replaced with a warning,
|
||||
* it will catch cases where someone forgets to call #evaluate(). */
|
||||
BLI_assert(is_evaluated_);
|
||||
}
|
||||
|
||||
/**
|
||||
* \param field: Field to add to the evaluator.
|
||||
* \param dst: Mutable virtual array that the evaluated result for this field is be written into.
|
||||
*/
|
||||
int add_with_destination(GField field, GVMutableArray &dst);
|
||||
|
||||
/** Same as #add_with_destination but typed. */
|
||||
template<typename T> int add_with_destination(Field<T> field, VMutableArray<T> &dst)
|
||||
{
|
||||
GVMutableArray &varray = scope_.construct<GVMutableArray_For_VMutableArray<T>>(__func__, dst);
|
||||
return this->add_with_destination(GField(std::move(field)), varray);
|
||||
}
|
||||
|
||||
/**
|
||||
* \param field: Field to add to the evaluator.
|
||||
* \param dst: Mutable span that the evaluated result for this field is be written into.
|
||||
* \note: When the output may only be used as a single value, the version of this function with
|
||||
* a virtual array result array should be used.
|
||||
*/
|
||||
int add_with_destination(GField field, GMutableSpan dst);
|
||||
|
||||
/**
|
||||
* \param field: Field to add to the evaluator.
|
||||
* \param dst: Mutable span that the evaluated result for this field is be written into.
|
||||
* \note: When the output may only be used as a single value, the version of this function with
|
||||
* a virtual array result array should be used.
|
||||
*/
|
||||
template<typename T> int add_with_destination(Field<T> field, MutableSpan<T> dst)
|
||||
{
|
||||
GVMutableArray &varray = scope_.construct<GVMutableArray_For_MutableSpan<T>>(__func__, dst);
|
||||
return this->add_with_destination(std::move(field), varray);
|
||||
}
|
||||
|
||||
int add(GField field, const GVArray **varray_ptr);
|
||||
|
||||
/**
|
||||
* \param field: Field to add to the evaluator.
|
||||
* \param varray_ptr: Once #evaluate is called, the resulting virtual array will be will be
|
||||
* assigned to the given position.
|
||||
* \return Index of the field in the evaluator which can be used in the #get_evaluated methods.
|
||||
*/
|
||||
template<typename T> int add(Field<T> field, const VArray<T> **varray_ptr)
|
||||
{
|
||||
const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field));
|
||||
dst_varrays_.append(nullptr);
|
||||
output_pointer_infos_.append(OutputPointerInfo{
|
||||
varray_ptr, [](void *dst, const GVArray &varray, ResourceScope &scope) {
|
||||
*(const VArray<T> **)dst = &*scope.construct<GVArray_Typed<T>>(__func__, varray);
|
||||
}});
|
||||
return field_index;
|
||||
}
|
||||
|
||||
/**
|
||||
* \return Index of the field in the evaluator which can be used in the #get_evaluated methods.
|
||||
*/
|
||||
int add(GField field);
|
||||
|
||||
/**
|
||||
* Evaluate all fields on the evaluator. This can only be called once.
|
||||
*/
|
||||
void evaluate();
|
||||
|
||||
const GVArray &get_evaluated(const int field_index) const
|
||||
{
|
||||
BLI_assert(is_evaluated_);
|
||||
return *evaluated_varrays_[field_index];
|
||||
}
|
||||
|
||||
template<typename T> const VArray<T> &get_evaluated(const int field_index)
|
||||
{
|
||||
const GVArray &varray = this->get_evaluated(field_index);
|
||||
GVArray_Typed<T> &typed_varray = scope_.construct<GVArray_Typed<T>>(__func__, varray);
|
||||
return *typed_varray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the output of an evaluated boolean field and convert it to a mask, which can be used
|
||||
* to avoid calculations for unnecessary elements later on. The evaluator will own the indices in
|
||||
* some cases, so it must live at least as long as the returned mask.
|
||||
*/
|
||||
IndexMask get_evaluated_as_mask(const int field_index);
|
||||
};
|
||||
|
||||
Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
|
||||
Span<GFieldRef> fields_to_evaluate,
|
||||
IndexMask mask,
|
||||
const FieldContext &context,
|
||||
Span<GVMutableArray *> dst_varrays = {});
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Utility functions for simple field creation and evaluation.
|
||||
*/
|
||||
|
||||
void evaluate_constant_field(const GField &field, void *r_value);
|
||||
|
||||
template<typename T> T evaluate_constant_field(const Field<T> &field)
|
||||
{
|
||||
T value;
|
||||
value.~T();
|
||||
evaluate_constant_field(field, &value);
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename T> Field<T> make_constant_field(T value)
|
||||
{
|
||||
auto constant_fn = std::make_unique<fn::CustomMF_Constant<T>>(std::forward<T>(value));
|
||||
auto operation = std::make_shared<FieldOperation>(std::move(constant_fn));
|
||||
return Field<T>{GField{std::move(operation), 0}};
|
||||
}
|
||||
|
||||
} // namespace blender::fn
|
72
source/blender/functions/FN_field_cpp_type.hh
Normal file
72
source/blender/functions/FN_field_cpp_type.hh
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup fn
|
||||
*/
|
||||
|
||||
#include "FN_cpp_type_make.hh"
|
||||
#include "FN_field.hh"
|
||||
|
||||
namespace blender::fn {
|
||||
|
||||
template<typename T> struct FieldCPPTypeParam {
|
||||
};
|
||||
|
||||
class FieldCPPType : public CPPType {
|
||||
private:
|
||||
const CPPType &field_type_;
|
||||
|
||||
public:
|
||||
template<typename T>
|
||||
FieldCPPType(FieldCPPTypeParam<Field<T>> /* unused */, StringRef debug_name)
|
||||
: CPPType(CPPTypeParam<Field<T>, CPPTypeFlags::None>(), debug_name),
|
||||
field_type_(CPPType::get<T>())
|
||||
{
|
||||
}
|
||||
|
||||
const CPPType &field_type() const
|
||||
{
|
||||
return field_type_;
|
||||
}
|
||||
|
||||
/* Ensure that #GField and #Field<T> have the same layout, to enable casting between the two. */
|
||||
static_assert(sizeof(Field<int>) == sizeof(GField));
|
||||
static_assert(sizeof(Field<int>) == sizeof(Field<std::string>));
|
||||
|
||||
const GField &get_gfield(const void *field) const
|
||||
{
|
||||
return *(const GField *)field;
|
||||
}
|
||||
|
||||
void construct_from_gfield(void *r_value, const GField &gfield) const
|
||||
{
|
||||
new (r_value) GField(gfield);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::fn
|
||||
|
||||
#define MAKE_FIELD_CPP_TYPE(DEBUG_NAME, FIELD_TYPE) \
|
||||
template<> \
|
||||
const blender::fn::CPPType &blender::fn::CPPType::get_impl<blender::fn::Field<FIELD_TYPE>>() \
|
||||
{ \
|
||||
static blender::fn::FieldCPPType cpp_type{ \
|
||||
blender::fn::FieldCPPTypeParam<blender::fn::Field<FIELD_TYPE>>(), STRINGIFY(DEBUG_NAME)}; \
|
||||
return cpp_type; \
|
||||
}
|
@@ -417,4 +417,13 @@ class CustomMF_DefaultOutput : public MultiFunction {
|
||||
void call(IndexMask mask, MFParams params, MFContext context) const override;
|
||||
};
|
||||
|
||||
class CustomMF_GenericCopy : public MultiFunction {
|
||||
private:
|
||||
MFSignature signature_;
|
||||
|
||||
public:
|
||||
CustomMF_GenericCopy(StringRef name, MFDataType data_type);
|
||||
void call(IndexMask mask, MFParams params, MFContext context) const override;
|
||||
};
|
||||
|
||||
} // namespace blender::fn
|
||||
|
@@ -195,7 +195,7 @@ class MFParams {
|
||||
template<typename T> const VArray<T> &readonly_single_input(int param_index, StringRef name = "")
|
||||
{
|
||||
const GVArray &array = this->readonly_single_input(param_index, name);
|
||||
return builder_->scope_.construct<VArray_For_GVArray<T>>(__func__, array);
|
||||
return builder_->scope_.construct<GVArray_Typed<T>>(__func__, array);
|
||||
}
|
||||
const GVArray &readonly_single_input(int param_index, StringRef name = "")
|
||||
{
|
||||
|
452
source/blender/functions/FN_multi_function_procedure.hh
Normal file
452
source/blender/functions/FN_multi_function_procedure.hh
Normal file
@@ -0,0 +1,452 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup fn
|
||||
*/
|
||||
|
||||
#include "FN_multi_function.hh"
|
||||
|
||||
namespace blender::fn {
|
||||
|
||||
class MFVariable;
|
||||
class MFInstruction;
|
||||
class MFCallInstruction;
|
||||
class MFBranchInstruction;
|
||||
class MFDestructInstruction;
|
||||
class MFDummyInstruction;
|
||||
class MFReturnInstruction;
|
||||
class MFProcedure;
|
||||
|
||||
/** Every instruction has exactly one of these types. */
|
||||
enum class MFInstructionType {
|
||||
Call,
|
||||
Branch,
|
||||
Destruct,
|
||||
Dummy,
|
||||
Return,
|
||||
};
|
||||
|
||||
/**
|
||||
* A variable is similar to a virtual register in other libraries. During evaluation, every is
|
||||
* either uninitialized or contains a value for every index (remember, a multi-function procedure
|
||||
* is always evaluated for many indices at the same time).
|
||||
*/
|
||||
class MFVariable : NonCopyable, NonMovable {
|
||||
private:
|
||||
MFDataType data_type_;
|
||||
Vector<MFInstruction *> users_;
|
||||
std::string name_;
|
||||
int id_;
|
||||
|
||||
friend MFProcedure;
|
||||
friend MFCallInstruction;
|
||||
friend MFBranchInstruction;
|
||||
friend MFDestructInstruction;
|
||||
|
||||
public:
|
||||
MFDataType data_type() const;
|
||||
Span<MFInstruction *> users();
|
||||
|
||||
StringRefNull name() const;
|
||||
void set_name(std::string name);
|
||||
|
||||
int id() const;
|
||||
};
|
||||
|
||||
/** Base class for all instruction types. */
|
||||
class MFInstruction : NonCopyable, NonMovable {
|
||||
protected:
|
||||
MFInstructionType type_;
|
||||
Vector<MFInstruction *> prev_;
|
||||
|
||||
friend MFProcedure;
|
||||
friend MFCallInstruction;
|
||||
friend MFBranchInstruction;
|
||||
friend MFDestructInstruction;
|
||||
friend MFDummyInstruction;
|
||||
friend MFReturnInstruction;
|
||||
|
||||
public:
|
||||
MFInstructionType type() const;
|
||||
|
||||
/**
|
||||
* Other instructions that come before this instruction. There can be multiple previous
|
||||
* instructions when branching is used in the procedure.
|
||||
*/
|
||||
Span<MFInstruction *> prev();
|
||||
Span<const MFInstruction *> prev() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* References a multi-function that is evaluated when the instruction is executed. It also
|
||||
* references the variables whose data will be passed into the multi-function.
|
||||
*/
|
||||
class MFCallInstruction : public MFInstruction {
|
||||
private:
|
||||
const MultiFunction *fn_ = nullptr;
|
||||
MFInstruction *next_ = nullptr;
|
||||
MutableSpan<MFVariable *> params_;
|
||||
|
||||
friend MFProcedure;
|
||||
|
||||
public:
|
||||
const MultiFunction &fn() const;
|
||||
|
||||
MFInstruction *next();
|
||||
const MFInstruction *next() const;
|
||||
void set_next(MFInstruction *instruction);
|
||||
|
||||
void set_param_variable(int param_index, MFVariable *variable);
|
||||
void set_params(Span<MFVariable *> variables);
|
||||
|
||||
Span<MFVariable *> params();
|
||||
Span<const MFVariable *> params() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* What makes a branch instruction special is that it has two successor instructions. One that will
|
||||
* be used when a condition variable was true, and one otherwise.
|
||||
*/
|
||||
class MFBranchInstruction : public MFInstruction {
|
||||
private:
|
||||
MFVariable *condition_ = nullptr;
|
||||
MFInstruction *branch_true_ = nullptr;
|
||||
MFInstruction *branch_false_ = nullptr;
|
||||
|
||||
friend MFProcedure;
|
||||
|
||||
public:
|
||||
MFVariable *condition();
|
||||
const MFVariable *condition() const;
|
||||
void set_condition(MFVariable *variable);
|
||||
|
||||
MFInstruction *branch_true();
|
||||
const MFInstruction *branch_true() const;
|
||||
void set_branch_true(MFInstruction *instruction);
|
||||
|
||||
MFInstruction *branch_false();
|
||||
const MFInstruction *branch_false() const;
|
||||
void set_branch_false(MFInstruction *instruction);
|
||||
};
|
||||
|
||||
/**
|
||||
* A destruct instruction destructs a single variable. So the variable value will be uninitialized
|
||||
* after this instruction. All variables that are not output variables of the procedure, have to be
|
||||
* destructed before the procedure ends. Destructing early is generally a good thing, because it
|
||||
* might help with memory buffer reuse, which decreases memory-usage and increases performance.
|
||||
*/
|
||||
class MFDestructInstruction : public MFInstruction {
|
||||
private:
|
||||
MFVariable *variable_ = nullptr;
|
||||
MFInstruction *next_ = nullptr;
|
||||
|
||||
friend MFProcedure;
|
||||
|
||||
public:
|
||||
MFVariable *variable();
|
||||
const MFVariable *variable() const;
|
||||
void set_variable(MFVariable *variable);
|
||||
|
||||
MFInstruction *next();
|
||||
const MFInstruction *next() const;
|
||||
void set_next(MFInstruction *instruction);
|
||||
};
|
||||
|
||||
/**
|
||||
* This instruction does nothing, it just exists to building a procedure simpler in some cases.
|
||||
*/
|
||||
class MFDummyInstruction : public MFInstruction {
|
||||
private:
|
||||
MFInstruction *next_ = nullptr;
|
||||
|
||||
friend MFProcedure;
|
||||
|
||||
public:
|
||||
MFInstruction *next();
|
||||
const MFInstruction *next() const;
|
||||
void set_next(MFInstruction *instruction);
|
||||
};
|
||||
|
||||
/**
|
||||
* This instruction ends the procedure.
|
||||
*/
|
||||
class MFReturnInstruction : public MFInstruction {
|
||||
};
|
||||
|
||||
/**
|
||||
* Inputs and outputs of the entire procedure network.
|
||||
*/
|
||||
struct MFParameter {
|
||||
MFParamType::InterfaceType type;
|
||||
MFVariable *variable;
|
||||
};
|
||||
|
||||
struct ConstMFParameter {
|
||||
MFParamType::InterfaceType type;
|
||||
const MFVariable *variable;
|
||||
};
|
||||
|
||||
/**
|
||||
* A multi-function procedure allows composing multi-functions in arbitrary ways. It consists of
|
||||
* variables and instructions that operate on those variables. Branching and looping within the
|
||||
* procedure is supported as well.
|
||||
*
|
||||
* Typically, a #MFProcedure should be constructed using a #MFProcedureBuilder, which has many more
|
||||
* utility methods for common use cases.
|
||||
*/
|
||||
class MFProcedure : NonCopyable, NonMovable {
|
||||
private:
|
||||
LinearAllocator<> allocator_;
|
||||
Vector<MFCallInstruction *> call_instructions_;
|
||||
Vector<MFBranchInstruction *> branch_instructions_;
|
||||
Vector<MFDestructInstruction *> destruct_instructions_;
|
||||
Vector<MFDummyInstruction *> dummy_instructions_;
|
||||
Vector<MFReturnInstruction *> return_instructions_;
|
||||
Vector<MFVariable *> variables_;
|
||||
Vector<MFParameter> params_;
|
||||
MFInstruction *entry_ = nullptr;
|
||||
|
||||
friend class MFProcedureDotExport;
|
||||
|
||||
public:
|
||||
MFProcedure() = default;
|
||||
~MFProcedure();
|
||||
|
||||
MFVariable &new_variable(MFDataType data_type, std::string name = "");
|
||||
MFCallInstruction &new_call_instruction(const MultiFunction &fn);
|
||||
MFBranchInstruction &new_branch_instruction();
|
||||
MFDestructInstruction &new_destruct_instruction();
|
||||
MFDummyInstruction &new_dummy_instruction();
|
||||
MFReturnInstruction &new_return_instruction();
|
||||
|
||||
void add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable);
|
||||
|
||||
Span<ConstMFParameter> params() const;
|
||||
|
||||
MFInstruction *entry();
|
||||
const MFInstruction *entry() const;
|
||||
void set_entry(MFInstruction &entry);
|
||||
|
||||
Span<MFVariable *> variables();
|
||||
Span<const MFVariable *> variables() const;
|
||||
|
||||
std::string to_dot() const;
|
||||
|
||||
bool validate() const;
|
||||
|
||||
private:
|
||||
bool validate_all_instruction_pointers_set() const;
|
||||
bool validate_all_params_provided() const;
|
||||
bool validate_same_variables_in_one_call() const;
|
||||
bool validate_parameters() const;
|
||||
bool validate_initialization() const;
|
||||
|
||||
struct InitState {
|
||||
bool can_be_initialized = false;
|
||||
bool can_be_uninitialized = false;
|
||||
};
|
||||
|
||||
InitState find_initialization_state_before_instruction(const MFInstruction &target_instruction,
|
||||
const MFVariable &variable) const;
|
||||
};
|
||||
|
||||
namespace multi_function_procedure_types {
|
||||
using MFVariable = fn::MFVariable;
|
||||
using MFInstruction = fn::MFInstruction;
|
||||
using MFCallInstruction = fn::MFCallInstruction;
|
||||
using MFBranchInstruction = fn::MFBranchInstruction;
|
||||
using MFDestructInstruction = fn::MFDestructInstruction;
|
||||
using MFProcedure = fn::MFProcedure;
|
||||
} // namespace multi_function_procedure_types
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFVariable inline methods.
|
||||
*/
|
||||
|
||||
inline MFDataType MFVariable::data_type() const
|
||||
{
|
||||
return data_type_;
|
||||
}
|
||||
|
||||
inline Span<MFInstruction *> MFVariable::users()
|
||||
{
|
||||
return users_;
|
||||
}
|
||||
|
||||
inline StringRefNull MFVariable::name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
inline int MFVariable::id() const
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFInstruction inline methods.
|
||||
*/
|
||||
|
||||
inline MFInstructionType MFInstruction::type() const
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
inline Span<MFInstruction *> MFInstruction::prev()
|
||||
{
|
||||
return prev_;
|
||||
}
|
||||
|
||||
inline Span<const MFInstruction *> MFInstruction::prev() const
|
||||
{
|
||||
return prev_;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFCallInstruction inline methods.
|
||||
*/
|
||||
|
||||
inline const MultiFunction &MFCallInstruction::fn() const
|
||||
{
|
||||
return *fn_;
|
||||
}
|
||||
|
||||
inline MFInstruction *MFCallInstruction::next()
|
||||
{
|
||||
return next_;
|
||||
}
|
||||
|
||||
inline const MFInstruction *MFCallInstruction::next() const
|
||||
{
|
||||
return next_;
|
||||
}
|
||||
|
||||
inline Span<MFVariable *> MFCallInstruction::params()
|
||||
{
|
||||
return params_;
|
||||
}
|
||||
|
||||
inline Span<const MFVariable *> MFCallInstruction::params() const
|
||||
{
|
||||
return params_;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFBranchInstruction inline methods.
|
||||
*/
|
||||
|
||||
inline MFVariable *MFBranchInstruction::condition()
|
||||
{
|
||||
return condition_;
|
||||
}
|
||||
|
||||
inline const MFVariable *MFBranchInstruction::condition() const
|
||||
{
|
||||
return condition_;
|
||||
}
|
||||
|
||||
inline MFInstruction *MFBranchInstruction::branch_true()
|
||||
{
|
||||
return branch_true_;
|
||||
}
|
||||
|
||||
inline const MFInstruction *MFBranchInstruction::branch_true() const
|
||||
{
|
||||
return branch_true_;
|
||||
}
|
||||
|
||||
inline MFInstruction *MFBranchInstruction::branch_false()
|
||||
{
|
||||
return branch_false_;
|
||||
}
|
||||
|
||||
inline const MFInstruction *MFBranchInstruction::branch_false() const
|
||||
{
|
||||
return branch_false_;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFDestructInstruction inline methods.
|
||||
*/
|
||||
|
||||
inline MFVariable *MFDestructInstruction::variable()
|
||||
{
|
||||
return variable_;
|
||||
}
|
||||
|
||||
inline const MFVariable *MFDestructInstruction::variable() const
|
||||
{
|
||||
return variable_;
|
||||
}
|
||||
|
||||
inline MFInstruction *MFDestructInstruction::next()
|
||||
{
|
||||
return next_;
|
||||
}
|
||||
|
||||
inline const MFInstruction *MFDestructInstruction::next() const
|
||||
{
|
||||
return next_;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFDummyInstruction inline methods.
|
||||
*/
|
||||
|
||||
inline MFInstruction *MFDummyInstruction::next()
|
||||
{
|
||||
return next_;
|
||||
}
|
||||
|
||||
inline const MFInstruction *MFDummyInstruction::next() const
|
||||
{
|
||||
return next_;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFProcedure inline methods.
|
||||
*/
|
||||
|
||||
inline Span<ConstMFParameter> MFProcedure::params() const
|
||||
{
|
||||
static_assert(sizeof(MFParameter) == sizeof(ConstMFParameter));
|
||||
return params_.as_span().cast<ConstMFParameter>();
|
||||
}
|
||||
|
||||
inline MFInstruction *MFProcedure::entry()
|
||||
{
|
||||
return entry_;
|
||||
}
|
||||
|
||||
inline const MFInstruction *MFProcedure::entry() const
|
||||
{
|
||||
return entry_;
|
||||
}
|
||||
|
||||
inline Span<MFVariable *> MFProcedure::variables()
|
||||
{
|
||||
return variables_;
|
||||
}
|
||||
|
||||
inline Span<const MFVariable *> MFProcedure::variables() const
|
||||
{
|
||||
return variables_;
|
||||
}
|
||||
|
||||
} // namespace blender::fn
|
260
source/blender/functions/FN_multi_function_procedure_builder.hh
Normal file
260
source/blender/functions/FN_multi_function_procedure_builder.hh
Normal file
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup fn
|
||||
*/
|
||||
|
||||
#include "FN_multi_function_procedure.hh"
|
||||
|
||||
namespace blender::fn {
|
||||
|
||||
/**
|
||||
* An #MFInstructionCursor points to a position in a multi-function procedure, where an instruction
|
||||
* can be inserted.
|
||||
*/
|
||||
class MFInstructionCursor {
|
||||
private:
|
||||
MFInstruction *instruction_ = nullptr;
|
||||
/* Only used when it is a branch instruction. */
|
||||
bool branch_output_ = false;
|
||||
/* Only used when instruction is null. */
|
||||
bool is_entry_ = false;
|
||||
|
||||
public:
|
||||
MFInstructionCursor() = default;
|
||||
|
||||
MFInstructionCursor(MFCallInstruction &instruction);
|
||||
MFInstructionCursor(MFDestructInstruction &instruction);
|
||||
MFInstructionCursor(MFBranchInstruction &instruction, bool branch_output);
|
||||
MFInstructionCursor(MFDummyInstruction &instruction);
|
||||
|
||||
static MFInstructionCursor Entry();
|
||||
|
||||
void insert(MFProcedure &procedure, MFInstruction *new_instruction);
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility class to build a #MFProcedure.
|
||||
*/
|
||||
class MFProcedureBuilder {
|
||||
private:
|
||||
/** Procedure that is being build. */
|
||||
MFProcedure *procedure_ = nullptr;
|
||||
/** Cursors where the next instruction should be inserted. */
|
||||
Vector<MFInstructionCursor> cursors_;
|
||||
|
||||
public:
|
||||
struct Branch;
|
||||
struct Loop;
|
||||
|
||||
MFProcedureBuilder(MFProcedure &procedure,
|
||||
MFInstructionCursor initial_cursor = MFInstructionCursor::Entry());
|
||||
|
||||
MFProcedureBuilder(Span<MFProcedureBuilder *> builders);
|
||||
|
||||
MFProcedureBuilder(Branch &branch);
|
||||
|
||||
void set_cursor(const MFInstructionCursor &cursor);
|
||||
void set_cursor(Span<MFInstructionCursor> cursors);
|
||||
void set_cursor(Span<MFProcedureBuilder *> builders);
|
||||
void set_cursor_after_branch(Branch &branch);
|
||||
void set_cursor_after_loop(Loop &loop);
|
||||
|
||||
void add_destruct(MFVariable &variable);
|
||||
void add_destruct(Span<MFVariable *> variables);
|
||||
|
||||
MFReturnInstruction &add_return();
|
||||
|
||||
Branch add_branch(MFVariable &condition);
|
||||
|
||||
Loop add_loop();
|
||||
void add_loop_continue(Loop &loop);
|
||||
void add_loop_break(Loop &loop);
|
||||
|
||||
MFCallInstruction &add_call_with_no_variables(const MultiFunction &fn);
|
||||
MFCallInstruction &add_call_with_all_variables(const MultiFunction &fn,
|
||||
Span<MFVariable *> param_variables);
|
||||
|
||||
Vector<MFVariable *> add_call(const MultiFunction &fn,
|
||||
Span<MFVariable *> input_and_mutable_variables = {});
|
||||
|
||||
template<int OutputN>
|
||||
std::array<MFVariable *, OutputN> add_call(const MultiFunction &fn,
|
||||
Span<MFVariable *> input_and_mutable_variables = {});
|
||||
|
||||
void add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable);
|
||||
MFVariable &add_parameter(MFParamType param_type, std::string name = "");
|
||||
|
||||
MFVariable &add_input_parameter(MFDataType data_type, std::string name = "");
|
||||
template<typename T> MFVariable &add_single_input_parameter(std::string name = "");
|
||||
template<typename T> MFVariable &add_single_mutable_parameter(std::string name = "");
|
||||
|
||||
void add_output_parameter(MFVariable &variable);
|
||||
|
||||
private:
|
||||
void link_to_cursors(MFInstruction *instruction);
|
||||
};
|
||||
|
||||
struct MFProcedureBuilder::Branch {
|
||||
MFProcedureBuilder branch_true;
|
||||
MFProcedureBuilder branch_false;
|
||||
};
|
||||
|
||||
struct MFProcedureBuilder::Loop {
|
||||
MFInstruction *begin = nullptr;
|
||||
MFDummyInstruction *end = nullptr;
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFInstructionCursor inline methods.
|
||||
*/
|
||||
|
||||
inline MFInstructionCursor::MFInstructionCursor(MFCallInstruction &instruction)
|
||||
: instruction_(&instruction)
|
||||
{
|
||||
}
|
||||
|
||||
inline MFInstructionCursor::MFInstructionCursor(MFDestructInstruction &instruction)
|
||||
: instruction_(&instruction)
|
||||
{
|
||||
}
|
||||
|
||||
inline MFInstructionCursor::MFInstructionCursor(MFBranchInstruction &instruction,
|
||||
bool branch_output)
|
||||
: instruction_(&instruction), branch_output_(branch_output)
|
||||
{
|
||||
}
|
||||
|
||||
inline MFInstructionCursor::MFInstructionCursor(MFDummyInstruction &instruction)
|
||||
: instruction_(&instruction)
|
||||
{
|
||||
}
|
||||
|
||||
inline MFInstructionCursor MFInstructionCursor::Entry()
|
||||
{
|
||||
MFInstructionCursor cursor;
|
||||
cursor.is_entry_ = true;
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFProcedureBuilder inline methods.
|
||||
*/
|
||||
|
||||
inline MFProcedureBuilder::MFProcedureBuilder(Branch &branch)
|
||||
: MFProcedureBuilder(*branch.branch_true.procedure_)
|
||||
{
|
||||
this->set_cursor_after_branch(branch);
|
||||
}
|
||||
|
||||
inline MFProcedureBuilder::MFProcedureBuilder(MFProcedure &procedure,
|
||||
MFInstructionCursor initial_cursor)
|
||||
: procedure_(&procedure), cursors_({initial_cursor})
|
||||
{
|
||||
}
|
||||
|
||||
inline MFProcedureBuilder::MFProcedureBuilder(Span<MFProcedureBuilder *> builders)
|
||||
: MFProcedureBuilder(*builders[0]->procedure_)
|
||||
{
|
||||
this->set_cursor(builders);
|
||||
}
|
||||
|
||||
inline void MFProcedureBuilder::set_cursor(const MFInstructionCursor &cursor)
|
||||
{
|
||||
cursors_ = {cursor};
|
||||
}
|
||||
|
||||
inline void MFProcedureBuilder::set_cursor(Span<MFInstructionCursor> cursors)
|
||||
{
|
||||
cursors_ = cursors;
|
||||
}
|
||||
|
||||
inline void MFProcedureBuilder::set_cursor_after_branch(Branch &branch)
|
||||
{
|
||||
this->set_cursor({&branch.branch_false, &branch.branch_true});
|
||||
}
|
||||
|
||||
inline void MFProcedureBuilder::set_cursor_after_loop(Loop &loop)
|
||||
{
|
||||
this->set_cursor(MFInstructionCursor{*loop.end});
|
||||
}
|
||||
|
||||
inline void MFProcedureBuilder::set_cursor(Span<MFProcedureBuilder *> builders)
|
||||
{
|
||||
cursors_.clear();
|
||||
for (MFProcedureBuilder *builder : builders) {
|
||||
cursors_.extend(builder->cursors_);
|
||||
}
|
||||
}
|
||||
|
||||
template<int OutputN>
|
||||
inline std::array<MFVariable *, OutputN> MFProcedureBuilder::add_call(
|
||||
const MultiFunction &fn, Span<MFVariable *> input_and_mutable_variables)
|
||||
{
|
||||
Vector<MFVariable *> output_variables = this->add_call(fn, input_and_mutable_variables);
|
||||
BLI_assert(output_variables.size() == OutputN);
|
||||
|
||||
std::array<MFVariable *, OutputN> output_array;
|
||||
initialized_copy_n(output_variables.data(), OutputN, output_array.data());
|
||||
return output_array;
|
||||
}
|
||||
|
||||
inline void MFProcedureBuilder::add_parameter(MFParamType::InterfaceType interface_type,
|
||||
MFVariable &variable)
|
||||
{
|
||||
procedure_->add_parameter(interface_type, variable);
|
||||
}
|
||||
|
||||
inline MFVariable &MFProcedureBuilder::add_parameter(MFParamType param_type, std::string name)
|
||||
{
|
||||
MFVariable &variable = procedure_->new_variable(param_type.data_type(), std::move(name));
|
||||
this->add_parameter(param_type.interface_type(), variable);
|
||||
return variable;
|
||||
}
|
||||
|
||||
inline MFVariable &MFProcedureBuilder::add_input_parameter(MFDataType data_type, std::string name)
|
||||
{
|
||||
return this->add_parameter(MFParamType(MFParamType::Input, data_type), std::move(name));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline MFVariable &MFProcedureBuilder::add_single_input_parameter(std::string name)
|
||||
{
|
||||
return this->add_parameter(MFParamType::ForSingleInput(CPPType::get<T>()), std::move(name));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline MFVariable &MFProcedureBuilder::add_single_mutable_parameter(std::string name)
|
||||
{
|
||||
return this->add_parameter(MFParamType::ForMutableSingle(CPPType::get<T>()), std::move(name));
|
||||
}
|
||||
|
||||
inline void MFProcedureBuilder::add_output_parameter(MFVariable &variable)
|
||||
{
|
||||
this->add_parameter(MFParamType::Output, variable);
|
||||
}
|
||||
|
||||
inline void MFProcedureBuilder::link_to_cursors(MFInstruction *instruction)
|
||||
{
|
||||
for (MFInstructionCursor &cursor : cursors_) {
|
||||
cursor.insert(*procedure_, instruction);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::fn
|
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup fn
|
||||
*/
|
||||
|
||||
#include "FN_multi_function_procedure.hh"
|
||||
|
||||
namespace blender::fn {
|
||||
|
||||
/** A multi-function that executes a procedure internally. */
|
||||
class MFProcedureExecutor : public MultiFunction {
|
||||
private:
|
||||
MFSignature signature_;
|
||||
const MFProcedure &procedure_;
|
||||
|
||||
public:
|
||||
MFProcedureExecutor(std::string name, const MFProcedure &procedure);
|
||||
|
||||
void call(IndexMask mask, MFParams params, MFContext context) const override;
|
||||
};
|
||||
|
||||
} // namespace blender::fn
|
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "FN_cpp_type_make.hh"
|
||||
#include "FN_field_cpp_type.hh"
|
||||
|
||||
#include "BLI_color.hh"
|
||||
#include "BLI_float2.hh"
|
||||
@@ -39,4 +40,12 @@ MAKE_CPP_TYPE(ColorGeometry4b, blender::ColorGeometry4b, CPPTypeFlags::BasicType
|
||||
|
||||
MAKE_CPP_TYPE(string, std::string, CPPTypeFlags::BasicType)
|
||||
|
||||
MAKE_FIELD_CPP_TYPE(FloatField, float);
|
||||
MAKE_FIELD_CPP_TYPE(Float2Field, float2);
|
||||
MAKE_FIELD_CPP_TYPE(Float3Field, float3);
|
||||
MAKE_FIELD_CPP_TYPE(ColorGeometry4fField, blender::ColorGeometry4f);
|
||||
MAKE_FIELD_CPP_TYPE(BoolField, bool);
|
||||
MAKE_FIELD_CPP_TYPE(Int32Field, int32_t);
|
||||
MAKE_FIELD_CPP_TYPE(StringField, std::string);
|
||||
|
||||
} // namespace blender::fn
|
||||
|
569
source/blender/functions/intern/field.cc
Normal file
569
source/blender/functions/intern/field.cc
Normal file
@@ -0,0 +1,569 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_multi_value_map.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_stack.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "FN_field.hh"
|
||||
|
||||
namespace blender::fn {
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Field Evaluation.
|
||||
*/
|
||||
|
||||
struct FieldTreeInfo {
|
||||
/**
|
||||
* When fields are built, they only have references to the fields that they depend on. This map
|
||||
* allows traversal of fields in the opposite direction. So for every field it stores the other
|
||||
* fields that depend on it directly.
|
||||
*/
|
||||
MultiValueMap<GFieldRef, GFieldRef> field_users;
|
||||
/**
|
||||
* The same field input may exist in the field tree as as separate nodes due to the way
|
||||
* the tree is constructed. This set contains every different input only once.
|
||||
*/
|
||||
VectorSet<std::reference_wrapper<const FieldInput>> deduplicated_field_inputs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Collects some information from the field tree that is required by later steps.
|
||||
*/
|
||||
static FieldTreeInfo preprocess_field_tree(Span<GFieldRef> entry_fields)
|
||||
{
|
||||
FieldTreeInfo field_tree_info;
|
||||
|
||||
Stack<GFieldRef> fields_to_check;
|
||||
Set<GFieldRef> handled_fields;
|
||||
|
||||
for (GFieldRef field : entry_fields) {
|
||||
if (handled_fields.add(field)) {
|
||||
fields_to_check.push(field);
|
||||
}
|
||||
}
|
||||
|
||||
while (!fields_to_check.is_empty()) {
|
||||
GFieldRef field = fields_to_check.pop();
|
||||
if (field.node().is_input()) {
|
||||
const FieldInput &field_input = static_cast<const FieldInput &>(field.node());
|
||||
field_tree_info.deduplicated_field_inputs.add(field_input);
|
||||
continue;
|
||||
}
|
||||
BLI_assert(field.node().is_operation());
|
||||
const FieldOperation &operation = static_cast<const FieldOperation &>(field.node());
|
||||
for (const GFieldRef operation_input : operation.inputs()) {
|
||||
field_tree_info.field_users.add(operation_input, field);
|
||||
if (handled_fields.add(operation_input)) {
|
||||
fields_to_check.push(operation_input);
|
||||
}
|
||||
}
|
||||
}
|
||||
return field_tree_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the data from the context that is passed as input into the field.
|
||||
*/
|
||||
static Vector<const GVArray *> get_field_context_inputs(
|
||||
ResourceScope &scope,
|
||||
const IndexMask mask,
|
||||
const FieldContext &context,
|
||||
const Span<std::reference_wrapper<const FieldInput>> field_inputs)
|
||||
{
|
||||
Vector<const GVArray *> field_context_inputs;
|
||||
for (const FieldInput &field_input : field_inputs) {
|
||||
const GVArray *varray = context.get_varray_for_input(field_input, mask, scope);
|
||||
if (varray == nullptr) {
|
||||
const CPPType &type = field_input.cpp_type();
|
||||
varray = &scope.construct<GVArray_For_SingleValueRef>(
|
||||
__func__, type, mask.min_array_size(), type.default_value());
|
||||
}
|
||||
field_context_inputs.append(varray);
|
||||
}
|
||||
return field_context_inputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* \return A set that contains all fields from the field tree that depend on an input that varies
|
||||
* for different indices.
|
||||
*/
|
||||
static Set<GFieldRef> find_varying_fields(const FieldTreeInfo &field_tree_info,
|
||||
Span<const GVArray *> field_context_inputs)
|
||||
{
|
||||
Set<GFieldRef> found_fields;
|
||||
Stack<GFieldRef> fields_to_check;
|
||||
|
||||
/* The varying fields are the ones that depend on inputs that are not constant. Therefore we
|
||||
* start the tree search at the non-constant input fields and traverse through all fields that
|
||||
* depend on them. */
|
||||
for (const int i : field_context_inputs.index_range()) {
|
||||
const GVArray *varray = field_context_inputs[i];
|
||||
if (varray->is_single()) {
|
||||
continue;
|
||||
}
|
||||
const FieldInput &field_input = field_tree_info.deduplicated_field_inputs[i];
|
||||
const GFieldRef field_input_field{field_input, 0};
|
||||
const Span<GFieldRef> users = field_tree_info.field_users.lookup(field_input_field);
|
||||
for (const GFieldRef &field : users) {
|
||||
if (found_fields.add(field)) {
|
||||
fields_to_check.push(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (!fields_to_check.is_empty()) {
|
||||
GFieldRef field = fields_to_check.pop();
|
||||
const Span<GFieldRef> users = field_tree_info.field_users.lookup(field);
|
||||
for (GFieldRef field : users) {
|
||||
if (found_fields.add(field)) {
|
||||
fields_to_check.push(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
return found_fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the #procedure so that it computes the the fields.
|
||||
*/
|
||||
static void build_multi_function_procedure_for_fields(MFProcedure &procedure,
|
||||
ResourceScope &scope,
|
||||
const FieldTreeInfo &field_tree_info,
|
||||
Span<GFieldRef> output_fields)
|
||||
{
|
||||
MFProcedureBuilder builder{procedure};
|
||||
/* Every input, intermediate and output field corresponds to a variable in the procedure. */
|
||||
Map<GFieldRef, MFVariable *> variable_by_field;
|
||||
|
||||
/* Start by adding the field inputs as parameters to the procedure. */
|
||||
for (const FieldInput &field_input : field_tree_info.deduplicated_field_inputs) {
|
||||
MFVariable &variable = builder.add_input_parameter(
|
||||
MFDataType::ForSingle(field_input.cpp_type()), field_input.debug_name());
|
||||
variable_by_field.add_new({field_input, 0}, &variable);
|
||||
}
|
||||
|
||||
/* Utility struct that is used to do proper depth first search traversal of the tree below. */
|
||||
struct FieldWithIndex {
|
||||
GFieldRef field;
|
||||
int current_input_index = 0;
|
||||
};
|
||||
|
||||
for (GFieldRef field : output_fields) {
|
||||
/* We start a new stack for each output field to make sure that a field pushed later to the
|
||||
* stack does never depend on a field that was pushed before. */
|
||||
Stack<FieldWithIndex> fields_to_check;
|
||||
fields_to_check.push({field, 0});
|
||||
while (!fields_to_check.is_empty()) {
|
||||
FieldWithIndex &field_with_index = fields_to_check.peek();
|
||||
const GFieldRef &field = field_with_index.field;
|
||||
if (variable_by_field.contains(field)) {
|
||||
/* The field has been handled already. */
|
||||
fields_to_check.pop();
|
||||
continue;
|
||||
}
|
||||
/* Field inputs should already be handled above. */
|
||||
BLI_assert(field.node().is_operation());
|
||||
|
||||
const FieldOperation &operation = static_cast<const FieldOperation &>(field.node());
|
||||
const Span<GField> operation_inputs = operation.inputs();
|
||||
|
||||
if (field_with_index.current_input_index < operation_inputs.size()) {
|
||||
/* Not all inputs are handled yet. Push the next input field to the stack and increment the
|
||||
* input index. */
|
||||
fields_to_check.push({operation_inputs[field_with_index.current_input_index]});
|
||||
field_with_index.current_input_index++;
|
||||
}
|
||||
else {
|
||||
/* All inputs variables are ready, now add the function call. */
|
||||
Vector<MFVariable *> input_variables;
|
||||
for (const GField &field : operation_inputs) {
|
||||
input_variables.append(variable_by_field.lookup(field));
|
||||
}
|
||||
const MultiFunction &multi_function = operation.multi_function();
|
||||
Vector<MFVariable *> output_variables = builder.add_call(multi_function, input_variables);
|
||||
/* Add newly created variables to the map. */
|
||||
for (const int i : output_variables.index_range()) {
|
||||
variable_by_field.add_new({operation, i}, output_variables[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Add output parameters to the procedure. */
|
||||
Set<MFVariable *> already_output_variables;
|
||||
for (const GFieldRef &field : output_fields) {
|
||||
MFVariable *variable = variable_by_field.lookup(field);
|
||||
if (!already_output_variables.add(variable)) {
|
||||
/* One variable can be output at most once. To output the same value twice, we have to make
|
||||
* a copy first. */
|
||||
const MultiFunction ©_fn = scope.construct<CustomMF_GenericCopy>(
|
||||
__func__, "copy", variable->data_type());
|
||||
variable = builder.add_call<1>(copy_fn, {variable})[0];
|
||||
}
|
||||
builder.add_output_parameter(*variable);
|
||||
}
|
||||
|
||||
/* Remove the variables that should not be destructed from the map. */
|
||||
for (const GFieldRef &field : output_fields) {
|
||||
variable_by_field.remove(field);
|
||||
}
|
||||
/* Add destructor calls for the remaining variables. */
|
||||
for (MFVariable *variable : variable_by_field.values()) {
|
||||
builder.add_destruct(*variable);
|
||||
}
|
||||
|
||||
builder.add_return();
|
||||
|
||||
// std::cout << procedure.to_dot() << "\n";
|
||||
BLI_assert(procedure.validate());
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class that destructs elements from a partially initialized array.
|
||||
*/
|
||||
struct PartiallyInitializedArray : NonCopyable, NonMovable {
|
||||
void *buffer;
|
||||
IndexMask mask;
|
||||
const CPPType *type;
|
||||
|
||||
~PartiallyInitializedArray()
|
||||
{
|
||||
this->type->destruct_indices(this->buffer, this->mask);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Evaluate fields in the given context. If possible, multiple fields should be evaluated together,
|
||||
* because that can be more efficient when they share common sub-fields.
|
||||
*
|
||||
* \param scope: The resource scope that owns data that makes up the output virtual arrays. Make
|
||||
* sure the scope is not destructed when the output virtual arrays are still used.
|
||||
* \param fields_to_evaluate: The fields that should be evaluated together.
|
||||
* \param mask: Determines which indices are computed. The mask may be referenced by the returned
|
||||
* virtual arrays. So the underlying indices (if applicable) should live longer then #scope.
|
||||
* \param context: The context that the field is evaluated in. Used to retrieve data from each
|
||||
* #FieldInput in the field network.
|
||||
* \param dst_varrays: If provided, the computed data will be written into those virtual arrays
|
||||
* instead of into newly created ones. That allows making the computed data live longer than
|
||||
* #scope and is more efficient when the data will be written into those virtual arrays
|
||||
* later anyway.
|
||||
* \return The computed virtual arrays for each provided field. If #dst_varrays is passed, the
|
||||
* provided virtual arrays are returned.
|
||||
*/
|
||||
Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
|
||||
Span<GFieldRef> fields_to_evaluate,
|
||||
IndexMask mask,
|
||||
const FieldContext &context,
|
||||
Span<GVMutableArray *> dst_varrays)
|
||||
{
|
||||
Vector<const GVArray *> r_varrays(fields_to_evaluate.size(), nullptr);
|
||||
const int array_size = mask.min_array_size();
|
||||
|
||||
/* Destination arrays are optional. Create a small utility method to access them. */
|
||||
auto get_dst_varray_if_available = [&](int index) -> GVMutableArray * {
|
||||
if (dst_varrays.is_empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
BLI_assert(dst_varrays[index] == nullptr || dst_varrays[index]->size() >= array_size);
|
||||
return dst_varrays[index];
|
||||
};
|
||||
|
||||
/* Traverse the field tree and prepare some data that is used in later steps. */
|
||||
FieldTreeInfo field_tree_info = preprocess_field_tree(fields_to_evaluate);
|
||||
|
||||
/* Get inputs that will be passed into the field when evaluated. */
|
||||
Vector<const GVArray *> field_context_inputs = get_field_context_inputs(
|
||||
scope, mask, context, field_tree_info.deduplicated_field_inputs);
|
||||
|
||||
/* Finish fields that output an input varray directly. For those we don't have to do any further
|
||||
* processing. */
|
||||
for (const int out_index : fields_to_evaluate.index_range()) {
|
||||
const GFieldRef &field = fields_to_evaluate[out_index];
|
||||
if (!field.node().is_input()) {
|
||||
continue;
|
||||
}
|
||||
const FieldInput &field_input = static_cast<const FieldInput &>(field.node());
|
||||
const int field_input_index = field_tree_info.deduplicated_field_inputs.index_of(field_input);
|
||||
const GVArray *varray = field_context_inputs[field_input_index];
|
||||
r_varrays[out_index] = varray;
|
||||
}
|
||||
|
||||
Set<GFieldRef> varying_fields = find_varying_fields(field_tree_info, field_context_inputs);
|
||||
|
||||
/* Separate fields into two categories. Those that are constant and need to be evaluated only
|
||||
* once, and those that need to be evaluated for every index. */
|
||||
Vector<GFieldRef> varying_fields_to_evaluate;
|
||||
Vector<int> varying_field_indices;
|
||||
Vector<GFieldRef> constant_fields_to_evaluate;
|
||||
Vector<int> constant_field_indices;
|
||||
for (const int i : fields_to_evaluate.index_range()) {
|
||||
if (r_varrays[i] != nullptr) {
|
||||
/* Already done. */
|
||||
continue;
|
||||
}
|
||||
GFieldRef field = fields_to_evaluate[i];
|
||||
if (varying_fields.contains(field)) {
|
||||
varying_fields_to_evaluate.append(field);
|
||||
varying_field_indices.append(i);
|
||||
}
|
||||
else {
|
||||
constant_fields_to_evaluate.append(field);
|
||||
constant_field_indices.append(i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Evaluate varying fields if necessary. */
|
||||
if (!varying_fields_to_evaluate.is_empty()) {
|
||||
/* Build the procedure for those fields. */
|
||||
MFProcedure procedure;
|
||||
build_multi_function_procedure_for_fields(
|
||||
procedure, scope, field_tree_info, varying_fields_to_evaluate);
|
||||
MFProcedureExecutor procedure_executor{"Procedure", procedure};
|
||||
MFParamsBuilder mf_params{procedure_executor, array_size};
|
||||
MFContextBuilder mf_context;
|
||||
|
||||
/* Provide inputs to the procedure executor. */
|
||||
for (const GVArray *varray : field_context_inputs) {
|
||||
mf_params.add_readonly_single_input(*varray);
|
||||
}
|
||||
|
||||
for (const int i : varying_fields_to_evaluate.index_range()) {
|
||||
const GFieldRef &field = varying_fields_to_evaluate[i];
|
||||
const CPPType &type = field.cpp_type();
|
||||
const int out_index = varying_field_indices[i];
|
||||
|
||||
/* Try to get an existing virtual array that the result should be written into. */
|
||||
GVMutableArray *output_varray = get_dst_varray_if_available(out_index);
|
||||
void *buffer;
|
||||
if (output_varray == nullptr || !output_varray->is_span()) {
|
||||
/* Allocate a new buffer for the computed result. */
|
||||
buffer = scope.linear_allocator().allocate(type.size() * array_size, type.alignment());
|
||||
|
||||
/* Make sure that elements in the buffer will be destructed. */
|
||||
PartiallyInitializedArray &destruct_helper = scope.construct<PartiallyInitializedArray>(
|
||||
__func__);
|
||||
destruct_helper.buffer = buffer;
|
||||
destruct_helper.mask = mask;
|
||||
destruct_helper.type = &type;
|
||||
|
||||
r_varrays[out_index] = &scope.construct<GVArray_For_GSpan>(
|
||||
__func__, GSpan{type, buffer, array_size});
|
||||
}
|
||||
else {
|
||||
/* Write the result into the existing span. */
|
||||
buffer = output_varray->get_internal_span().data();
|
||||
|
||||
r_varrays[out_index] = output_varray;
|
||||
}
|
||||
|
||||
/* Pass output buffer to the procedure executor. */
|
||||
const GMutableSpan span{type, buffer, array_size};
|
||||
mf_params.add_uninitialized_single_output(span);
|
||||
}
|
||||
|
||||
procedure_executor.call(mask, mf_params, mf_context);
|
||||
}
|
||||
|
||||
/* Evaluate constant fields if necessary. */
|
||||
if (!constant_fields_to_evaluate.is_empty()) {
|
||||
/* Build the procedure for those fields. */
|
||||
MFProcedure procedure;
|
||||
build_multi_function_procedure_for_fields(
|
||||
procedure, scope, field_tree_info, constant_fields_to_evaluate);
|
||||
MFProcedureExecutor procedure_executor{"Procedure", procedure};
|
||||
MFParamsBuilder mf_params{procedure_executor, 1};
|
||||
MFContextBuilder mf_context;
|
||||
|
||||
/* Provide inputs to the procedure executor. */
|
||||
for (const GVArray *varray : field_context_inputs) {
|
||||
mf_params.add_readonly_single_input(*varray);
|
||||
}
|
||||
|
||||
for (const int i : constant_fields_to_evaluate.index_range()) {
|
||||
const GFieldRef &field = constant_fields_to_evaluate[i];
|
||||
const CPPType &type = field.cpp_type();
|
||||
/* Allocate memory where the computed value will be stored in. */
|
||||
void *buffer = scope.linear_allocator().allocate(type.size(), type.alignment());
|
||||
|
||||
/* Use this to make sure that the value is destructed in the end. */
|
||||
PartiallyInitializedArray &destruct_helper = scope.construct<PartiallyInitializedArray>(
|
||||
__func__);
|
||||
destruct_helper.buffer = buffer;
|
||||
destruct_helper.mask = IndexRange(1);
|
||||
destruct_helper.type = &type;
|
||||
|
||||
/* Pass output buffer to the procedure executor. */
|
||||
mf_params.add_uninitialized_single_output({type, buffer, 1});
|
||||
|
||||
/* Create virtual array that can be used after the procedure has been executed below. */
|
||||
const int out_index = constant_field_indices[i];
|
||||
r_varrays[out_index] = &scope.construct<GVArray_For_SingleValueRef>(
|
||||
__func__, type, array_size, buffer);
|
||||
}
|
||||
|
||||
procedure_executor.call(IndexRange(1), mf_params, mf_context);
|
||||
}
|
||||
|
||||
/* Copy data to supplied destination arrays if necessary. In some cases the evaluation above has
|
||||
* written the computed data in the right place already. */
|
||||
if (!dst_varrays.is_empty()) {
|
||||
for (const int out_index : fields_to_evaluate.index_range()) {
|
||||
GVMutableArray *output_varray = get_dst_varray_if_available(out_index);
|
||||
if (output_varray == nullptr) {
|
||||
/* Caller did not provide a destination for this output. */
|
||||
continue;
|
||||
}
|
||||
const GVArray *computed_varray = r_varrays[out_index];
|
||||
BLI_assert(computed_varray->type() == output_varray->type());
|
||||
if (output_varray == computed_varray) {
|
||||
/* The result has been written into the destination provided by the caller already. */
|
||||
continue;
|
||||
}
|
||||
/* Still have to copy over the data in the destination provided by the caller. */
|
||||
if (output_varray->is_span()) {
|
||||
/* Materialize into a span. */
|
||||
computed_varray->materialize_to_uninitialized(output_varray->get_internal_span().data());
|
||||
}
|
||||
else {
|
||||
/* Slower materialize into a different structure. */
|
||||
const CPPType &type = computed_varray->type();
|
||||
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
|
||||
for (const int i : mask) {
|
||||
computed_varray->get_to_uninitialized(i, buffer);
|
||||
output_varray->set_by_relocate(i, buffer);
|
||||
}
|
||||
}
|
||||
r_varrays[out_index] = output_varray;
|
||||
}
|
||||
}
|
||||
return r_varrays;
|
||||
}
|
||||
|
||||
void evaluate_constant_field(const GField &field, void *r_value)
|
||||
{
|
||||
ResourceScope scope;
|
||||
FieldContext context;
|
||||
Vector<const GVArray *> varrays = evaluate_fields(scope, {field}, IndexRange(1), context);
|
||||
varrays[0]->get_to_uninitialized(0, r_value);
|
||||
}
|
||||
|
||||
const GVArray *FieldContext::get_varray_for_input(const FieldInput &field_input,
|
||||
IndexMask mask,
|
||||
ResourceScope &scope) const
|
||||
{
|
||||
/* By default ask the field input to create the varray. Another field context might overwrite
|
||||
* the context here. */
|
||||
return field_input.get_varray_for_context(*this, mask, scope);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* FieldEvaluator.
|
||||
*/
|
||||
|
||||
static Vector<int64_t> indices_from_selection(const VArray<bool> &selection)
|
||||
{
|
||||
/* If the selection is just a single value, it's best to avoid calling this
|
||||
* function when constructing an IndexMask and use an IndexRange instead. */
|
||||
BLI_assert(!selection.is_single());
|
||||
|
||||
Vector<int64_t> indices;
|
||||
if (selection.is_span()) {
|
||||
Span<bool> span = selection.get_internal_span();
|
||||
for (const int64_t i : span.index_range()) {
|
||||
if (span[i]) {
|
||||
indices.append(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const int i : selection.index_range()) {
|
||||
if (selection[i]) {
|
||||
indices.append(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
|
||||
int FieldEvaluator::add_with_destination(GField field, GVMutableArray &dst)
|
||||
{
|
||||
const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field));
|
||||
dst_varrays_.append(&dst);
|
||||
output_pointer_infos_.append({});
|
||||
return field_index;
|
||||
}
|
||||
|
||||
int FieldEvaluator::add_with_destination(GField field, GMutableSpan dst)
|
||||
{
|
||||
GVMutableArray &varray = scope_.construct<GVMutableArray_For_GMutableSpan>(__func__, dst);
|
||||
return this->add_with_destination(std::move(field), varray);
|
||||
}
|
||||
|
||||
int FieldEvaluator::add(GField field, const GVArray **varray_ptr)
|
||||
{
|
||||
const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field));
|
||||
dst_varrays_.append(nullptr);
|
||||
output_pointer_infos_.append(OutputPointerInfo{
|
||||
varray_ptr, [](void *dst, const GVArray &varray, ResourceScope &UNUSED(scope)) {
|
||||
*(const GVArray **)dst = &varray;
|
||||
}});
|
||||
return field_index;
|
||||
}
|
||||
|
||||
int FieldEvaluator::add(GField field)
|
||||
{
|
||||
const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field));
|
||||
dst_varrays_.append(nullptr);
|
||||
output_pointer_infos_.append({});
|
||||
return field_index;
|
||||
}
|
||||
|
||||
void FieldEvaluator::evaluate()
|
||||
{
|
||||
BLI_assert_msg(!is_evaluated_, "Cannot evaluate fields twice.");
|
||||
Array<GFieldRef> fields(fields_to_evaluate_.size());
|
||||
for (const int i : fields_to_evaluate_.index_range()) {
|
||||
fields[i] = fields_to_evaluate_[i];
|
||||
}
|
||||
evaluated_varrays_ = evaluate_fields(scope_, fields, mask_, context_, dst_varrays_);
|
||||
BLI_assert(fields_to_evaluate_.size() == evaluated_varrays_.size());
|
||||
for (const int i : fields_to_evaluate_.index_range()) {
|
||||
OutputPointerInfo &info = output_pointer_infos_[i];
|
||||
if (info.dst != nullptr) {
|
||||
info.set(info.dst, *evaluated_varrays_[i], scope_);
|
||||
}
|
||||
}
|
||||
is_evaluated_ = true;
|
||||
}
|
||||
|
||||
IndexMask FieldEvaluator::get_evaluated_as_mask(const int field_index)
|
||||
{
|
||||
const GVArray &varray = this->get_evaluated(field_index);
|
||||
GVArray_Typed<bool> typed_varray{varray};
|
||||
|
||||
if (typed_varray->is_single()) {
|
||||
if (typed_varray->get_internal_single()) {
|
||||
return IndexRange(typed_varray.size());
|
||||
}
|
||||
return IndexRange(0);
|
||||
}
|
||||
|
||||
return scope_.add_value(indices_from_selection(*typed_varray), __func__).as_span();
|
||||
}
|
||||
|
||||
} // namespace blender::fn
|
@@ -123,4 +123,32 @@ void CustomMF_DefaultOutput::call(IndexMask mask, MFParams params, MFContext UNU
|
||||
}
|
||||
}
|
||||
|
||||
CustomMF_GenericCopy::CustomMF_GenericCopy(StringRef name, MFDataType data_type)
|
||||
{
|
||||
MFSignatureBuilder signature{name};
|
||||
signature.input("Input", data_type);
|
||||
signature.output("Output", data_type);
|
||||
signature_ = signature.build();
|
||||
this->set_signature(&signature_);
|
||||
}
|
||||
|
||||
void CustomMF_GenericCopy::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const
|
||||
{
|
||||
const MFDataType data_type = this->param_type(0).data_type();
|
||||
switch (data_type.category()) {
|
||||
case MFDataType::Single: {
|
||||
const GVArray &inputs = params.readonly_single_input(0, "Input");
|
||||
GMutableSpan outputs = params.uninitialized_single_output(1, "Output");
|
||||
inputs.materialize_to_uninitialized(mask, outputs.data());
|
||||
break;
|
||||
}
|
||||
case MFDataType::Vector: {
|
||||
const GVVectorArray &inputs = params.readonly_vector_input(0, "Input");
|
||||
GVectorArray &outputs = params.vector_output(1, "Output");
|
||||
outputs.extend(mask, inputs);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::fn
|
||||
|
794
source/blender/functions/intern/multi_function_procedure.cc
Normal file
794
source/blender/functions/intern/multi_function_procedure.cc
Normal file
@@ -0,0 +1,794 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "FN_multi_function_procedure.hh"
|
||||
|
||||
#include "BLI_dot_export.hh"
|
||||
#include "BLI_stack.hh"
|
||||
|
||||
namespace blender::fn {
|
||||
|
||||
void MFVariable::set_name(std::string name)
|
||||
{
|
||||
name_ = std::move(name);
|
||||
}
|
||||
|
||||
void MFCallInstruction::set_next(MFInstruction *instruction)
|
||||
{
|
||||
if (next_ != nullptr) {
|
||||
next_->prev_.remove_first_occurrence_and_reorder(this);
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append(this);
|
||||
}
|
||||
next_ = instruction;
|
||||
}
|
||||
|
||||
void MFCallInstruction::set_param_variable(int param_index, MFVariable *variable)
|
||||
{
|
||||
if (params_[param_index] != nullptr) {
|
||||
params_[param_index]->users_.remove_first_occurrence_and_reorder(this);
|
||||
}
|
||||
if (variable != nullptr) {
|
||||
BLI_assert(fn_->param_type(param_index).data_type() == variable->data_type());
|
||||
variable->users_.append(this);
|
||||
}
|
||||
params_[param_index] = variable;
|
||||
}
|
||||
|
||||
void MFCallInstruction::set_params(Span<MFVariable *> variables)
|
||||
{
|
||||
BLI_assert(variables.size() == params_.size());
|
||||
for (const int i : variables.index_range()) {
|
||||
this->set_param_variable(i, variables[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void MFBranchInstruction::set_condition(MFVariable *variable)
|
||||
{
|
||||
if (condition_ != nullptr) {
|
||||
condition_->users_.remove_first_occurrence_and_reorder(this);
|
||||
}
|
||||
if (variable != nullptr) {
|
||||
variable->users_.append(this);
|
||||
}
|
||||
condition_ = variable;
|
||||
}
|
||||
|
||||
void MFBranchInstruction::set_branch_true(MFInstruction *instruction)
|
||||
{
|
||||
if (branch_true_ != nullptr) {
|
||||
branch_true_->prev_.remove_first_occurrence_and_reorder(this);
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append(this);
|
||||
}
|
||||
branch_true_ = instruction;
|
||||
}
|
||||
|
||||
void MFBranchInstruction::set_branch_false(MFInstruction *instruction)
|
||||
{
|
||||
if (branch_false_ != nullptr) {
|
||||
branch_false_->prev_.remove_first_occurrence_and_reorder(this);
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append(this);
|
||||
}
|
||||
branch_false_ = instruction;
|
||||
}
|
||||
|
||||
void MFDestructInstruction::set_variable(MFVariable *variable)
|
||||
{
|
||||
if (variable_ != nullptr) {
|
||||
variable_->users_.remove_first_occurrence_and_reorder(this);
|
||||
}
|
||||
if (variable != nullptr) {
|
||||
variable->users_.append(this);
|
||||
}
|
||||
variable_ = variable;
|
||||
}
|
||||
|
||||
void MFDestructInstruction::set_next(MFInstruction *instruction)
|
||||
{
|
||||
if (next_ != nullptr) {
|
||||
next_->prev_.remove_first_occurrence_and_reorder(this);
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append(this);
|
||||
}
|
||||
next_ = instruction;
|
||||
}
|
||||
|
||||
void MFDummyInstruction::set_next(MFInstruction *instruction)
|
||||
{
|
||||
if (next_ != nullptr) {
|
||||
next_->prev_.remove_first_occurrence_and_reorder(this);
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append(this);
|
||||
}
|
||||
next_ = instruction;
|
||||
}
|
||||
|
||||
MFVariable &MFProcedure::new_variable(MFDataType data_type, std::string name)
|
||||
{
|
||||
MFVariable &variable = *allocator_.construct<MFVariable>().release();
|
||||
variable.name_ = std::move(name);
|
||||
variable.data_type_ = data_type;
|
||||
variable.id_ = variables_.size();
|
||||
variables_.append(&variable);
|
||||
return variable;
|
||||
}
|
||||
|
||||
MFCallInstruction &MFProcedure::new_call_instruction(const MultiFunction &fn)
|
||||
{
|
||||
MFCallInstruction &instruction = *allocator_.construct<MFCallInstruction>().release();
|
||||
instruction.type_ = MFInstructionType::Call;
|
||||
instruction.fn_ = &fn;
|
||||
instruction.params_ = allocator_.allocate_array<MFVariable *>(fn.param_amount());
|
||||
instruction.params_.fill(nullptr);
|
||||
call_instructions_.append(&instruction);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
MFBranchInstruction &MFProcedure::new_branch_instruction()
|
||||
{
|
||||
MFBranchInstruction &instruction = *allocator_.construct<MFBranchInstruction>().release();
|
||||
instruction.type_ = MFInstructionType::Branch;
|
||||
branch_instructions_.append(&instruction);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
MFDestructInstruction &MFProcedure::new_destruct_instruction()
|
||||
{
|
||||
MFDestructInstruction &instruction = *allocator_.construct<MFDestructInstruction>().release();
|
||||
instruction.type_ = MFInstructionType::Destruct;
|
||||
destruct_instructions_.append(&instruction);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
MFDummyInstruction &MFProcedure::new_dummy_instruction()
|
||||
{
|
||||
MFDummyInstruction &instruction = *allocator_.construct<MFDummyInstruction>().release();
|
||||
instruction.type_ = MFInstructionType::Dummy;
|
||||
dummy_instructions_.append(&instruction);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
MFReturnInstruction &MFProcedure::new_return_instruction()
|
||||
{
|
||||
MFReturnInstruction &instruction = *allocator_.construct<MFReturnInstruction>().release();
|
||||
instruction.type_ = MFInstructionType::Return;
|
||||
return_instructions_.append(&instruction);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
void MFProcedure::add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable)
|
||||
{
|
||||
params_.append({interface_type, &variable});
|
||||
}
|
||||
|
||||
void MFProcedure::set_entry(MFInstruction &entry)
|
||||
{
|
||||
entry_ = &entry;
|
||||
}
|
||||
|
||||
MFProcedure::~MFProcedure()
|
||||
{
|
||||
for (MFCallInstruction *instruction : call_instructions_) {
|
||||
instruction->~MFCallInstruction();
|
||||
}
|
||||
for (MFBranchInstruction *instruction : branch_instructions_) {
|
||||
instruction->~MFBranchInstruction();
|
||||
}
|
||||
for (MFDestructInstruction *instruction : destruct_instructions_) {
|
||||
instruction->~MFDestructInstruction();
|
||||
}
|
||||
for (MFDummyInstruction *instruction : dummy_instructions_) {
|
||||
instruction->~MFDummyInstruction();
|
||||
}
|
||||
for (MFReturnInstruction *instruction : return_instructions_) {
|
||||
instruction->~MFReturnInstruction();
|
||||
}
|
||||
for (MFVariable *variable : variables_) {
|
||||
variable->~MFVariable();
|
||||
}
|
||||
}
|
||||
|
||||
bool MFProcedure::validate() const
|
||||
{
|
||||
if (entry_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (!this->validate_all_instruction_pointers_set()) {
|
||||
return false;
|
||||
}
|
||||
if (!this->validate_all_params_provided()) {
|
||||
return false;
|
||||
}
|
||||
if (!this->validate_same_variables_in_one_call()) {
|
||||
return false;
|
||||
}
|
||||
if (!this->validate_parameters()) {
|
||||
return false;
|
||||
}
|
||||
if (!this->validate_initialization()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MFProcedure::validate_all_instruction_pointers_set() const
|
||||
{
|
||||
for (const MFCallInstruction *instruction : call_instructions_) {
|
||||
if (instruction->next_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const MFDestructInstruction *instruction : destruct_instructions_) {
|
||||
if (instruction->next_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const MFBranchInstruction *instruction : branch_instructions_) {
|
||||
if (instruction->branch_true_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (instruction->branch_false_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const MFDummyInstruction *instruction : dummy_instructions_) {
|
||||
if (instruction->next_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MFProcedure::validate_all_params_provided() const
|
||||
{
|
||||
for (const MFCallInstruction *instruction : call_instructions_) {
|
||||
for (const MFVariable *variable : instruction->params_) {
|
||||
if (variable == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const MFBranchInstruction *instruction : branch_instructions_) {
|
||||
if (instruction->condition_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const MFDestructInstruction *instruction : destruct_instructions_) {
|
||||
if (instruction->variable_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MFProcedure::validate_same_variables_in_one_call() const
|
||||
{
|
||||
for (const MFCallInstruction *instruction : call_instructions_) {
|
||||
const MultiFunction &fn = *instruction->fn_;
|
||||
for (const int param_index : fn.param_indices()) {
|
||||
const MFParamType param_type = fn.param_type(param_index);
|
||||
const MFVariable *variable = instruction->params_[param_index];
|
||||
for (const int other_param_index : fn.param_indices()) {
|
||||
if (other_param_index == param_index) {
|
||||
continue;
|
||||
}
|
||||
const MFVariable *other_variable = instruction->params_[other_param_index];
|
||||
if (other_variable != variable) {
|
||||
continue;
|
||||
}
|
||||
if (ELEM(param_type.interface_type(), MFParamType::Mutable, MFParamType::Output)) {
|
||||
/* When a variable is used as mutable or output parameter, it can only be used once. */
|
||||
return false;
|
||||
}
|
||||
const MFParamType other_param_type = fn.param_type(other_param_index);
|
||||
/* A variable is allowed to be used as input more than once. */
|
||||
if (other_param_type.interface_type() != MFParamType::Input) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MFProcedure::validate_parameters() const
|
||||
{
|
||||
Set<const MFVariable *> variables;
|
||||
for (const MFParameter ¶m : params_) {
|
||||
/* One variable cannot be used as multiple parameters. */
|
||||
if (!variables.add(param.variable)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MFProcedure::validate_initialization() const
|
||||
{
|
||||
/* TODO: Issue warning when it maybe wrongly initialized. */
|
||||
for (const MFDestructInstruction *instruction : destruct_instructions_) {
|
||||
const MFVariable &variable = *instruction->variable_;
|
||||
const InitState state = this->find_initialization_state_before_instruction(*instruction,
|
||||
variable);
|
||||
if (!state.can_be_initialized) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const MFBranchInstruction *instruction : branch_instructions_) {
|
||||
const MFVariable &variable = *instruction->condition_;
|
||||
const InitState state = this->find_initialization_state_before_instruction(*instruction,
|
||||
variable);
|
||||
if (!state.can_be_initialized) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const MFCallInstruction *instruction : call_instructions_) {
|
||||
const MultiFunction &fn = *instruction->fn_;
|
||||
for (const int param_index : fn.param_indices()) {
|
||||
const MFParamType param_type = fn.param_type(param_index);
|
||||
const MFVariable &variable = *instruction->params_[param_index];
|
||||
const InitState state = this->find_initialization_state_before_instruction(*instruction,
|
||||
variable);
|
||||
switch (param_type.interface_type()) {
|
||||
case MFParamType::Input:
|
||||
case MFParamType::Mutable: {
|
||||
if (!state.can_be_initialized) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MFParamType::Output: {
|
||||
if (!state.can_be_uninitialized) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Set<const MFVariable *> variables_that_should_be_initialized_on_return;
|
||||
for (const MFParameter ¶m : params_) {
|
||||
if (ELEM(param.type, MFParamType::Mutable, MFParamType::Output)) {
|
||||
variables_that_should_be_initialized_on_return.add_new(param.variable);
|
||||
}
|
||||
}
|
||||
for (const MFReturnInstruction *instruction : return_instructions_) {
|
||||
for (const MFVariable *variable : variables_) {
|
||||
const InitState init_state = this->find_initialization_state_before_instruction(*instruction,
|
||||
*variable);
|
||||
if (variables_that_should_be_initialized_on_return.contains(variable)) {
|
||||
if (!init_state.can_be_initialized) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!init_state.can_be_uninitialized) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
MFProcedure::InitState MFProcedure::find_initialization_state_before_instruction(
|
||||
const MFInstruction &target_instruction, const MFVariable &target_variable) const
|
||||
{
|
||||
InitState state;
|
||||
|
||||
auto check_entry_instruction = [&]() {
|
||||
bool caller_initialized_variable = false;
|
||||
for (const MFParameter ¶m : params_) {
|
||||
if (param.variable == &target_variable) {
|
||||
if (ELEM(param.type, MFParamType::Input, MFParamType::Mutable)) {
|
||||
caller_initialized_variable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (caller_initialized_variable) {
|
||||
state.can_be_initialized = true;
|
||||
}
|
||||
else {
|
||||
state.can_be_uninitialized = true;
|
||||
}
|
||||
};
|
||||
|
||||
if (&target_instruction == entry_) {
|
||||
check_entry_instruction();
|
||||
}
|
||||
|
||||
Set<const MFInstruction *> checked_instructions;
|
||||
Stack<const MFInstruction *> instructions_to_check;
|
||||
instructions_to_check.push_multiple(target_instruction.prev_);
|
||||
|
||||
while (!instructions_to_check.is_empty()) {
|
||||
const MFInstruction &instruction = *instructions_to_check.pop();
|
||||
if (!checked_instructions.add(&instruction)) {
|
||||
/* Skip if the instruction has been checked already. */
|
||||
continue;
|
||||
}
|
||||
bool state_modified = false;
|
||||
switch (instruction.type_) {
|
||||
case MFInstructionType::Call: {
|
||||
const MFCallInstruction &call_instruction = static_cast<const MFCallInstruction &>(
|
||||
instruction);
|
||||
const MultiFunction &fn = *call_instruction.fn_;
|
||||
for (const int param_index : fn.param_indices()) {
|
||||
if (call_instruction.params_[param_index] == &target_variable) {
|
||||
const MFParamType param_type = fn.param_type(param_index);
|
||||
if (param_type.interface_type() == MFParamType::Output) {
|
||||
state.can_be_initialized = true;
|
||||
state_modified = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Destruct: {
|
||||
const MFDestructInstruction &destruct_instruction =
|
||||
static_cast<const MFDestructInstruction &>(instruction);
|
||||
if (destruct_instruction.variable_ == &target_variable) {
|
||||
state.can_be_uninitialized = true;
|
||||
state_modified = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Branch:
|
||||
case MFInstructionType::Dummy:
|
||||
case MFInstructionType::Return: {
|
||||
/* These instruction types don't change the initialization state of variables. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!state_modified) {
|
||||
if (&instruction == entry_) {
|
||||
check_entry_instruction();
|
||||
}
|
||||
instructions_to_check.push_multiple(instruction.prev_);
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
class MFProcedureDotExport {
|
||||
private:
|
||||
const MFProcedure &procedure_;
|
||||
dot::DirectedGraph digraph_;
|
||||
Map<const MFInstruction *, dot::Node *> dot_nodes_by_begin_;
|
||||
Map<const MFInstruction *, dot::Node *> dot_nodes_by_end_;
|
||||
|
||||
public:
|
||||
MFProcedureDotExport(const MFProcedure &procedure) : procedure_(procedure)
|
||||
{
|
||||
}
|
||||
|
||||
std::string generate()
|
||||
{
|
||||
this->create_nodes();
|
||||
this->create_edges();
|
||||
return digraph_.to_dot_string();
|
||||
}
|
||||
|
||||
void create_nodes()
|
||||
{
|
||||
Vector<const MFInstruction *> all_instructions;
|
||||
auto add_instructions = [&](auto instructions) {
|
||||
all_instructions.extend(instructions.begin(), instructions.end());
|
||||
};
|
||||
add_instructions(procedure_.call_instructions_);
|
||||
add_instructions(procedure_.branch_instructions_);
|
||||
add_instructions(procedure_.destruct_instructions_);
|
||||
add_instructions(procedure_.dummy_instructions_);
|
||||
add_instructions(procedure_.return_instructions_);
|
||||
|
||||
Set<const MFInstruction *> handled_instructions;
|
||||
|
||||
for (const MFInstruction *representative : all_instructions) {
|
||||
if (handled_instructions.contains(representative)) {
|
||||
continue;
|
||||
}
|
||||
Vector<const MFInstruction *> block_instructions = this->get_instructions_in_block(
|
||||
*representative);
|
||||
std::stringstream ss;
|
||||
ss << "<";
|
||||
|
||||
for (const MFInstruction *current : block_instructions) {
|
||||
handled_instructions.add_new(current);
|
||||
switch (current->type()) {
|
||||
case MFInstructionType::Call: {
|
||||
this->instruction_to_string(*static_cast<const MFCallInstruction *>(current), ss);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Destruct: {
|
||||
this->instruction_to_string(*static_cast<const MFDestructInstruction *>(current), ss);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Dummy: {
|
||||
this->instruction_to_string(*static_cast<const MFDummyInstruction *>(current), ss);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Return: {
|
||||
this->instruction_to_string(*static_cast<const MFReturnInstruction *>(current), ss);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Branch: {
|
||||
this->instruction_to_string(*static_cast<const MFBranchInstruction *>(current), ss);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ss << R"(<br align="left" />)";
|
||||
}
|
||||
ss << ">";
|
||||
|
||||
dot::Node &dot_node = digraph_.new_node(ss.str());
|
||||
dot_node.set_shape(dot::Attr_shape::Rectangle);
|
||||
dot_nodes_by_begin_.add_new(block_instructions.first(), &dot_node);
|
||||
dot_nodes_by_end_.add_new(block_instructions.last(), &dot_node);
|
||||
}
|
||||
}
|
||||
|
||||
void create_edges()
|
||||
{
|
||||
auto create_edge = [&](dot::Node &from_node,
|
||||
const MFInstruction *to_instruction) -> dot::DirectedEdge & {
|
||||
if (to_instruction == nullptr) {
|
||||
dot::Node &to_node = digraph_.new_node("missing");
|
||||
to_node.set_shape(dot::Attr_shape::Diamond);
|
||||
return digraph_.new_edge(from_node, to_node);
|
||||
}
|
||||
dot::Node &to_node = *dot_nodes_by_begin_.lookup(to_instruction);
|
||||
return digraph_.new_edge(from_node, to_node);
|
||||
};
|
||||
|
||||
for (auto item : dot_nodes_by_end_.items()) {
|
||||
const MFInstruction &from_instruction = *item.key;
|
||||
dot::Node &from_node = *item.value;
|
||||
switch (from_instruction.type()) {
|
||||
case MFInstructionType::Call: {
|
||||
const MFInstruction *to_instruction =
|
||||
static_cast<const MFCallInstruction &>(from_instruction).next();
|
||||
create_edge(from_node, to_instruction);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Destruct: {
|
||||
const MFInstruction *to_instruction =
|
||||
static_cast<const MFDestructInstruction &>(from_instruction).next();
|
||||
create_edge(from_node, to_instruction);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Dummy: {
|
||||
const MFInstruction *to_instruction =
|
||||
static_cast<const MFDummyInstruction &>(from_instruction).next();
|
||||
create_edge(from_node, to_instruction);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Return: {
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Branch: {
|
||||
const MFBranchInstruction &branch_instruction = static_cast<const MFBranchInstruction &>(
|
||||
from_instruction);
|
||||
const MFInstruction *to_true_instruction = branch_instruction.branch_true();
|
||||
const MFInstruction *to_false_instruction = branch_instruction.branch_false();
|
||||
create_edge(from_node, to_true_instruction).attributes.set("color", "#118811");
|
||||
create_edge(from_node, to_false_instruction).attributes.set("color", "#881111");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dot::Node &entry_node = this->create_entry_node();
|
||||
create_edge(entry_node, procedure_.entry());
|
||||
}
|
||||
|
||||
bool has_to_be_block_begin(const MFInstruction &instruction)
|
||||
{
|
||||
if (procedure_.entry() == &instruction) {
|
||||
return true;
|
||||
}
|
||||
if (instruction.prev().size() != 1) {
|
||||
return true;
|
||||
}
|
||||
if (instruction.prev()[0]->type() == MFInstructionType::Branch) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const MFInstruction &get_first_instruction_in_block(const MFInstruction &representative)
|
||||
{
|
||||
const MFInstruction *current = &representative;
|
||||
while (!this->has_to_be_block_begin(*current)) {
|
||||
current = current->prev()[0];
|
||||
if (current == &representative) {
|
||||
/* There is a loop without entry or exit, just break it up here. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
return *current;
|
||||
}
|
||||
|
||||
const MFInstruction *get_next_instruction_in_block(const MFInstruction &instruction,
|
||||
const MFInstruction &block_begin)
|
||||
{
|
||||
const MFInstruction *next = nullptr;
|
||||
switch (instruction.type()) {
|
||||
case MFInstructionType::Call: {
|
||||
next = static_cast<const MFCallInstruction &>(instruction).next();
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Destruct: {
|
||||
next = static_cast<const MFDestructInstruction &>(instruction).next();
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Dummy: {
|
||||
next = static_cast<const MFDummyInstruction &>(instruction).next();
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Return:
|
||||
case MFInstructionType::Branch: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (next == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
if (next == &block_begin) {
|
||||
return nullptr;
|
||||
}
|
||||
if (this->has_to_be_block_begin(*next)) {
|
||||
return nullptr;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
Vector<const MFInstruction *> get_instructions_in_block(const MFInstruction &representative)
|
||||
{
|
||||
Vector<const MFInstruction *> instructions;
|
||||
const MFInstruction &begin = this->get_first_instruction_in_block(representative);
|
||||
for (const MFInstruction *current = &begin; current != nullptr;
|
||||
current = this->get_next_instruction_in_block(*current, begin)) {
|
||||
instructions.append(current);
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
|
||||
void variable_to_string(const MFVariable *variable, std::stringstream &ss)
|
||||
{
|
||||
if (variable == nullptr) {
|
||||
ss << "null";
|
||||
}
|
||||
else {
|
||||
ss << "$" << variable->id();
|
||||
if (!variable->name().is_empty()) {
|
||||
ss << "(" << variable->name() << ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void instruction_name_format(StringRef name, std::stringstream &ss)
|
||||
{
|
||||
ss << name;
|
||||
}
|
||||
|
||||
void instruction_to_string(const MFCallInstruction &instruction, std::stringstream &ss)
|
||||
{
|
||||
const MultiFunction &fn = instruction.fn();
|
||||
this->instruction_name_format(fn.name() + ": ", ss);
|
||||
for (const int param_index : fn.param_indices()) {
|
||||
const MFParamType param_type = fn.param_type(param_index);
|
||||
const MFVariable *variable = instruction.params()[param_index];
|
||||
ss << R"(<font color="grey30">)";
|
||||
switch (param_type.interface_type()) {
|
||||
case MFParamType::Input: {
|
||||
ss << "in";
|
||||
break;
|
||||
}
|
||||
case MFParamType::Mutable: {
|
||||
ss << "mut";
|
||||
break;
|
||||
}
|
||||
case MFParamType::Output: {
|
||||
ss << "out";
|
||||
break;
|
||||
}
|
||||
}
|
||||
ss << " </font> ";
|
||||
variable_to_string(variable, ss);
|
||||
if (param_index < fn.param_amount() - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void instruction_to_string(const MFDestructInstruction &instruction, std::stringstream &ss)
|
||||
{
|
||||
instruction_name_format("Destruct ", ss);
|
||||
variable_to_string(instruction.variable(), ss);
|
||||
}
|
||||
|
||||
void instruction_to_string(const MFDummyInstruction &UNUSED(instruction), std::stringstream &ss)
|
||||
{
|
||||
instruction_name_format("Dummy ", ss);
|
||||
}
|
||||
|
||||
void instruction_to_string(const MFReturnInstruction &UNUSED(instruction), std::stringstream &ss)
|
||||
{
|
||||
instruction_name_format("Return ", ss);
|
||||
|
||||
Vector<ConstMFParameter> outgoing_parameters;
|
||||
for (const ConstMFParameter ¶m : procedure_.params()) {
|
||||
if (ELEM(param.type, MFParamType::Mutable, MFParamType::Output)) {
|
||||
outgoing_parameters.append(param);
|
||||
}
|
||||
}
|
||||
for (const int param_index : outgoing_parameters.index_range()) {
|
||||
const ConstMFParameter ¶m = outgoing_parameters[param_index];
|
||||
variable_to_string(param.variable, ss);
|
||||
if (param_index < outgoing_parameters.size() - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void instruction_to_string(const MFBranchInstruction &instruction, std::stringstream &ss)
|
||||
{
|
||||
instruction_name_format("Branch ", ss);
|
||||
variable_to_string(instruction.condition(), ss);
|
||||
}
|
||||
|
||||
dot::Node &create_entry_node()
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Entry: ";
|
||||
Vector<ConstMFParameter> incoming_parameters;
|
||||
for (const ConstMFParameter ¶m : procedure_.params()) {
|
||||
if (ELEM(param.type, MFParamType::Input, MFParamType::Mutable)) {
|
||||
incoming_parameters.append(param);
|
||||
}
|
||||
}
|
||||
for (const int param_index : incoming_parameters.index_range()) {
|
||||
const ConstMFParameter ¶m = incoming_parameters[param_index];
|
||||
variable_to_string(param.variable, ss);
|
||||
if (param_index < incoming_parameters.size() - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
}
|
||||
|
||||
dot::Node &node = digraph_.new_node(ss.str());
|
||||
node.set_shape(dot::Attr_shape::Ellipse);
|
||||
return node;
|
||||
}
|
||||
};
|
||||
|
||||
std::string MFProcedure::to_dot() const
|
||||
{
|
||||
MFProcedureDotExport dot_export{*this};
|
||||
return dot_export.generate();
|
||||
}
|
||||
|
||||
} // namespace blender::fn
|
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "FN_multi_function_procedure_builder.hh"
|
||||
|
||||
namespace blender::fn {
|
||||
|
||||
void MFInstructionCursor::insert(MFProcedure &procedure, MFInstruction *new_instruction)
|
||||
{
|
||||
if (instruction_ == nullptr) {
|
||||
if (is_entry_) {
|
||||
procedure.set_entry(*new_instruction);
|
||||
}
|
||||
else {
|
||||
/* The cursors points at nothing, nothing to do. */
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (instruction_->type()) {
|
||||
case MFInstructionType::Call: {
|
||||
static_cast<MFCallInstruction *>(instruction_)->set_next(new_instruction);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Branch: {
|
||||
MFBranchInstruction &branch_instruction = *static_cast<MFBranchInstruction *>(
|
||||
instruction_);
|
||||
if (branch_output_) {
|
||||
branch_instruction.set_branch_true(new_instruction);
|
||||
}
|
||||
else {
|
||||
branch_instruction.set_branch_false(new_instruction);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Destruct: {
|
||||
static_cast<MFDestructInstruction *>(instruction_)->set_next(new_instruction);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Dummy: {
|
||||
static_cast<MFDummyInstruction *>(instruction_)->set_next(new_instruction);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Return: {
|
||||
/* It shouldn't be possible to build a cursor that points to a return instruction. */
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MFProcedureBuilder::add_destruct(MFVariable &variable)
|
||||
{
|
||||
MFDestructInstruction &instruction = procedure_->new_destruct_instruction();
|
||||
instruction.set_variable(&variable);
|
||||
this->link_to_cursors(&instruction);
|
||||
cursors_ = {MFInstructionCursor{instruction}};
|
||||
}
|
||||
|
||||
void MFProcedureBuilder::add_destruct(Span<MFVariable *> variables)
|
||||
{
|
||||
for (MFVariable *variable : variables) {
|
||||
this->add_destruct(*variable);
|
||||
}
|
||||
}
|
||||
|
||||
MFReturnInstruction &MFProcedureBuilder::add_return()
|
||||
{
|
||||
MFReturnInstruction &instruction = procedure_->new_return_instruction();
|
||||
this->link_to_cursors(&instruction);
|
||||
cursors_ = {};
|
||||
return instruction;
|
||||
}
|
||||
|
||||
MFCallInstruction &MFProcedureBuilder::add_call_with_no_variables(const MultiFunction &fn)
|
||||
{
|
||||
MFCallInstruction &instruction = procedure_->new_call_instruction(fn);
|
||||
this->link_to_cursors(&instruction);
|
||||
cursors_ = {MFInstructionCursor{instruction}};
|
||||
return instruction;
|
||||
}
|
||||
|
||||
MFCallInstruction &MFProcedureBuilder::add_call_with_all_variables(
|
||||
const MultiFunction &fn, Span<MFVariable *> param_variables)
|
||||
{
|
||||
MFCallInstruction &instruction = this->add_call_with_no_variables(fn);
|
||||
instruction.set_params(param_variables);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
Vector<MFVariable *> MFProcedureBuilder::add_call(const MultiFunction &fn,
|
||||
Span<MFVariable *> input_and_mutable_variables)
|
||||
{
|
||||
Vector<MFVariable *> output_variables;
|
||||
MFCallInstruction &instruction = this->add_call_with_no_variables(fn);
|
||||
for (const int param_index : fn.param_indices()) {
|
||||
const MFParamType param_type = fn.param_type(param_index);
|
||||
switch (param_type.interface_type()) {
|
||||
case MFParamType::Input:
|
||||
case MFParamType::Mutable: {
|
||||
MFVariable *variable = input_and_mutable_variables.first();
|
||||
instruction.set_param_variable(param_index, variable);
|
||||
input_and_mutable_variables = input_and_mutable_variables.drop_front(1);
|
||||
break;
|
||||
}
|
||||
case MFParamType::Output: {
|
||||
MFVariable &variable = procedure_->new_variable(param_type.data_type(),
|
||||
fn.param_name(param_index));
|
||||
instruction.set_param_variable(param_index, &variable);
|
||||
output_variables.append(&variable);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* All passed in variables should have been dropped in the loop above. */
|
||||
BLI_assert(input_and_mutable_variables.is_empty());
|
||||
return output_variables;
|
||||
}
|
||||
|
||||
MFProcedureBuilder::Branch MFProcedureBuilder::add_branch(MFVariable &condition)
|
||||
{
|
||||
MFBranchInstruction &instruction = procedure_->new_branch_instruction();
|
||||
instruction.set_condition(&condition);
|
||||
this->link_to_cursors(&instruction);
|
||||
/* Clear cursors because this builder ends here. */
|
||||
cursors_.clear();
|
||||
|
||||
Branch branch{*procedure_, *procedure_};
|
||||
branch.branch_true.set_cursor(MFInstructionCursor{instruction, true});
|
||||
branch.branch_false.set_cursor(MFInstructionCursor{instruction, false});
|
||||
return branch;
|
||||
}
|
||||
|
||||
MFProcedureBuilder::Loop MFProcedureBuilder::add_loop()
|
||||
{
|
||||
MFDummyInstruction &loop_begin = procedure_->new_dummy_instruction();
|
||||
MFDummyInstruction &loop_end = procedure_->new_dummy_instruction();
|
||||
this->link_to_cursors(&loop_begin);
|
||||
cursors_ = {MFInstructionCursor{loop_begin}};
|
||||
|
||||
Loop loop;
|
||||
loop.begin = &loop_begin;
|
||||
loop.end = &loop_end;
|
||||
|
||||
return loop;
|
||||
}
|
||||
|
||||
void MFProcedureBuilder::add_loop_continue(Loop &loop)
|
||||
{
|
||||
this->link_to_cursors(loop.begin);
|
||||
/* Clear cursors because this builder ends here. */
|
||||
cursors_.clear();
|
||||
}
|
||||
|
||||
void MFProcedureBuilder::add_loop_break(Loop &loop)
|
||||
{
|
||||
this->link_to_cursors(loop.end);
|
||||
/* Clear cursors because this builder ends here. */
|
||||
cursors_.clear();
|
||||
}
|
||||
|
||||
} // namespace blender::fn
|
1212
source/blender/functions/intern/multi_function_procedure_executor.cc
Normal file
1212
source/blender/functions/intern/multi_function_procedure_executor.cc
Normal file
File diff suppressed because it is too large
Load Diff
278
source/blender/functions/tests/FN_field_test.cc
Normal file
278
source/blender/functions/tests/FN_field_test.cc
Normal file
@@ -0,0 +1,278 @@
|
||||
/* Apache License, Version 2.0 */
|
||||
|
||||
#include "testing/testing.h"
|
||||
|
||||
#include "FN_cpp_type.hh"
|
||||
#include "FN_field.hh"
|
||||
#include "FN_multi_function_builder.hh"
|
||||
|
||||
namespace blender::fn::tests {
|
||||
|
||||
TEST(field, ConstantFunction)
|
||||
{
|
||||
/* TODO: Figure out how to not use another "FieldOperation(" inside of std::make_shared. */
|
||||
GField constant_field{std::make_shared<FieldOperation>(
|
||||
FieldOperation(std::make_unique<CustomMF_Constant<int>>(10), {})),
|
||||
0};
|
||||
|
||||
Array<int> result(4);
|
||||
|
||||
FieldContext context;
|
||||
FieldEvaluator evaluator{context, 4};
|
||||
evaluator.add_with_destination(constant_field, result.as_mutable_span());
|
||||
evaluator.evaluate();
|
||||
EXPECT_EQ(result[0], 10);
|
||||
EXPECT_EQ(result[1], 10);
|
||||
EXPECT_EQ(result[2], 10);
|
||||
EXPECT_EQ(result[3], 10);
|
||||
}
|
||||
|
||||
class IndexFieldInput final : public FieldInput {
|
||||
public:
|
||||
IndexFieldInput() : FieldInput(CPPType::get<int>(), "Index")
|
||||
{
|
||||
}
|
||||
|
||||
const GVArray *get_varray_for_context(const FieldContext &UNUSED(context),
|
||||
IndexMask mask,
|
||||
ResourceScope &scope) const final
|
||||
{
|
||||
auto index_func = [](int i) { return i; };
|
||||
return &scope.construct<
|
||||
GVArray_For_EmbeddedVArray<int, VArray_For_Func<int, decltype(index_func)>>>(
|
||||
__func__, mask.min_array_size(), mask.min_array_size(), index_func);
|
||||
}
|
||||
};
|
||||
|
||||
TEST(field, VArrayInput)
|
||||
{
|
||||
GField index_field{std::make_shared<IndexFieldInput>()};
|
||||
|
||||
Array<int> result_1(4);
|
||||
|
||||
FieldContext context;
|
||||
FieldEvaluator evaluator{context, 4};
|
||||
evaluator.add_with_destination(index_field, result_1.as_mutable_span());
|
||||
evaluator.evaluate();
|
||||
EXPECT_EQ(result_1[0], 0);
|
||||
EXPECT_EQ(result_1[1], 1);
|
||||
EXPECT_EQ(result_1[2], 2);
|
||||
EXPECT_EQ(result_1[3], 3);
|
||||
|
||||
/* Evaluate a second time, just to test that the first didn't break anything. */
|
||||
Array<int> result_2(10);
|
||||
|
||||
const Array<int64_t> indices = {2, 4, 6, 8};
|
||||
const IndexMask mask{indices};
|
||||
|
||||
FieldEvaluator evaluator_2{context, &mask};
|
||||
evaluator_2.add_with_destination(index_field, result_2.as_mutable_span());
|
||||
evaluator_2.evaluate();
|
||||
EXPECT_EQ(result_2[2], 2);
|
||||
EXPECT_EQ(result_2[4], 4);
|
||||
EXPECT_EQ(result_2[6], 6);
|
||||
EXPECT_EQ(result_2[8], 8);
|
||||
}
|
||||
|
||||
TEST(field, VArrayInputMultipleOutputs)
|
||||
{
|
||||
std::shared_ptr<FieldInput> index_input = std::make_shared<IndexFieldInput>();
|
||||
GField field_1{index_input};
|
||||
GField field_2{index_input};
|
||||
|
||||
Array<int> result_1(10);
|
||||
Array<int> result_2(10);
|
||||
|
||||
const Array<int64_t> indices = {2, 4, 6, 8};
|
||||
const IndexMask mask{indices};
|
||||
|
||||
FieldContext context;
|
||||
FieldEvaluator evaluator{context, &mask};
|
||||
evaluator.add_with_destination(field_1, result_1.as_mutable_span());
|
||||
evaluator.add_with_destination(field_2, result_2.as_mutable_span());
|
||||
evaluator.evaluate();
|
||||
EXPECT_EQ(result_1[2], 2);
|
||||
EXPECT_EQ(result_1[4], 4);
|
||||
EXPECT_EQ(result_1[6], 6);
|
||||
EXPECT_EQ(result_1[8], 8);
|
||||
EXPECT_EQ(result_2[2], 2);
|
||||
EXPECT_EQ(result_2[4], 4);
|
||||
EXPECT_EQ(result_2[6], 6);
|
||||
EXPECT_EQ(result_2[8], 8);
|
||||
}
|
||||
|
||||
TEST(field, InputAndFunction)
|
||||
{
|
||||
GField index_field{std::make_shared<IndexFieldInput>()};
|
||||
|
||||
std::unique_ptr<MultiFunction> add_fn = std::make_unique<CustomMF_SI_SI_SO<int, int, int>>(
|
||||
"add", [](int a, int b) { return a + b; });
|
||||
GField output_field{std::make_shared<FieldOperation>(
|
||||
FieldOperation(std::move(add_fn), {index_field, index_field})),
|
||||
0};
|
||||
|
||||
Array<int> result(10);
|
||||
|
||||
const Array<int64_t> indices = {2, 4, 6, 8};
|
||||
const IndexMask mask{indices};
|
||||
|
||||
FieldContext context;
|
||||
FieldEvaluator evaluator{context, &mask};
|
||||
evaluator.add_with_destination(output_field, result.as_mutable_span());
|
||||
evaluator.evaluate();
|
||||
EXPECT_EQ(result[2], 4);
|
||||
EXPECT_EQ(result[4], 8);
|
||||
EXPECT_EQ(result[6], 12);
|
||||
EXPECT_EQ(result[8], 16);
|
||||
}
|
||||
|
||||
TEST(field, TwoFunctions)
|
||||
{
|
||||
GField index_field{std::make_shared<IndexFieldInput>()};
|
||||
|
||||
std::unique_ptr<MultiFunction> add_fn = std::make_unique<CustomMF_SI_SI_SO<int, int, int>>(
|
||||
"add", [](int a, int b) { return a + b; });
|
||||
GField add_field{std::make_shared<FieldOperation>(
|
||||
FieldOperation(std::move(add_fn), {index_field, index_field})),
|
||||
0};
|
||||
|
||||
std::unique_ptr<MultiFunction> add_10_fn = std::make_unique<CustomMF_SI_SO<int, int>>(
|
||||
"add_10", [](int a) { return a + 10; });
|
||||
GField result_field{
|
||||
std::make_shared<FieldOperation>(FieldOperation(std::move(add_10_fn), {add_field})), 0};
|
||||
|
||||
Array<int> result(10);
|
||||
|
||||
const Array<int64_t> indices = {2, 4, 6, 8};
|
||||
const IndexMask mask{indices};
|
||||
|
||||
FieldContext context;
|
||||
FieldEvaluator evaluator{context, &mask};
|
||||
evaluator.add_with_destination(result_field, result.as_mutable_span());
|
||||
evaluator.evaluate();
|
||||
EXPECT_EQ(result[2], 14);
|
||||
EXPECT_EQ(result[4], 18);
|
||||
EXPECT_EQ(result[6], 22);
|
||||
EXPECT_EQ(result[8], 26);
|
||||
}
|
||||
|
||||
class TwoOutputFunction : public MultiFunction {
|
||||
private:
|
||||
MFSignature signature_;
|
||||
|
||||
public:
|
||||
TwoOutputFunction(StringRef name)
|
||||
{
|
||||
MFSignatureBuilder signature{name};
|
||||
signature.single_input<int>("In1");
|
||||
signature.single_input<int>("In2");
|
||||
signature.single_output<int>("Add");
|
||||
signature.single_output<int>("Add10");
|
||||
signature_ = signature.build();
|
||||
this->set_signature(&signature_);
|
||||
}
|
||||
|
||||
void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
|
||||
{
|
||||
const VArray<int> &in1 = params.readonly_single_input<int>(0, "In1");
|
||||
const VArray<int> &in2 = params.readonly_single_input<int>(1, "In2");
|
||||
MutableSpan<int> add = params.uninitialized_single_output<int>(2, "Add");
|
||||
MutableSpan<int> add_10 = params.uninitialized_single_output<int>(3, "Add10");
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
add[i] = in1[i] + in2[i];
|
||||
add_10[i] = add[i] + 10;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
TEST(field, FunctionTwoOutputs)
|
||||
{
|
||||
/* Also use two separate input fields, why not. */
|
||||
GField index_field_1{std::make_shared<IndexFieldInput>()};
|
||||
GField index_field_2{std::make_shared<IndexFieldInput>()};
|
||||
|
||||
std::shared_ptr<FieldOperation> fn = std::make_shared<FieldOperation>(FieldOperation(
|
||||
std::make_unique<TwoOutputFunction>("SI_SI_SO_SO"), {index_field_1, index_field_2}));
|
||||
|
||||
GField result_field_1{fn, 0};
|
||||
GField result_field_2{fn, 1};
|
||||
|
||||
Array<int> result_1(10);
|
||||
Array<int> result_2(10);
|
||||
|
||||
const Array<int64_t> indices = {2, 4, 6, 8};
|
||||
const IndexMask mask{indices};
|
||||
|
||||
FieldContext context;
|
||||
FieldEvaluator evaluator{context, &mask};
|
||||
evaluator.add_with_destination(result_field_1, result_1.as_mutable_span());
|
||||
evaluator.add_with_destination(result_field_2, result_2.as_mutable_span());
|
||||
evaluator.evaluate();
|
||||
EXPECT_EQ(result_1[2], 4);
|
||||
EXPECT_EQ(result_1[4], 8);
|
||||
EXPECT_EQ(result_1[6], 12);
|
||||
EXPECT_EQ(result_1[8], 16);
|
||||
EXPECT_EQ(result_2[2], 14);
|
||||
EXPECT_EQ(result_2[4], 18);
|
||||
EXPECT_EQ(result_2[6], 22);
|
||||
EXPECT_EQ(result_2[8], 26);
|
||||
}
|
||||
|
||||
TEST(field, TwoFunctionsTwoOutputs)
|
||||
{
|
||||
GField index_field{std::make_shared<IndexFieldInput>()};
|
||||
|
||||
std::shared_ptr<FieldOperation> fn = std::make_shared<FieldOperation>(FieldOperation(
|
||||
std::make_unique<TwoOutputFunction>("SI_SI_SO_SO"), {index_field, index_field}));
|
||||
|
||||
Array<int64_t> mask_indices = {2, 4, 6, 8};
|
||||
IndexMask mask = mask_indices.as_span();
|
||||
|
||||
Field<int> result_field_1{fn, 0};
|
||||
Field<int> intermediate_field{fn, 1};
|
||||
|
||||
std::unique_ptr<MultiFunction> add_10_fn = std::make_unique<CustomMF_SI_SO<int, int>>(
|
||||
"add_10", [](int a) { return a + 10; });
|
||||
Field<int> result_field_2{
|
||||
std::make_shared<FieldOperation>(FieldOperation(std::move(add_10_fn), {intermediate_field})),
|
||||
0};
|
||||
|
||||
FieldContext field_context;
|
||||
FieldEvaluator field_evaluator{field_context, &mask};
|
||||
const VArray<int> *result_1 = nullptr;
|
||||
const VArray<int> *result_2 = nullptr;
|
||||
field_evaluator.add(result_field_1, &result_1);
|
||||
field_evaluator.add(result_field_2, &result_2);
|
||||
field_evaluator.evaluate();
|
||||
|
||||
EXPECT_EQ(result_1->get(2), 4);
|
||||
EXPECT_EQ(result_1->get(4), 8);
|
||||
EXPECT_EQ(result_1->get(6), 12);
|
||||
EXPECT_EQ(result_1->get(8), 16);
|
||||
EXPECT_EQ(result_2->get(2), 24);
|
||||
EXPECT_EQ(result_2->get(4), 28);
|
||||
EXPECT_EQ(result_2->get(6), 32);
|
||||
EXPECT_EQ(result_2->get(8), 36);
|
||||
}
|
||||
|
||||
TEST(field, SameFieldTwice)
|
||||
{
|
||||
GField constant_field{
|
||||
std::make_shared<FieldOperation>(std::make_unique<CustomMF_Constant<int>>(10)), 0};
|
||||
|
||||
FieldContext field_context;
|
||||
IndexMask mask{IndexRange(2)};
|
||||
ResourceScope scope;
|
||||
Vector<const GVArray *> results = evaluate_fields(
|
||||
scope, {constant_field, constant_field}, mask, field_context);
|
||||
|
||||
GVArray_Typed<int> varray1{*results[0]};
|
||||
GVArray_Typed<int> varray2{*results[1]};
|
||||
|
||||
EXPECT_EQ(varray1->get(0), 10);
|
||||
EXPECT_EQ(varray1->get(1), 10);
|
||||
EXPECT_EQ(varray2->get(0), 10);
|
||||
EXPECT_EQ(varray2->get(1), 10);
|
||||
}
|
||||
|
||||
} // namespace blender::fn::tests
|
@@ -0,0 +1,344 @@
|
||||
/* Apache License, Version 2.0 */
|
||||
|
||||
#include "testing/testing.h"
|
||||
|
||||
#include "FN_multi_function_builder.hh"
|
||||
#include "FN_multi_function_procedure_builder.hh"
|
||||
#include "FN_multi_function_procedure_executor.hh"
|
||||
#include "FN_multi_function_test_common.hh"
|
||||
|
||||
namespace blender::fn::tests {
|
||||
|
||||
TEST(multi_function_procedure, SimpleTest)
|
||||
{
|
||||
/**
|
||||
* procedure(int var1, int var2, int *var4) {
|
||||
* int var3 = var1 + var2;
|
||||
* var4 = var2 + var3;
|
||||
* var4 += 10;
|
||||
* }
|
||||
*/
|
||||
|
||||
CustomMF_SI_SI_SO<int, int, int> add_fn{"add", [](int a, int b) { return a + b; }};
|
||||
CustomMF_SM<int> add_10_fn{"add_10", [](int &a) { a += 10; }};
|
||||
|
||||
MFProcedure procedure;
|
||||
MFProcedureBuilder builder{procedure};
|
||||
|
||||
MFVariable *var1 = &builder.add_single_input_parameter<int>();
|
||||
MFVariable *var2 = &builder.add_single_input_parameter<int>();
|
||||
auto [var3] = builder.add_call<1>(add_fn, {var1, var2});
|
||||
auto [var4] = builder.add_call<1>(add_fn, {var2, var3});
|
||||
builder.add_call(add_10_fn, {var4});
|
||||
builder.add_destruct({var1, var2, var3});
|
||||
builder.add_return();
|
||||
builder.add_output_parameter(*var4);
|
||||
|
||||
EXPECT_TRUE(procedure.validate());
|
||||
|
||||
MFProcedureExecutor executor{"My Procedure", procedure};
|
||||
|
||||
MFParamsBuilder params{executor, 3};
|
||||
MFContextBuilder context;
|
||||
|
||||
Array<int> input_array = {1, 2, 3};
|
||||
params.add_readonly_single_input(input_array.as_span());
|
||||
params.add_readonly_single_input_value(3);
|
||||
|
||||
Array<int> output_array(3);
|
||||
params.add_uninitialized_single_output(output_array.as_mutable_span());
|
||||
|
||||
executor.call(IndexRange(3), params, context);
|
||||
|
||||
EXPECT_EQ(output_array[0], 17);
|
||||
EXPECT_EQ(output_array[1], 18);
|
||||
EXPECT_EQ(output_array[2], 19);
|
||||
}
|
||||
|
||||
TEST(multi_function_procedure, BranchTest)
|
||||
{
|
||||
/**
|
||||
* procedure(int &var1, bool var2) {
|
||||
* if (var2) {
|
||||
* var1 += 100;
|
||||
* }
|
||||
* else {
|
||||
* var1 += 10;
|
||||
* }
|
||||
* var1 += 10;
|
||||
* }
|
||||
*/
|
||||
|
||||
CustomMF_SM<int> add_10_fn{"add_10", [](int &a) { a += 10; }};
|
||||
CustomMF_SM<int> add_100_fn{"add_100", [](int &a) { a += 100; }};
|
||||
|
||||
MFProcedure procedure;
|
||||
MFProcedureBuilder builder{procedure};
|
||||
|
||||
MFVariable *var1 = &builder.add_single_mutable_parameter<int>();
|
||||
MFVariable *var2 = &builder.add_single_input_parameter<bool>();
|
||||
|
||||
MFProcedureBuilder::Branch branch = builder.add_branch(*var2);
|
||||
branch.branch_false.add_call(add_10_fn, {var1});
|
||||
branch.branch_true.add_call(add_100_fn, {var1});
|
||||
builder.set_cursor_after_branch(branch);
|
||||
builder.add_call(add_10_fn, {var1});
|
||||
builder.add_destruct({var2});
|
||||
builder.add_return();
|
||||
|
||||
EXPECT_TRUE(procedure.validate());
|
||||
|
||||
MFProcedureExecutor procedure_fn{"Condition Test", procedure};
|
||||
MFParamsBuilder params(procedure_fn, 5);
|
||||
|
||||
Array<int> values_a = {1, 5, 3, 6, 2};
|
||||
Array<bool> values_cond = {true, false, true, true, false};
|
||||
|
||||
params.add_single_mutable(values_a.as_mutable_span());
|
||||
params.add_readonly_single_input(values_cond.as_span());
|
||||
|
||||
MFContextBuilder context;
|
||||
procedure_fn.call({1, 2, 3, 4}, params, context);
|
||||
|
||||
EXPECT_EQ(values_a[0], 1);
|
||||
EXPECT_EQ(values_a[1], 25);
|
||||
EXPECT_EQ(values_a[2], 113);
|
||||
EXPECT_EQ(values_a[3], 116);
|
||||
EXPECT_EQ(values_a[4], 22);
|
||||
}
|
||||
|
||||
TEST(multi_function_procedure, EvaluateOne)
|
||||
{
|
||||
/**
|
||||
* procedure(int var1, int *var2) {
|
||||
* var2 = var1 + 10;
|
||||
* }
|
||||
*/
|
||||
|
||||
int tot_evaluations = 0;
|
||||
CustomMF_SI_SO<int, int> add_10_fn{"add_10", [&](int a) {
|
||||
tot_evaluations++;
|
||||
return a + 10;
|
||||
}};
|
||||
|
||||
MFProcedure procedure;
|
||||
MFProcedureBuilder builder{procedure};
|
||||
|
||||
MFVariable *var1 = &builder.add_single_input_parameter<int>();
|
||||
auto [var2] = builder.add_call<1>(add_10_fn, {var1});
|
||||
builder.add_destruct(*var1);
|
||||
builder.add_return();
|
||||
builder.add_output_parameter(*var2);
|
||||
|
||||
MFProcedureExecutor procedure_fn{"Evaluate One", procedure};
|
||||
MFParamsBuilder params{procedure_fn, 5};
|
||||
|
||||
Array<int> values_out = {1, 2, 3, 4, 5};
|
||||
params.add_readonly_single_input_value(1);
|
||||
params.add_uninitialized_single_output(values_out.as_mutable_span());
|
||||
|
||||
MFContextBuilder context;
|
||||
procedure_fn.call({0, 1, 3, 4}, params, context);
|
||||
|
||||
EXPECT_EQ(values_out[0], 11);
|
||||
EXPECT_EQ(values_out[1], 11);
|
||||
EXPECT_EQ(values_out[2], 3);
|
||||
EXPECT_EQ(values_out[3], 11);
|
||||
EXPECT_EQ(values_out[4], 11);
|
||||
/* We expect only one evaluation, because the input is constant. */
|
||||
EXPECT_EQ(tot_evaluations, 1);
|
||||
}
|
||||
|
||||
TEST(multi_function_procedure, SimpleLoop)
|
||||
{
|
||||
/**
|
||||
* procedure(int count, int *out) {
|
||||
* out = 1;
|
||||
* int index = 0'
|
||||
* loop {
|
||||
* if (index >= count) {
|
||||
* break;
|
||||
* }
|
||||
* out *= 2;
|
||||
* index += 1;
|
||||
* }
|
||||
* out += 1000;
|
||||
* }
|
||||
*/
|
||||
|
||||
CustomMF_Constant<int> const_1_fn{1};
|
||||
CustomMF_Constant<int> const_0_fn{0};
|
||||
CustomMF_SI_SI_SO<int, int, bool> greater_or_equal_fn{"greater or equal",
|
||||
[](int a, int b) { return a >= b; }};
|
||||
CustomMF_SM<int> double_fn{"double", [](int &a) { a *= 2; }};
|
||||
CustomMF_SM<int> add_1000_fn{"add 1000", [](int &a) { a += 1000; }};
|
||||
CustomMF_SM<int> add_1_fn{"add 1", [](int &a) { a += 1; }};
|
||||
|
||||
MFProcedure procedure;
|
||||
MFProcedureBuilder builder{procedure};
|
||||
|
||||
MFVariable *var_count = &builder.add_single_input_parameter<int>("count");
|
||||
auto [var_out] = builder.add_call<1>(const_1_fn);
|
||||
var_out->set_name("out");
|
||||
auto [var_index] = builder.add_call<1>(const_0_fn);
|
||||
var_index->set_name("index");
|
||||
|
||||
MFProcedureBuilder::Loop loop = builder.add_loop();
|
||||
auto [var_condition] = builder.add_call<1>(greater_or_equal_fn, {var_index, var_count});
|
||||
var_condition->set_name("condition");
|
||||
MFProcedureBuilder::Branch branch = builder.add_branch(*var_condition);
|
||||
branch.branch_true.add_destruct(*var_condition);
|
||||
branch.branch_true.add_loop_break(loop);
|
||||
branch.branch_false.add_destruct(*var_condition);
|
||||
builder.set_cursor_after_branch(branch);
|
||||
builder.add_call(double_fn, {var_out});
|
||||
builder.add_call(add_1_fn, {var_index});
|
||||
builder.add_loop_continue(loop);
|
||||
builder.set_cursor_after_loop(loop);
|
||||
builder.add_call(add_1000_fn, {var_out});
|
||||
builder.add_destruct({var_count, var_index});
|
||||
builder.add_return();
|
||||
builder.add_output_parameter(*var_out);
|
||||
|
||||
EXPECT_TRUE(procedure.validate());
|
||||
|
||||
MFProcedureExecutor procedure_fn{"Simple Loop", procedure};
|
||||
MFParamsBuilder params{procedure_fn, 5};
|
||||
|
||||
Array<int> counts = {4, 3, 7, 6, 4};
|
||||
Array<int> results(5, -1);
|
||||
|
||||
params.add_readonly_single_input(counts.as_span());
|
||||
params.add_uninitialized_single_output(results.as_mutable_span());
|
||||
|
||||
MFContextBuilder context;
|
||||
procedure_fn.call({0, 1, 3, 4}, params, context);
|
||||
|
||||
EXPECT_EQ(results[0], 1016);
|
||||
EXPECT_EQ(results[1], 1008);
|
||||
EXPECT_EQ(results[2], -1);
|
||||
EXPECT_EQ(results[3], 1064);
|
||||
EXPECT_EQ(results[4], 1016);
|
||||
}
|
||||
|
||||
TEST(multi_function_procedure, Vectors)
|
||||
{
|
||||
/**
|
||||
* procedure(vector<int> v1, vector<int> &v2, vector<int> *v3) {
|
||||
* v1.extend(v2);
|
||||
* int constant = 5;
|
||||
* v2.append(constant);
|
||||
* v2.extend(v1);
|
||||
* int len = sum(v2);
|
||||
* v3 = range(len);
|
||||
* }
|
||||
*/
|
||||
|
||||
CreateRangeFunction create_range_fn;
|
||||
ConcatVectorsFunction extend_fn;
|
||||
GenericAppendFunction append_fn{CPPType::get<int>()};
|
||||
SumVectorFunction sum_elements_fn;
|
||||
CustomMF_Constant<int> constant_5_fn{5};
|
||||
|
||||
MFProcedure procedure;
|
||||
MFProcedureBuilder builder{procedure};
|
||||
|
||||
MFVariable *var_v1 = &builder.add_input_parameter(MFDataType::ForVector<int>());
|
||||
MFVariable *var_v2 = &builder.add_parameter(MFParamType::ForMutableVector(CPPType::get<int>()));
|
||||
builder.add_call(extend_fn, {var_v1, var_v2});
|
||||
auto [var_constant] = builder.add_call<1>(constant_5_fn);
|
||||
builder.add_call(append_fn, {var_v2, var_constant});
|
||||
builder.add_destruct(*var_constant);
|
||||
builder.add_call(extend_fn, {var_v2, var_v1});
|
||||
auto [var_len] = builder.add_call<1>(sum_elements_fn, {var_v2});
|
||||
auto [var_v3] = builder.add_call<1>(create_range_fn, {var_len});
|
||||
builder.add_destruct({var_v1, var_len});
|
||||
builder.add_return();
|
||||
builder.add_output_parameter(*var_v3);
|
||||
|
||||
EXPECT_TRUE(procedure.validate());
|
||||
|
||||
MFProcedureExecutor procedure_fn{"Vectors", procedure};
|
||||
MFParamsBuilder params{procedure_fn, 5};
|
||||
|
||||
Array<int> v1 = {5, 2, 3};
|
||||
GVectorArray v2{CPPType::get<int>(), 5};
|
||||
GVectorArray v3{CPPType::get<int>(), 5};
|
||||
|
||||
int value_10 = 10;
|
||||
v2.append(0, &value_10);
|
||||
v2.append(4, &value_10);
|
||||
|
||||
params.add_readonly_vector_input(v1.as_span());
|
||||
params.add_vector_mutable(v2);
|
||||
params.add_vector_output(v3);
|
||||
|
||||
MFContextBuilder context;
|
||||
procedure_fn.call({0, 1, 3, 4}, params, context);
|
||||
|
||||
EXPECT_EQ(v2[0].size(), 6);
|
||||
EXPECT_EQ(v2[1].size(), 4);
|
||||
EXPECT_EQ(v2[2].size(), 0);
|
||||
EXPECT_EQ(v2[3].size(), 4);
|
||||
EXPECT_EQ(v2[4].size(), 6);
|
||||
|
||||
EXPECT_EQ(v3[0].size(), 35);
|
||||
EXPECT_EQ(v3[1].size(), 15);
|
||||
EXPECT_EQ(v3[2].size(), 0);
|
||||
EXPECT_EQ(v3[3].size(), 15);
|
||||
EXPECT_EQ(v3[4].size(), 35);
|
||||
}
|
||||
|
||||
TEST(multi_function_procedure, BufferReuse)
|
||||
{
|
||||
/**
|
||||
* procedure(int a, int *out) {
|
||||
* int b = a + 10;
|
||||
* int c = c + 10;
|
||||
* int d = d + 10;
|
||||
* int e = d + 10;
|
||||
* out = e + 10;
|
||||
* }
|
||||
*/
|
||||
|
||||
CustomMF_SI_SO<int, int> add_10_fn{"add 10", [](int a) { return a + 10; }};
|
||||
|
||||
MFProcedure procedure;
|
||||
MFProcedureBuilder builder{procedure};
|
||||
|
||||
MFVariable *var_a = &builder.add_single_input_parameter<int>();
|
||||
auto [var_b] = builder.add_call<1>(add_10_fn, {var_a});
|
||||
builder.add_destruct(*var_a);
|
||||
auto [var_c] = builder.add_call<1>(add_10_fn, {var_b});
|
||||
builder.add_destruct(*var_b);
|
||||
auto [var_d] = builder.add_call<1>(add_10_fn, {var_c});
|
||||
builder.add_destruct(*var_c);
|
||||
auto [var_e] = builder.add_call<1>(add_10_fn, {var_d});
|
||||
builder.add_destruct(*var_d);
|
||||
auto [var_out] = builder.add_call<1>(add_10_fn, {var_e});
|
||||
builder.add_destruct(*var_e);
|
||||
builder.add_return();
|
||||
builder.add_output_parameter(*var_out);
|
||||
|
||||
EXPECT_TRUE(procedure.validate());
|
||||
|
||||
MFProcedureExecutor procedure_fn{"Buffer Reuse", procedure};
|
||||
|
||||
Array<int> inputs = {4, 1, 6, 2, 3};
|
||||
Array<int> results(5, -1);
|
||||
|
||||
MFParamsBuilder params{procedure_fn, 5};
|
||||
params.add_readonly_single_input(inputs.as_span());
|
||||
params.add_uninitialized_single_output(results.as_mutable_span());
|
||||
|
||||
MFContextBuilder context;
|
||||
procedure_fn.call({0, 2, 3, 4}, params, context);
|
||||
|
||||
EXPECT_EQ(results[0], 54);
|
||||
EXPECT_EQ(results[1], -1);
|
||||
EXPECT_EQ(results[2], 56);
|
||||
EXPECT_EQ(results[3], 52);
|
||||
EXPECT_EQ(results[4], 53);
|
||||
}
|
||||
|
||||
} // namespace blender::fn::tests
|
@@ -31,6 +31,8 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct AnonymousAttributeID;
|
||||
|
||||
/** Descriptor and storage for a custom data layer. */
|
||||
typedef struct CustomDataLayer {
|
||||
/** Type of data in layer. */
|
||||
@@ -53,6 +55,13 @@ typedef struct CustomDataLayer {
|
||||
char name[64];
|
||||
/** 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.
|
||||
*/
|
||||
const struct AnonymousAttributeID *anonymous_id;
|
||||
} CustomDataLayer;
|
||||
|
||||
#define MAX_CUSTOMDATA_LAYER_NAME 64
|
||||
|
@@ -1441,6 +1441,13 @@ typedef struct NodeGeometryCurveFill {
|
||||
uint8_t mode;
|
||||
} NodeGeometryCurveFill;
|
||||
|
||||
typedef struct NodeGeometryAttributeCapture {
|
||||
/* CustomDataType. */
|
||||
int8_t data_type;
|
||||
/* AttributeDomain. */
|
||||
int8_t domain;
|
||||
} NodeGeometryAttributeCapture;
|
||||
|
||||
/* script node mode */
|
||||
#define NODE_SCRIPT_INTERNAL 0
|
||||
#define NODE_SCRIPT_EXTERNAL 1
|
||||
|
@@ -646,7 +646,8 @@ typedef struct UserDef_Experimental {
|
||||
char use_sculpt_tools_tilt;
|
||||
char use_extended_asset_browser;
|
||||
char use_override_templates;
|
||||
char _pad[5];
|
||||
char use_geometry_nodes_fields;
|
||||
char _pad[4];
|
||||
/** `makesdna` does not allow empty structs. */
|
||||
} UserDef_Experimental;
|
||||
|
||||
|
@@ -10284,6 +10284,26 @@ static void def_geo_curve_fill(StructRNA *srna)
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
|
||||
}
|
||||
|
||||
static void def_geo_attribute_capture(StructRNA *srna)
|
||||
{
|
||||
PropertyRNA *prop;
|
||||
|
||||
RNA_def_struct_sdna_from(srna, "NodeGeometryAttributeCapture", "storage");
|
||||
|
||||
prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_items(prop, rna_enum_attribute_type_items);
|
||||
RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_GeometryNodeAttributeFill_type_itemf");
|
||||
RNA_def_property_enum_default(prop, CD_PROP_FLOAT);
|
||||
RNA_def_property_ui_text(prop, "Data Type", "Type of data stored in attribute");
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_GeometryNode_socket_update");
|
||||
|
||||
prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items);
|
||||
RNA_def_property_enum_default(prop, ATTR_DOMAIN_POINT);
|
||||
RNA_def_property_ui_text(prop, "Domain", "Which domain to store the data in");
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
static void rna_def_shader_node(BlenderRNA *brna)
|
||||
|
@@ -6300,6 +6300,10 @@ static void rna_def_userdef_experimental(BlenderRNA *brna)
|
||||
RNA_def_property_boolean_sdna(prop, NULL, "use_override_templates", 1);
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Override Templates", "Enable library override template in the python API");
|
||||
|
||||
prop = RNA_def_property(srna, "use_geometry_nodes_fields", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, NULL, "use_geometry_nodes_fields", 1);
|
||||
RNA_def_property_ui_text(prop, "Geometry Nodes Fields", "Enable field nodes in geometry nodes");
|
||||
}
|
||||
|
||||
static void rna_def_userdef_addon_collection(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
|
@@ -85,6 +85,7 @@
|
||||
#include "NOD_geometry.h"
|
||||
#include "NOD_geometry_nodes_eval_log.hh"
|
||||
|
||||
#include "FN_field.hh"
|
||||
#include "FN_multi_function.hh"
|
||||
|
||||
using blender::destruct_ptr;
|
||||
@@ -410,28 +411,36 @@ static void init_socket_cpp_value_from_property(const IDProperty &property,
|
||||
{
|
||||
switch (socket_value_type) {
|
||||
case SOCK_FLOAT: {
|
||||
float value = 0.0f;
|
||||
if (property.type == IDP_FLOAT) {
|
||||
*(float *)r_value = IDP_Float(&property);
|
||||
value = IDP_Float(&property);
|
||||
}
|
||||
else if (property.type == IDP_DOUBLE) {
|
||||
*(float *)r_value = (float)IDP_Double(&property);
|
||||
value = (float)IDP_Double(&property);
|
||||
}
|
||||
new (r_value) blender::fn::Field<float>(blender::fn::make_constant_field(value));
|
||||
break;
|
||||
}
|
||||
case SOCK_INT: {
|
||||
*(int *)r_value = IDP_Int(&property);
|
||||
int value = IDP_Int(&property);
|
||||
new (r_value) blender::fn::Field<int>(blender::fn::make_constant_field(value));
|
||||
break;
|
||||
}
|
||||
case SOCK_VECTOR: {
|
||||
copy_v3_v3((float *)r_value, (const float *)IDP_Array(&property));
|
||||
float3 value;
|
||||
copy_v3_v3(value, (const float *)IDP_Array(&property));
|
||||
new (r_value) blender::fn::Field<float3>(blender::fn::make_constant_field(value));
|
||||
break;
|
||||
}
|
||||
case SOCK_BOOLEAN: {
|
||||
*(bool *)r_value = IDP_Int(&property) != 0;
|
||||
bool value = IDP_Int(&property) != 0;
|
||||
new (r_value) blender::fn::Field<bool>(blender::fn::make_constant_field(value));
|
||||
break;
|
||||
}
|
||||
case SOCK_STRING: {
|
||||
new (r_value) std::string(IDP_String(&property));
|
||||
std::string value = IDP_String(&property);
|
||||
new (r_value)
|
||||
blender::fn::Field<std::string>(blender::fn::make_constant_field(std::move(value)));
|
||||
break;
|
||||
}
|
||||
case SOCK_OBJECT: {
|
||||
|
@@ -21,6 +21,8 @@
|
||||
|
||||
#include "DEG_depsgraph_query.h"
|
||||
|
||||
#include "FN_field.hh"
|
||||
#include "FN_field_cpp_type.hh"
|
||||
#include "FN_generic_value_map.hh"
|
||||
#include "FN_multi_function.hh"
|
||||
|
||||
@@ -33,6 +35,9 @@
|
||||
namespace blender::modifiers::geometry_nodes {
|
||||
|
||||
using fn::CPPType;
|
||||
using fn::Field;
|
||||
using fn::FieldCPPType;
|
||||
using fn::GField;
|
||||
using fn::GValueMap;
|
||||
using nodes::GeoNodeExecParams;
|
||||
using namespace fn::multi_function_types;
|
||||
@@ -858,11 +863,10 @@ class GeometryNodesEvaluator {
|
||||
const MultiFunction &fn,
|
||||
NodeState &node_state)
|
||||
{
|
||||
MFContextBuilder fn_context;
|
||||
MFParamsBuilder fn_params{fn, 1};
|
||||
LinearAllocator<> &allocator = local_allocators_.local();
|
||||
|
||||
/* Prepare the inputs for the multi function. */
|
||||
Vector<GField> input_fields;
|
||||
for (const int i : node->inputs().index_range()) {
|
||||
const InputSocketRef &socket_ref = node->input(i);
|
||||
if (!socket_ref.is_available()) {
|
||||
@@ -873,24 +877,12 @@ class GeometryNodesEvaluator {
|
||||
BLI_assert(input_state.was_ready_for_execution);
|
||||
SingleInputValue &single_value = *input_state.value.single;
|
||||
BLI_assert(single_value.value != nullptr);
|
||||
fn_params.add_readonly_single_input(GPointer{*input_state.type, single_value.value});
|
||||
}
|
||||
/* Prepare the outputs for the multi function. */
|
||||
Vector<GMutablePointer> outputs;
|
||||
for (const int i : node->outputs().index_range()) {
|
||||
const OutputSocketRef &socket_ref = node->output(i);
|
||||
if (!socket_ref.is_available()) {
|
||||
continue;
|
||||
}
|
||||
const CPPType &type = *get_socket_cpp_type(socket_ref);
|
||||
void *buffer = allocator.allocate(type.size(), type.alignment());
|
||||
fn_params.add_uninitialized_single_output(GMutableSpan{type, buffer, 1});
|
||||
outputs.append({type, buffer});
|
||||
input_fields.append(std::move(*(GField *)single_value.value));
|
||||
}
|
||||
|
||||
fn.call(IndexRange(1), fn_params, fn_context);
|
||||
auto operation = std::make_shared<fn::FieldOperation>(fn, std::move(input_fields));
|
||||
|
||||
/* Forward the computed outputs. */
|
||||
/* Forward outputs. */
|
||||
int output_index = 0;
|
||||
for (const int i : node->outputs().index_range()) {
|
||||
const OutputSocketRef &socket_ref = node->output(i);
|
||||
@@ -899,8 +891,9 @@ class GeometryNodesEvaluator {
|
||||
}
|
||||
OutputState &output_state = node_state.outputs[i];
|
||||
const DOutputSocket socket{node.context(), &socket_ref};
|
||||
GMutablePointer value = outputs[output_index];
|
||||
this->forward_output(socket, value);
|
||||
const CPPType *cpp_type = get_socket_cpp_type(socket_ref);
|
||||
GField &field = *allocator.construct<GField>(operation, output_index).release();
|
||||
this->forward_output(socket, {cpp_type, &field});
|
||||
output_state.has_been_computed = true;
|
||||
output_index++;
|
||||
}
|
||||
@@ -922,7 +915,7 @@ class GeometryNodesEvaluator {
|
||||
OutputState &output_state = node_state.outputs[socket->index()];
|
||||
output_state.has_been_computed = true;
|
||||
void *buffer = allocator.allocate(type->size(), type->alignment());
|
||||
type->copy_construct(type->default_value(), buffer);
|
||||
this->construct_default_value(*type, buffer);
|
||||
this->forward_output({node.context(), socket}, {*type, buffer});
|
||||
}
|
||||
}
|
||||
@@ -1389,16 +1382,44 @@ class GeometryNodesEvaluator {
|
||||
return;
|
||||
}
|
||||
|
||||
const FieldCPPType *from_field_type = dynamic_cast<const FieldCPPType *>(&from_type);
|
||||
const FieldCPPType *to_field_type = dynamic_cast<const FieldCPPType *>(&to_type);
|
||||
|
||||
if (from_field_type != nullptr && to_field_type != nullptr) {
|
||||
const CPPType &from_base_type = from_field_type->field_type();
|
||||
const CPPType &to_base_type = to_field_type->field_type();
|
||||
if (conversions_.is_convertible(from_base_type, to_base_type)) {
|
||||
const MultiFunction &fn = *conversions_.get_conversion_multi_function(
|
||||
MFDataType::ForSingle(from_base_type), MFDataType::ForSingle(to_base_type));
|
||||
const GField &from_field = *(const GField *)from_value;
|
||||
auto operation = std::make_shared<fn::FieldOperation>(fn, Vector<GField>{from_field});
|
||||
new (to_value) GField(std::move(operation), 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (conversions_.is_convertible(from_type, to_type)) {
|
||||
/* Do the conversion if possible. */
|
||||
conversions_.convert_to_uninitialized(from_type, to_type, from_value, to_value);
|
||||
}
|
||||
else {
|
||||
/* Cannot convert, use default value instead. */
|
||||
to_type.copy_construct(to_type.default_value(), to_value);
|
||||
this->construct_default_value(to_type, to_value);
|
||||
}
|
||||
}
|
||||
|
||||
void construct_default_value(const CPPType &type, void *r_value)
|
||||
{
|
||||
if (const FieldCPPType *field_cpp_type = dynamic_cast<const FieldCPPType *>(&type)) {
|
||||
const CPPType &base_type = field_cpp_type->field_type();
|
||||
auto constant_fn = std::make_unique<fn::CustomMF_GenericConstant>(base_type,
|
||||
base_type.default_value());
|
||||
auto operation = std::make_shared<fn::FieldOperation>(std::move(constant_fn));
|
||||
new (r_value) GField(std::move(operation), 0);
|
||||
return;
|
||||
}
|
||||
type.copy_construct(type.default_value(), r_value);
|
||||
}
|
||||
|
||||
NodeState &get_node_state(const DNode node)
|
||||
{
|
||||
return *node_states_.lookup_key_as(node).state;
|
||||
|
@@ -142,6 +142,7 @@ set(SRC
|
||||
function/node_function_util.cc
|
||||
|
||||
geometry/nodes/node_geo_align_rotation_to_vector.cc
|
||||
geometry/nodes/node_geo_attribute_capture.cc
|
||||
geometry/nodes/node_geo_attribute_clamp.cc
|
||||
geometry/nodes/node_geo_attribute_color_ramp.cc
|
||||
geometry/nodes/node_geo_attribute_combine_xyz.cc
|
||||
@@ -187,6 +188,9 @@ set(SRC
|
||||
geometry/nodes/node_geo_delete_geometry.cc
|
||||
geometry/nodes/node_geo_edge_split.cc
|
||||
geometry/nodes/node_geo_input_material.cc
|
||||
geometry/nodes/node_geo_input_normal.cc
|
||||
geometry/nodes/node_geo_input_position.cc
|
||||
geometry/nodes/node_geo_input_index.cc
|
||||
geometry/nodes/node_geo_is_viewport.cc
|
||||
geometry/nodes/node_geo_join_geometry.cc
|
||||
geometry/nodes/node_geo_material_assign.cc
|
||||
@@ -212,6 +216,7 @@ set(SRC
|
||||
geometry/nodes/node_geo_raycast.cc
|
||||
geometry/nodes/node_geo_select_by_material.cc
|
||||
geometry/nodes/node_geo_separate_components.cc
|
||||
geometry/nodes/node_geo_set_position.cc
|
||||
geometry/nodes/node_geo_subdivision_surface.cc
|
||||
geometry/nodes/node_geo_switch.cc
|
||||
geometry/nodes/node_geo_transform.cc
|
||||
|
@@ -37,6 +37,7 @@ void register_node_type_geo_attribute_compare(void);
|
||||
void register_node_type_geo_attribute_convert(void);
|
||||
void register_node_type_geo_attribute_curve_map(void);
|
||||
void register_node_type_geo_attribute_fill(void);
|
||||
void register_node_type_geo_attribute_capture(void);
|
||||
void register_node_type_geo_attribute_map_range(void);
|
||||
void register_node_type_geo_attribute_math(void);
|
||||
void register_node_type_geo_attribute_mix(void);
|
||||
@@ -71,7 +72,10 @@ void register_node_type_geo_curve_to_points(void);
|
||||
void register_node_type_geo_curve_trim(void);
|
||||
void register_node_type_geo_delete_geometry(void);
|
||||
void register_node_type_geo_edge_split(void);
|
||||
void register_node_type_geo_input_index(void);
|
||||
void register_node_type_geo_input_material(void);
|
||||
void register_node_type_geo_input_normal(void);
|
||||
void register_node_type_geo_input_position(void);
|
||||
void register_node_type_geo_is_viewport(void);
|
||||
void register_node_type_geo_join_geometry(void);
|
||||
void register_node_type_geo_material_assign(void);
|
||||
@@ -99,6 +103,7 @@ void register_node_type_geo_sample_texture(void);
|
||||
void register_node_type_geo_select_by_handle_type(void);
|
||||
void register_node_type_geo_select_by_material(void);
|
||||
void register_node_type_geo_separate_components(void);
|
||||
void register_node_type_geo_set_position(void);
|
||||
void register_node_type_geo_subdivision_surface(void);
|
||||
void register_node_type_geo_switch(void);
|
||||
void register_node_type_geo_transform(void);
|
||||
|
@@ -16,7 +16,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FN_generic_value_map.hh"
|
||||
#include "FN_field.hh"
|
||||
#include "FN_multi_function_builder.hh"
|
||||
|
||||
#include "BKE_attribute_access.hh"
|
||||
#include "BKE_geometry_set.hh"
|
||||
@@ -32,17 +33,24 @@ struct ModifierData;
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
using bke::AttributeIDRef;
|
||||
using bke::geometry_set_realize_instances;
|
||||
using bke::GeometryComponentFieldContext;
|
||||
using bke::OutputAttribute;
|
||||
using bke::OutputAttribute_Typed;
|
||||
using bke::ReadAttributeLookup;
|
||||
using bke::StrongAnonymousAttributeID;
|
||||
using bke::WeakAnonymousAttributeID;
|
||||
using bke::WriteAttributeLookup;
|
||||
using fn::CPPType;
|
||||
using fn::Field;
|
||||
using fn::FieldInput;
|
||||
using fn::FieldOperation;
|
||||
using fn::GField;
|
||||
using fn::GMutablePointer;
|
||||
using fn::GMutableSpan;
|
||||
using fn::GPointer;
|
||||
using fn::GSpan;
|
||||
using fn::GValueMap;
|
||||
using fn::GVArray;
|
||||
using fn::GVArray_GSpan;
|
||||
using fn::GVArray_Span;
|
||||
@@ -121,6 +129,14 @@ class GeoNodeExecParams {
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static inline constexpr bool is_stored_as_field_v = std::is_same_v<T, float> ||
|
||||
std::is_same_v<T, int> ||
|
||||
std::is_same_v<T, bool> ||
|
||||
std::is_same_v<T, ColorGeometry4f> ||
|
||||
std::is_same_v<T, float3> ||
|
||||
std::is_same_v<T, std::string>;
|
||||
|
||||
/**
|
||||
* Get the input value for the input socket with the given identifier.
|
||||
*
|
||||
@@ -142,11 +158,17 @@ class GeoNodeExecParams {
|
||||
*/
|
||||
template<typename T> T extract_input(StringRef identifier)
|
||||
{
|
||||
if constexpr (is_stored_as_field_v<T>) {
|
||||
Field<T> field = this->extract_input<Field<T>>(identifier);
|
||||
return fn::evaluate_constant_field(field);
|
||||
}
|
||||
else {
|
||||
#ifdef DEBUG
|
||||
this->check_input_access(identifier, &CPPType::get<T>());
|
||||
this->check_input_access(identifier, &CPPType::get<T>());
|
||||
#endif
|
||||
GMutablePointer gvalue = this->extract_input(identifier);
|
||||
return gvalue.relocate_out<T>();
|
||||
GMutablePointer gvalue = this->extract_input(identifier);
|
||||
return gvalue.relocate_out<T>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,7 +181,13 @@ class GeoNodeExecParams {
|
||||
Vector<GMutablePointer> gvalues = provider_->extract_multi_input(identifier);
|
||||
Vector<T> values;
|
||||
for (GMutablePointer gvalue : gvalues) {
|
||||
values.append(gvalue.relocate_out<T>());
|
||||
if constexpr (is_stored_as_field_v<T>) {
|
||||
const Field<T> &field = *gvalue.get<Field<T>>();
|
||||
values.append(fn::evaluate_constant_field(field));
|
||||
}
|
||||
else {
|
||||
values.append(gvalue.relocate_out<T>());
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
@@ -167,14 +195,20 @@ class GeoNodeExecParams {
|
||||
/**
|
||||
* Get the input value for the input socket with the given identifier.
|
||||
*/
|
||||
template<typename T> const T &get_input(StringRef identifier) const
|
||||
template<typename T> const T get_input(StringRef identifier) const
|
||||
{
|
||||
if constexpr (is_stored_as_field_v<T>) {
|
||||
const Field<T> &field = this->get_input<Field<T>>(identifier);
|
||||
return fn::evaluate_constant_field(field);
|
||||
}
|
||||
else {
|
||||
#ifdef DEBUG
|
||||
this->check_input_access(identifier, &CPPType::get<T>());
|
||||
this->check_input_access(identifier, &CPPType::get<T>());
|
||||
#endif
|
||||
GPointer gvalue = provider_->get_input(identifier);
|
||||
BLI_assert(gvalue.is_type<T>());
|
||||
return *(const T *)gvalue.get();
|
||||
GPointer gvalue = provider_->get_input(identifier);
|
||||
BLI_assert(gvalue.is_type<T>());
|
||||
return *(const T *)gvalue.get();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,13 +217,19 @@ class GeoNodeExecParams {
|
||||
template<typename T> void set_output(StringRef identifier, T &&value)
|
||||
{
|
||||
using StoredT = std::decay_t<T>;
|
||||
const CPPType &type = CPPType::get<std::decay_t<T>>();
|
||||
if constexpr (is_stored_as_field_v<StoredT>) {
|
||||
this->set_output<Field<StoredT>>(identifier,
|
||||
fn::make_constant_field<StoredT>(std::forward<T>(value)));
|
||||
}
|
||||
else {
|
||||
const CPPType &type = CPPType::get<StoredT>();
|
||||
#ifdef DEBUG
|
||||
this->check_output_access(identifier, type);
|
||||
this->check_output_access(identifier, type);
|
||||
#endif
|
||||
GMutablePointer gvalue = provider_->alloc_output_value(type);
|
||||
new (gvalue.get()) StoredT(std::forward<T>(value));
|
||||
provider_->set_output(identifier, gvalue);
|
||||
GMutablePointer gvalue = provider_->alloc_output_value(type);
|
||||
new (gvalue.get()) StoredT(std::forward<T>(value));
|
||||
provider_->set_output(identifier, gvalue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -276,6 +276,7 @@ DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMBINE_XYZ, def_geo_attribute_combine_
|
||||
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMPARE, def_geo_attribute_attribute_compare, "ATTRIBUTE_COMPARE", AttributeCompare, "Attribute Compare", "")
|
||||
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CONVERT, def_geo_attribute_convert, "ATTRIBUTE_CONVERT", AttributeConvert, "Attribute Convert", "")
|
||||
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CURVE_MAP, def_geo_attribute_curve_map, "ATTRIBUTE_CURVE_MAP", AttributeCurveMap, "Attribute Curve Map", "")
|
||||
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CAPTURE, def_geo_attribute_capture, "ATTRIBUTE_CAPTURE", AttributeCapture, "Attribute Capture", "")
|
||||
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_FILL, def_geo_attribute_fill, "ATTRIBUTE_FILL", AttributeFill, "Attribute Fill", "")
|
||||
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MAP_RANGE, def_geo_attribute_map_range, "ATTRIBUTE_MAP_RANGE", AttributeMapRange, "Attribute Map Range", "")
|
||||
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MATH, def_geo_attribute_math, "ATTRIBUTE_MATH", AttributeMath, "Attribute Math", "")
|
||||
@@ -304,8 +305,8 @@ DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_SPIRAL, 0, "CURVE_PRIMITIVE_SPIRA
|
||||
DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_STAR, 0, "CURVE_PRIMITIVE_STAR", CurveStar, "Star", "")
|
||||
DefNode(GeometryNode, GEO_NODE_CURVE_RESAMPLE, def_geo_curve_resample, "CURVE_RESAMPLE", CurveResample, "Resample Curve", "")
|
||||
DefNode(GeometryNode, GEO_NODE_CURVE_REVERSE, 0, "CURVE_REVERSE", CurveReverse, "Curve Reverse", "")
|
||||
DefNode(GeometryNode, GEO_NODE_CURVE_SET_HANDLES, def_geo_curve_set_handles, "CURVE_SET_HANDLES", CurveSetHandles, "Set Handle Type", "")
|
||||
DefNode(GeometryNode, GEO_NODE_CURVE_SELECT_HANDLES, def_geo_curve_select_handles, "CURVE_SELECT_HANDLES", CurveSelectHandles, "Select by Handle Type", "")
|
||||
DefNode(GeometryNode, GEO_NODE_CURVE_SET_HANDLES, def_geo_curve_set_handles, "CURVE_SET_HANDLES", CurveSetHandles, "Set Handle Type", "")
|
||||
DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_TYPE, def_geo_curve_spline_type, "CURVE_SPLINE_TYPE", CurveSplineType, "Set Spline Type", "")
|
||||
DefNode(GeometryNode, GEO_NODE_CURVE_SUBDIVIDE, def_geo_curve_subdivide, "CURVE_SUBDIVIDE", CurveSubdivide, "Curve Subdivide", "")
|
||||
DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "")
|
||||
@@ -313,7 +314,10 @@ DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, def_geo_curve_to_points, "CURVE_
|
||||
DefNode(GeometryNode, GEO_NODE_CURVE_TRIM, def_geo_curve_trim, "CURVE_TRIM", CurveTrim, "Curve Trim", "")
|
||||
DefNode(GeometryNode, GEO_NODE_DELETE_GEOMETRY, 0, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "")
|
||||
DefNode(GeometryNode, GEO_NODE_EDGE_SPLIT, 0, "EDGE_SPLIT", EdgeSplit, "Edge Split", "")
|
||||
DefNode(GeometryNode, GEO_NODE_INPUT_INDEX, 0, "INDEX", InputIndex, "Index", "")
|
||||
DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "")
|
||||
DefNode(GeometryNode, GEO_NODE_INPUT_NORMAL, 0, "INPUT_NORMAL", InputNormal, "Normal", "")
|
||||
DefNode(GeometryNode, GEO_NODE_INPUT_POSITION, 0, "POSITION", InputPosition, "Position", "")
|
||||
DefNode(GeometryNode, GEO_NODE_IS_VIEWPORT, 0, "IS_VIEWPORT", IsViewport, "Is Viewport", "")
|
||||
DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "")
|
||||
DefNode(GeometryNode, GEO_NODE_MATERIAL_ASSIGN, 0, "MATERIAL_ASSIGN", MaterialAssign, "Material Assign", "")
|
||||
@@ -339,6 +343,7 @@ DefNode(GeometryNode, GEO_NODE_POINTS_TO_VOLUME, def_geo_points_to_volume, "POIN
|
||||
DefNode(GeometryNode, GEO_NODE_RAYCAST, def_geo_raycast, "RAYCAST", Raycast, "Raycast", "")
|
||||
DefNode(GeometryNode, GEO_NODE_SELECT_BY_MATERIAL, 0, "SELECT_BY_MATERIAL", SelectByMaterial, "Select by Material", "")
|
||||
DefNode(GeometryNode, GEO_NODE_SEPARATE_COMPONENTS, 0, "SEPARATE_COMPONENTS", SeparateComponents, "Separate Components", "")
|
||||
DefNode(GeometryNode, GEO_NODE_SET_POSITION, 0, "SET_POSITION", SetPosition, "Set Position", "")
|
||||
DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, def_geo_subdivision_surface, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "")
|
||||
DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "")
|
||||
DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "")
|
||||
|
@@ -84,7 +84,7 @@ struct CurveToPointsResults {
|
||||
MutableSpan<float> radii;
|
||||
MutableSpan<float> tilts;
|
||||
|
||||
Map<std::string, GMutableSpan> point_attributes;
|
||||
Map<AttributeIDRef, GMutableSpan> point_attributes;
|
||||
|
||||
MutableSpan<float3> tangents;
|
||||
MutableSpan<float3> normals;
|
||||
|
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "UI_interface.h"
|
||||
#include "UI_resources.h"
|
||||
|
||||
#include "BKE_attribute_math.hh"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
static void geo_node_attribute_capture_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_input<decl::Geometry>("Geometry");
|
||||
b.add_input<decl::Vector>("Value");
|
||||
b.add_input<decl::Float>("Value", "Value_001");
|
||||
b.add_input<decl::Color>("Value", "Value_002");
|
||||
b.add_input<decl::Bool>("Value", "Value_003");
|
||||
b.add_input<decl::Int>("Value", "Value_004");
|
||||
|
||||
b.add_output<decl::Geometry>("Geometry");
|
||||
b.add_output<decl::Vector>("Attribute");
|
||||
b.add_output<decl::Float>("Attribute", "Attribute_001");
|
||||
b.add_output<decl::Color>("Attribute", "Attribute_002");
|
||||
b.add_output<decl::Bool>("Attribute", "Attribute_003");
|
||||
b.add_output<decl::Int>("Attribute", "Attribute_004");
|
||||
}
|
||||
|
||||
static void geo_node_attribute_capture_layout(uiLayout *layout,
|
||||
bContext *UNUSED(C),
|
||||
PointerRNA *ptr)
|
||||
{
|
||||
uiLayoutSetPropSep(layout, true);
|
||||
uiLayoutSetPropDecorate(layout, false);
|
||||
uiItemR(layout, ptr, "domain", 0, "", ICON_NONE);
|
||||
uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE);
|
||||
}
|
||||
|
||||
static void geo_node_attribute_capture_init(bNodeTree *UNUSED(tree), bNode *node)
|
||||
{
|
||||
NodeGeometryAttributeCapture *data = (NodeGeometryAttributeCapture *)MEM_callocN(
|
||||
sizeof(NodeGeometryAttributeCapture), __func__);
|
||||
data->data_type = CD_PROP_FLOAT;
|
||||
data->domain = ATTR_DOMAIN_POINT;
|
||||
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
static void geo_node_attribute_capture_update(bNodeTree *UNUSED(ntree), bNode *node)
|
||||
{
|
||||
const NodeGeometryAttributeCapture &storage = *(const NodeGeometryAttributeCapture *)
|
||||
node->storage;
|
||||
const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type);
|
||||
|
||||
bNodeSocket *socket_value_attribute_name = (bNodeSocket *)node->inputs.first;
|
||||
bNodeSocket *socket_value_vector = socket_value_attribute_name->next;
|
||||
bNodeSocket *socket_value_float = socket_value_vector->next;
|
||||
bNodeSocket *socket_value_color4f = socket_value_float->next;
|
||||
bNodeSocket *socket_value_boolean = socket_value_color4f->next;
|
||||
bNodeSocket *socket_value_int32 = socket_value_boolean->next;
|
||||
|
||||
nodeSetSocketAvailability(socket_value_vector, data_type == CD_PROP_FLOAT3);
|
||||
nodeSetSocketAvailability(socket_value_float, data_type == CD_PROP_FLOAT);
|
||||
nodeSetSocketAvailability(socket_value_color4f, data_type == CD_PROP_COLOR);
|
||||
nodeSetSocketAvailability(socket_value_boolean, data_type == CD_PROP_BOOL);
|
||||
nodeSetSocketAvailability(socket_value_int32, data_type == CD_PROP_INT32);
|
||||
|
||||
bNodeSocket *out_socket_value_attribute_name = (bNodeSocket *)node->outputs.first;
|
||||
bNodeSocket *out_socket_value_vector = out_socket_value_attribute_name->next;
|
||||
bNodeSocket *out_socket_value_float = out_socket_value_vector->next;
|
||||
bNodeSocket *out_socket_value_color4f = out_socket_value_float->next;
|
||||
bNodeSocket *out_socket_value_boolean = out_socket_value_color4f->next;
|
||||
bNodeSocket *out_socket_value_int32 = out_socket_value_boolean->next;
|
||||
|
||||
nodeSetSocketAvailability(out_socket_value_vector, data_type == CD_PROP_FLOAT3);
|
||||
nodeSetSocketAvailability(out_socket_value_float, data_type == CD_PROP_FLOAT);
|
||||
nodeSetSocketAvailability(out_socket_value_color4f, data_type == CD_PROP_COLOR);
|
||||
nodeSetSocketAvailability(out_socket_value_boolean, data_type == CD_PROP_BOOL);
|
||||
nodeSetSocketAvailability(out_socket_value_int32, data_type == CD_PROP_INT32);
|
||||
}
|
||||
|
||||
static void try_capture_field_on_geometry(GeometryComponent &component,
|
||||
const AttributeIDRef &attribute_id,
|
||||
const AttributeDomain domain,
|
||||
const GField &field)
|
||||
{
|
||||
GeometryComponentFieldContext field_context{component, domain};
|
||||
const int domain_size = component.attribute_domain_size(domain);
|
||||
const IndexMask mask{IndexMask(domain_size)};
|
||||
|
||||
const CustomDataType data_type = bke::cpp_type_to_custom_data_type(field.cpp_type());
|
||||
OutputAttribute output_attribute = component.attribute_try_get_for_output_only(
|
||||
attribute_id, domain, data_type);
|
||||
|
||||
fn::FieldEvaluator evaluator{field_context, &mask};
|
||||
evaluator.add_with_destination(field, output_attribute.varray());
|
||||
evaluator.evaluate();
|
||||
|
||||
output_attribute.save();
|
||||
}
|
||||
|
||||
static void geo_node_attribute_capture_exec(GeoNodeExecParams params)
|
||||
{
|
||||
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
|
||||
|
||||
geometry_set = bke::geometry_set_realize_instances(geometry_set);
|
||||
|
||||
const bNode &node = params.node();
|
||||
const NodeGeometryAttributeCapture &storage = *(const NodeGeometryAttributeCapture *)
|
||||
node.storage;
|
||||
const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type);
|
||||
const AttributeDomain domain = static_cast<AttributeDomain>(storage.domain);
|
||||
|
||||
GField field;
|
||||
switch (data_type) {
|
||||
case CD_PROP_FLOAT:
|
||||
field = params.get_input<Field<float>>("Value_001");
|
||||
break;
|
||||
case CD_PROP_FLOAT3:
|
||||
field = params.get_input<Field<float3>>("Value");
|
||||
break;
|
||||
case CD_PROP_COLOR:
|
||||
field = params.get_input<Field<ColorGeometry4f>>("Value_002");
|
||||
break;
|
||||
case CD_PROP_BOOL:
|
||||
field = params.get_input<Field<bool>>("Value_003");
|
||||
break;
|
||||
case CD_PROP_INT32:
|
||||
field = params.get_input<Field<int>>("Value_004");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
WeakAnonymousAttributeID anonymous_id{"Attribute Capture"};
|
||||
const CPPType &type = field.cpp_type();
|
||||
|
||||
static const Array<GeometryComponentType> types = {
|
||||
GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE};
|
||||
for (const GeometryComponentType type : types) {
|
||||
if (geometry_set.has(type)) {
|
||||
GeometryComponent &component = geometry_set.get_component_for_write(type);
|
||||
try_capture_field_on_geometry(component, anonymous_id.get(), domain, field);
|
||||
}
|
||||
}
|
||||
|
||||
GField output_field{
|
||||
std::make_shared<bke::AnonymousAttributeFieldInput>(std::move(anonymous_id), type)};
|
||||
|
||||
switch (data_type) {
|
||||
case CD_PROP_FLOAT: {
|
||||
params.set_output("Attribute_001", Field<float>(output_field));
|
||||
break;
|
||||
}
|
||||
case CD_PROP_FLOAT3: {
|
||||
params.set_output("Attribute", Field<float3>(output_field));
|
||||
break;
|
||||
}
|
||||
case CD_PROP_COLOR: {
|
||||
params.set_output("Attribute_002", Field<ColorGeometry4f>(output_field));
|
||||
break;
|
||||
}
|
||||
case CD_PROP_BOOL: {
|
||||
params.set_output("Attribute_003", Field<bool>(output_field));
|
||||
break;
|
||||
}
|
||||
case CD_PROP_INT32: {
|
||||
params.set_output("Attribute_004", Field<int>(output_field));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
params.set_output("Geometry", geometry_set);
|
||||
}
|
||||
|
||||
} // namespace blender::nodes
|
||||
|
||||
void register_node_type_geo_attribute_capture()
|
||||
{
|
||||
static bNodeType ntype;
|
||||
|
||||
geo_node_type_base(
|
||||
&ntype, GEO_NODE_ATTRIBUTE_CAPTURE, "Attribute Capture", NODE_CLASS_ATTRIBUTE, 0);
|
||||
node_type_storage(&ntype,
|
||||
"NodeGeometryAttributeCapture",
|
||||
node_free_standard_storage,
|
||||
node_copy_standard_storage);
|
||||
node_type_init(&ntype, blender::nodes::geo_node_attribute_capture_init);
|
||||
node_type_update(&ntype, blender::nodes::geo_node_attribute_capture_update);
|
||||
ntype.declare = blender::nodes::geo_node_attribute_capture_declare;
|
||||
ntype.geometry_node_execute = blender::nodes::geo_node_attribute_capture_exec;
|
||||
ntype.draw_buttons = blender::nodes::geo_node_attribute_capture_layout;
|
||||
nodeRegisterType(&ntype);
|
||||
}
|
@@ -56,25 +56,26 @@ static void copy_spline_domain_attributes(const CurveComponent &curve_component,
|
||||
Span<int> offsets,
|
||||
PointCloudComponent &points)
|
||||
{
|
||||
curve_component.attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
|
||||
if (meta_data.domain != ATTR_DOMAIN_CURVE) {
|
||||
return true;
|
||||
}
|
||||
GVArrayPtr spline_attribute = curve_component.attribute_get_for_read(
|
||||
name, ATTR_DOMAIN_CURVE, meta_data.data_type);
|
||||
curve_component.attribute_foreach(
|
||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
|
||||
if (meta_data.domain != ATTR_DOMAIN_CURVE) {
|
||||
return true;
|
||||
}
|
||||
GVArrayPtr spline_attribute = curve_component.attribute_get_for_read(
|
||||
attribute_id, ATTR_DOMAIN_CURVE, meta_data.data_type);
|
||||
|
||||
OutputAttribute result_attribute = points.attribute_try_get_for_output_only(
|
||||
name, ATTR_DOMAIN_POINT, meta_data.data_type);
|
||||
GMutableSpan result = result_attribute.as_span();
|
||||
OutputAttribute result_attribute = points.attribute_try_get_for_output_only(
|
||||
attribute_id, ATTR_DOMAIN_POINT, meta_data.data_type);
|
||||
GMutableSpan result = result_attribute.as_span();
|
||||
|
||||
/* Only copy the attributes of splines in the offsets. */
|
||||
for (const int i : offsets.index_range()) {
|
||||
spline_attribute->get(offsets[i], result[i]);
|
||||
}
|
||||
/* Only copy the attributes of splines in the offsets. */
|
||||
for (const int i : offsets.index_range()) {
|
||||
spline_attribute->get(offsets[i], result[i]);
|
||||
}
|
||||
|
||||
result_attribute.save();
|
||||
return true;
|
||||
});
|
||||
result_attribute.save();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,20 +125,20 @@ static void copy_endpoint_attributes(Span<SplinePtr> splines,
|
||||
|
||||
/* Copy the point attribute data over. */
|
||||
for (const auto &item : start_data.point_attributes.items()) {
|
||||
const StringRef name = item.key;
|
||||
const AttributeIDRef attribute_id = item.key;
|
||||
GMutableSpan point_span = item.value;
|
||||
|
||||
BLI_assert(spline.attributes.get_for_read(name));
|
||||
GSpan spline_span = *spline.attributes.get_for_read(name);
|
||||
BLI_assert(spline.attributes.get_for_read(attribute_id));
|
||||
GSpan spline_span = *spline.attributes.get_for_read(attribute_id);
|
||||
blender::fn::GVArray_For_GSpan(spline_span).get(0, point_span[i]);
|
||||
}
|
||||
|
||||
for (const auto &item : end_data.point_attributes.items()) {
|
||||
const StringRef name = item.key;
|
||||
const AttributeIDRef attribute_id = item.key;
|
||||
GMutableSpan point_span = item.value;
|
||||
|
||||
BLI_assert(spline.attributes.get_for_read(name));
|
||||
GSpan spline_span = *spline.attributes.get_for_read(name);
|
||||
BLI_assert(spline.attributes.get_for_read(attribute_id));
|
||||
GSpan spline_span = *spline.attributes.get_for_read(attribute_id);
|
||||
blender::fn::GVArray_For_GSpan(spline_span).get(spline.size() - 1, point_span[i]);
|
||||
}
|
||||
}
|
||||
|
@@ -84,14 +84,14 @@ static SplinePtr resample_spline(const Spline &input_spline, const int count)
|
||||
input_spline.radii().first());
|
||||
output_spline->attributes.reallocate(1);
|
||||
input_spline.attributes.foreach_attribute(
|
||||
[&](StringRefNull name, const AttributeMetaData &meta_data) {
|
||||
std::optional<GSpan> src = input_spline.attributes.get_for_read(name);
|
||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
|
||||
std::optional<GSpan> src = input_spline.attributes.get_for_read(attribute_id);
|
||||
BLI_assert(src);
|
||||
if (!output_spline->attributes.create(name, meta_data.data_type)) {
|
||||
if (!output_spline->attributes.create(attribute_id, meta_data.data_type)) {
|
||||
BLI_assert_unreachable();
|
||||
return false;
|
||||
}
|
||||
std::optional<GMutableSpan> dst = output_spline->attributes.get_for_write(name);
|
||||
std::optional<GMutableSpan> dst = output_spline->attributes.get_for_write(attribute_id);
|
||||
if (!dst) {
|
||||
BLI_assert_unreachable();
|
||||
return false;
|
||||
@@ -122,15 +122,15 @@ static SplinePtr resample_spline(const Spline &input_spline, const int count)
|
||||
|
||||
output_spline->attributes.reallocate(count);
|
||||
input_spline.attributes.foreach_attribute(
|
||||
[&](StringRefNull name, const AttributeMetaData &meta_data) {
|
||||
std::optional<GSpan> input_attribute = input_spline.attributes.get_for_read(name);
|
||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
|
||||
std::optional<GSpan> input_attribute = input_spline.attributes.get_for_read(attribute_id);
|
||||
BLI_assert(input_attribute);
|
||||
if (!output_spline->attributes.create(name, meta_data.data_type)) {
|
||||
if (!output_spline->attributes.create(attribute_id, meta_data.data_type)) {
|
||||
BLI_assert_unreachable();
|
||||
return false;
|
||||
}
|
||||
std::optional<GMutableSpan> output_attribute = output_spline->attributes.get_for_write(
|
||||
name);
|
||||
attribute_id);
|
||||
if (!output_attribute) {
|
||||
BLI_assert_unreachable();
|
||||
return false;
|
||||
|
@@ -83,9 +83,9 @@ static void geo_node_curve_reverse_exec(GeoNodeExecParams params)
|
||||
reverse_data<float>(splines[i]->tilts());
|
||||
|
||||
splines[i]->attributes.foreach_attribute(
|
||||
[&](StringRefNull name, const AttributeMetaData &meta_data) {
|
||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
|
||||
std::optional<blender::fn::GMutableSpan> output_attribute =
|
||||
splines[i]->attributes.get_for_write(name);
|
||||
splines[i]->attributes.get_for_write(attribute_id);
|
||||
if (!output_attribute) {
|
||||
BLI_assert_unreachable();
|
||||
return false;
|
||||
|
@@ -74,14 +74,14 @@ template<typename CopyFn>
|
||||
static void copy_attributes(const Spline &input_spline, Spline &output_spline, CopyFn copy_fn)
|
||||
{
|
||||
input_spline.attributes.foreach_attribute(
|
||||
[&](StringRefNull name, const AttributeMetaData &meta_data) {
|
||||
std::optional<GSpan> src = input_spline.attributes.get_for_read(name);
|
||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
|
||||
std::optional<GSpan> src = input_spline.attributes.get_for_read(attribute_id);
|
||||
BLI_assert(src);
|
||||
if (!output_spline.attributes.create(name, meta_data.data_type)) {
|
||||
if (!output_spline.attributes.create(attribute_id, meta_data.data_type)) {
|
||||
BLI_assert_unreachable();
|
||||
return false;
|
||||
}
|
||||
std::optional<GMutableSpan> dst = output_spline.attributes.get_for_write(name);
|
||||
std::optional<GMutableSpan> dst = output_spline.attributes.get_for_write(attribute_id);
|
||||
if (!dst) {
|
||||
BLI_assert_unreachable();
|
||||
return false;
|
||||
|
@@ -283,16 +283,16 @@ static void subdivide_dynamic_attributes(const Spline &src_spline,
|
||||
{
|
||||
const bool is_cyclic = src_spline.is_cyclic();
|
||||
src_spline.attributes.foreach_attribute(
|
||||
[&](StringRefNull name, const AttributeMetaData &meta_data) {
|
||||
std::optional<GSpan> src = src_spline.attributes.get_for_read(name);
|
||||
[&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
|
||||
std::optional<GSpan> src = src_spline.attributes.get_for_read(attribute_id);
|
||||
BLI_assert(src);
|
||||
|
||||
if (!dst_spline.attributes.create(name, meta_data.data_type)) {
|
||||
if (!dst_spline.attributes.create(attribute_id, meta_data.data_type)) {
|
||||
/* Since the source spline of the same type had the attribute, adding it should work. */
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
std::optional<GMutableSpan> dst = dst_spline.attributes.get_for_write(name);
|
||||
std::optional<GMutableSpan> dst = dst_spline.attributes.get_for_write(attribute_id);
|
||||
BLI_assert(dst);
|
||||
|
||||
attribute_math::convert_to_static_type(dst->type(), [&](auto dummy) {
|
||||
|
@@ -115,21 +115,21 @@ static Array<int> calculate_spline_point_offsets(GeoNodeExecParams ¶ms,
|
||||
}
|
||||
|
||||
static GMutableSpan create_attribute_and_retrieve_span(PointCloudComponent &points,
|
||||
const StringRef name,
|
||||
const AttributeIDRef &attribute_id,
|
||||
const CustomDataType data_type)
|
||||
{
|
||||
points.attribute_try_create(name, ATTR_DOMAIN_POINT, data_type, AttributeInitDefault());
|
||||
WriteAttributeLookup attribute = points.attribute_try_get_for_write(name);
|
||||
points.attribute_try_create(attribute_id, ATTR_DOMAIN_POINT, data_type, AttributeInitDefault());
|
||||
WriteAttributeLookup attribute = points.attribute_try_get_for_write(attribute_id);
|
||||
BLI_assert(attribute);
|
||||
return attribute.varray->get_internal_span();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static MutableSpan<T> create_attribute_and_retrieve_span(PointCloudComponent &points,
|
||||
const StringRef name)
|
||||
const AttributeIDRef &attribute_id)
|
||||
{
|
||||
GMutableSpan attribute = create_attribute_and_retrieve_span(
|
||||
points, name, bke::cpp_type_to_custom_data_type(CPPType::get<T>()));
|
||||
points, attribute_id, bke::cpp_type_to_custom_data_type(CPPType::get<T>()));
|
||||
return attribute.typed<T>();
|
||||
}
|
||||
|
||||
@@ -147,9 +147,10 @@ CurveToPointsResults curve_to_points_create_result_attributes(PointCloudComponen
|
||||
/* Because of the invariants of the curve component, we use the attributes of the
|
||||
* first spline as a representative for the attribute meta data all splines. */
|
||||
curve.splines().first()->attributes.foreach_attribute(
|
||||
[&](StringRefNull name, const AttributeMetaData &meta_data) {
|
||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
|
||||
attributes.point_attributes.add_new(
|
||||
name, create_attribute_and_retrieve_span(points, name, meta_data.data_type));
|
||||
attribute_id,
|
||||
create_attribute_and_retrieve_span(points, attribute_id, meta_data.data_type));
|
||||
return true;
|
||||
},
|
||||
ATTR_DOMAIN_POINT);
|
||||
@@ -179,12 +180,12 @@ static void copy_evaluated_point_attributes(Span<SplinePtr> splines,
|
||||
spline.interpolate_to_evaluated(spline.radii())->materialize(data.radii.slice(offset, size));
|
||||
spline.interpolate_to_evaluated(spline.tilts())->materialize(data.tilts.slice(offset, size));
|
||||
|
||||
for (const Map<std::string, GMutableSpan>::Item &item : data.point_attributes.items()) {
|
||||
const StringRef name = item.key;
|
||||
for (const Map<AttributeIDRef, GMutableSpan>::Item &item : data.point_attributes.items()) {
|
||||
const AttributeIDRef attribute_id = item.key;
|
||||
GMutableSpan point_span = item.value;
|
||||
|
||||
BLI_assert(spline.attributes.get_for_read(name));
|
||||
GSpan spline_span = *spline.attributes.get_for_read(name);
|
||||
BLI_assert(spline.attributes.get_for_read(attribute_id));
|
||||
GSpan spline_span = *spline.attributes.get_for_read(attribute_id);
|
||||
|
||||
spline.interpolate_to_evaluated(spline_span)
|
||||
->materialize(point_span.slice(offset, size).data());
|
||||
@@ -222,12 +223,12 @@ static void copy_uniform_sample_point_attributes(Span<SplinePtr> splines,
|
||||
uniform_samples,
|
||||
data.tilts.slice(offset, size));
|
||||
|
||||
for (const Map<std::string, GMutableSpan>::Item &item : data.point_attributes.items()) {
|
||||
const StringRef name = item.key;
|
||||
for (const Map<AttributeIDRef, GMutableSpan>::Item &item : data.point_attributes.items()) {
|
||||
const AttributeIDRef attribute_id = item.key;
|
||||
GMutableSpan point_span = item.value;
|
||||
|
||||
BLI_assert(spline.attributes.get_for_read(name));
|
||||
GSpan spline_span = *spline.attributes.get_for_read(name);
|
||||
BLI_assert(spline.attributes.get_for_read(attribute_id));
|
||||
GSpan spline_span = *spline.attributes.get_for_read(attribute_id);
|
||||
|
||||
spline.sample_with_index_factors(*spline.interpolate_to_evaluated(spline_span),
|
||||
uniform_samples,
|
||||
@@ -257,31 +258,32 @@ static void copy_spline_domain_attributes(const CurveComponent &curve_component,
|
||||
Span<int> offsets,
|
||||
PointCloudComponent &points)
|
||||
{
|
||||
curve_component.attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
|
||||
if (meta_data.domain != ATTR_DOMAIN_CURVE) {
|
||||
return true;
|
||||
}
|
||||
GVArrayPtr spline_attribute = curve_component.attribute_get_for_read(
|
||||
name, ATTR_DOMAIN_CURVE, meta_data.data_type);
|
||||
const CPPType &type = spline_attribute->type();
|
||||
curve_component.attribute_foreach(
|
||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
|
||||
if (meta_data.domain != ATTR_DOMAIN_CURVE) {
|
||||
return true;
|
||||
}
|
||||
GVArrayPtr spline_attribute = curve_component.attribute_get_for_read(
|
||||
attribute_id, ATTR_DOMAIN_CURVE, meta_data.data_type);
|
||||
const CPPType &type = spline_attribute->type();
|
||||
|
||||
OutputAttribute result_attribute = points.attribute_try_get_for_output_only(
|
||||
name, ATTR_DOMAIN_POINT, meta_data.data_type);
|
||||
GMutableSpan result = result_attribute.as_span();
|
||||
OutputAttribute result_attribute = points.attribute_try_get_for_output_only(
|
||||
attribute_id, ATTR_DOMAIN_POINT, meta_data.data_type);
|
||||
GMutableSpan result = result_attribute.as_span();
|
||||
|
||||
for (const int i : IndexRange(spline_attribute->size())) {
|
||||
const int offset = offsets[i];
|
||||
const int size = offsets[i + 1] - offsets[i];
|
||||
if (size != 0) {
|
||||
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
|
||||
spline_attribute->get(i, buffer);
|
||||
type.fill_assign_n(buffer, result[offset], size);
|
||||
}
|
||||
}
|
||||
for (const int i : IndexRange(spline_attribute->size())) {
|
||||
const int offset = offsets[i];
|
||||
const int size = offsets[i + 1] - offsets[i];
|
||||
if (size != 0) {
|
||||
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
|
||||
spline_attribute->get(i, buffer);
|
||||
type.fill_assign_n(buffer, result[offset], size);
|
||||
}
|
||||
}
|
||||
|
||||
result_attribute.save();
|
||||
return true;
|
||||
});
|
||||
result_attribute.save();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void curve_create_default_rotation_attribute(Span<float3> tangents,
|
||||
|
@@ -158,8 +158,8 @@ static void trim_poly_spline(Spline &spline,
|
||||
linear_trim_data<float>(start, end, spline.tilts());
|
||||
|
||||
spline.attributes.foreach_attribute(
|
||||
[&](StringRefNull name, const AttributeMetaData &UNUSED(meta_data)) {
|
||||
std::optional<GMutableSpan> src = spline.attributes.get_for_write(name);
|
||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) {
|
||||
std::optional<GMutableSpan> src = spline.attributes.get_for_write(attribute_id);
|
||||
BLI_assert(src);
|
||||
attribute_math::convert_to_static_type(src->type(), [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
@@ -193,14 +193,14 @@ static PolySpline trim_nurbs_spline(const Spline &spline,
|
||||
|
||||
/* Copy generic attribute data. */
|
||||
spline.attributes.foreach_attribute(
|
||||
[&](StringRefNull name, const AttributeMetaData &meta_data) {
|
||||
std::optional<GSpan> src = spline.attributes.get_for_read(name);
|
||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
|
||||
std::optional<GSpan> src = spline.attributes.get_for_read(attribute_id);
|
||||
BLI_assert(src);
|
||||
if (!new_spline.attributes.create(name, meta_data.data_type)) {
|
||||
if (!new_spline.attributes.create(attribute_id, meta_data.data_type)) {
|
||||
BLI_assert_unreachable();
|
||||
return false;
|
||||
}
|
||||
std::optional<GMutableSpan> dst = new_spline.attributes.get_for_write(name);
|
||||
std::optional<GMutableSpan> dst = new_spline.attributes.get_for_write(attribute_id);
|
||||
BLI_assert(dst);
|
||||
|
||||
attribute_math::convert_to_static_type(src->type(), [&](auto dummy) {
|
||||
@@ -249,8 +249,8 @@ static void trim_bezier_spline(Spline &spline,
|
||||
linear_trim_data<float>(start, end, bezier_spline.radii());
|
||||
linear_trim_data<float>(start, end, bezier_spline.tilts());
|
||||
spline.attributes.foreach_attribute(
|
||||
[&](StringRefNull name, const AttributeMetaData &UNUSED(meta_data)) {
|
||||
std::optional<GMutableSpan> src = spline.attributes.get_for_write(name);
|
||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) {
|
||||
std::optional<GMutableSpan> src = spline.attributes.get_for_write(attribute_id);
|
||||
BLI_assert(src);
|
||||
attribute_math::convert_to_static_type(src->type(), [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
|
@@ -93,17 +93,17 @@ static void copy_dynamic_attributes(const CustomDataAttributes &src,
|
||||
const IndexMask mask)
|
||||
{
|
||||
src.foreach_attribute(
|
||||
[&](StringRefNull name, const AttributeMetaData &meta_data) {
|
||||
std::optional<GSpan> src_attribute = src.get_for_read(name);
|
||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
|
||||
std::optional<GSpan> src_attribute = src.get_for_read(attribute_id);
|
||||
BLI_assert(src_attribute);
|
||||
|
||||
if (!dst.create(name, meta_data.data_type)) {
|
||||
if (!dst.create(attribute_id, meta_data.data_type)) {
|
||||
/* Since the source spline of the same type had the attribute, adding it should work.
|
||||
*/
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
std::optional<GMutableSpan> new_attribute = dst.get_for_write(name);
|
||||
std::optional<GMutableSpan> new_attribute = dst.get_for_write(attribute_id);
|
||||
BLI_assert(new_attribute);
|
||||
|
||||
attribute_math::convert_to_static_type(new_attribute->type(), [&](auto dummy) {
|
||||
|
60
source/blender/nodes/geometry/nodes/node_geo_input_index.cc
Normal file
60
source/blender/nodes/geometry/nodes/node_geo_input_index.cc
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
static void geo_node_input_index_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_output<decl::Int>("Index");
|
||||
}
|
||||
|
||||
class IndexFieldInput final : public fn::FieldInput {
|
||||
public:
|
||||
IndexFieldInput() : FieldInput(CPPType::get<int>(), "Index")
|
||||
{
|
||||
}
|
||||
|
||||
const GVArray *get_varray_for_context(const fn::FieldContext &UNUSED(context),
|
||||
IndexMask mask,
|
||||
ResourceScope &scope) const final
|
||||
{
|
||||
/* TODO: Investigate a similar method to IndexRange::as_span() */
|
||||
auto index_func = [](int i) { return i; };
|
||||
return &scope.construct<
|
||||
fn::GVArray_For_EmbeddedVArray<int, VArray_For_Func<int, decltype(index_func)>>>(
|
||||
__func__, mask.min_array_size(), mask.min_array_size(), index_func);
|
||||
}
|
||||
};
|
||||
|
||||
static void geo_node_input_index_exec(GeoNodeExecParams params)
|
||||
{
|
||||
Field<int> index_field{std::make_shared<IndexFieldInput>()};
|
||||
params.set_output("Index", std::move(index_field));
|
||||
}
|
||||
|
||||
} // namespace blender::nodes
|
||||
|
||||
void register_node_type_geo_input_index()
|
||||
{
|
||||
static bNodeType ntype;
|
||||
|
||||
geo_node_type_base(&ntype, GEO_NODE_INPUT_INDEX, "Index", NODE_CLASS_INPUT, 0);
|
||||
ntype.geometry_node_execute = blender::nodes::geo_node_input_index_exec;
|
||||
ntype.declare = blender::nodes::geo_node_input_index_declare;
|
||||
nodeRegisterType(&ntype);
|
||||
}
|
211
source/blender/nodes/geometry/nodes/node_geo_input_normal.cc
Normal file
211
source/blender/nodes/geometry/nodes/node_geo_input_normal.cc
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "DNA_mesh_types.h"
|
||||
#include "DNA_meshdata_types.h"
|
||||
|
||||
#include "BKE_mesh.h"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
static void geo_node_input_normal_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_output<decl::Vector>("Normal");
|
||||
}
|
||||
|
||||
static GVArrayPtr mesh_face_normals(const Mesh &mesh,
|
||||
const Span<MVert> verts,
|
||||
const Span<MPoly> polys,
|
||||
const Span<MLoop> loops,
|
||||
const IndexMask mask)
|
||||
{
|
||||
/* Use existing normals to avoid unnecessarily recalculating them, if possible. */
|
||||
if (!(mesh.runtime.cd_dirty_poly & CD_MASK_NORMAL) &&
|
||||
CustomData_has_layer(&mesh.pdata, CD_NORMAL)) {
|
||||
const void *data = CustomData_get_layer(&mesh.pdata, CD_NORMAL);
|
||||
|
||||
return std::make_unique<fn::GVArray_For_Span<float3>>(
|
||||
Span<float3>((const float3 *)data, polys.size()));
|
||||
}
|
||||
|
||||
auto normal_fn = [verts, polys, loops](const int i) -> float3 {
|
||||
float3 normal;
|
||||
const MPoly &poly = polys[i];
|
||||
BKE_mesh_calc_poly_normal(&poly, &loops[poly.loopstart], verts.data(), normal);
|
||||
return normal;
|
||||
};
|
||||
|
||||
return std::make_unique<
|
||||
fn::GVArray_For_EmbeddedVArray<float3, VArray_For_Func<float3, decltype(normal_fn)>>>(
|
||||
mask.min_array_size(), mask.min_array_size(), normal_fn);
|
||||
}
|
||||
|
||||
static GVArrayPtr mesh_vertex_normals(const Mesh &mesh,
|
||||
const Span<MVert> verts,
|
||||
const Span<MPoly> polys,
|
||||
const Span<MLoop> loops,
|
||||
const IndexMask mask)
|
||||
{
|
||||
/* Use existing normals to avoid unnecessarily recalculating them, if possible. */
|
||||
if (!(mesh.runtime.cd_dirty_vert & CD_MASK_NORMAL) &&
|
||||
CustomData_has_layer(&mesh.vdata, CD_NORMAL)) {
|
||||
const void *data = CustomData_get_layer(&mesh.pdata, CD_NORMAL);
|
||||
|
||||
return std::make_unique<fn::GVArray_For_Span<float3>>(
|
||||
Span<float3>((const float3 *)data, mesh.totvert));
|
||||
}
|
||||
|
||||
/* If the normals are dirty, they must be recalculated for the output of this node's field
|
||||
* source. Ideally vertex normals could be calculated lazily on a const mesh, but that's not
|
||||
* possible at the moment, so we take ownership of the results. Sadly we must also create a copy
|
||||
* of MVert to use the mesh normals API. This can be improved by adding mutex-protected lazy
|
||||
* calculation of normals on meshes.
|
||||
*
|
||||
* Use mask.min_array_size() to avoid calculating a final chunk of data if possible. */
|
||||
Array<MVert> temp_verts(verts);
|
||||
Array<float3> normals(verts.size()); /* Use full size for accumulation from faces. */
|
||||
BKE_mesh_calc_normals_poly_and_vertex(temp_verts.data(),
|
||||
mask.min_array_size(),
|
||||
loops.data(),
|
||||
loops.size(),
|
||||
polys.data(),
|
||||
polys.size(),
|
||||
nullptr,
|
||||
(float(*)[3])normals.data());
|
||||
|
||||
return std::make_unique<fn::GVArray_For_ArrayContainer<Array<float3>>>(std::move(normals));
|
||||
}
|
||||
|
||||
static const GVArray *construct_mesh_normals_gvarray(const MeshComponent &mesh_component,
|
||||
const Mesh &mesh,
|
||||
const IndexMask mask,
|
||||
const AttributeDomain domain,
|
||||
ResourceScope &scope)
|
||||
{
|
||||
Span<MVert> verts{mesh.mvert, mesh.totvert};
|
||||
Span<MEdge> edges{mesh.medge, mesh.totedge};
|
||||
Span<MPoly> polys{mesh.mpoly, mesh.totpoly};
|
||||
Span<MLoop> loops{mesh.mloop, mesh.totloop};
|
||||
|
||||
switch (domain) {
|
||||
case ATTR_DOMAIN_FACE: {
|
||||
return scope.add_value(mesh_face_normals(mesh, verts, polys, loops, mask), __func__).get();
|
||||
}
|
||||
case ATTR_DOMAIN_POINT: {
|
||||
return scope.add_value(mesh_vertex_normals(mesh, verts, polys, loops, mask), __func__).get();
|
||||
}
|
||||
case ATTR_DOMAIN_EDGE: {
|
||||
/* In this case, start with vertex normals and convert to the edge domain, since the
|
||||
* conversion from edges to vertices is very simple. Use the full mask since the edges
|
||||
* might use the vertex normal from any index. */
|
||||
GVArrayPtr vert_normals = mesh_vertex_normals(
|
||||
mesh, verts, polys, loops, IndexRange(verts.size()));
|
||||
Span<float3> vert_normals_span = vert_normals->get_internal_span().typed<float3>();
|
||||
Array<float3> edge_normals(mask.min_array_size());
|
||||
|
||||
/* Use "manual" domain interpolation instead of the GeometryComponent API to avoid
|
||||
* calculating unnecessary values and to allow normalizing the result much more simply. */
|
||||
for (const int i : mask) {
|
||||
const MEdge &edge = edges[i];
|
||||
edge_normals[i] = float3::interpolate(
|
||||
vert_normals_span[edge.v1], vert_normals_span[edge.v2], 0.5f)
|
||||
.normalized();
|
||||
}
|
||||
|
||||
return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float3>>>(
|
||||
__func__, std::move(edge_normals));
|
||||
}
|
||||
case ATTR_DOMAIN_CORNER: {
|
||||
/* The normals on corners are just the mesh's face normals, so start with the face normal
|
||||
* array and copy the face normal for each of its corners. */
|
||||
GVArrayPtr face_normals = mesh_face_normals(
|
||||
mesh, verts, polys, loops, IndexRange(polys.size()));
|
||||
|
||||
/* In this case using the mesh component's generic domain interpolation is fine, the data
|
||||
* will still be normalized, since the face normal is just copied to every corner. */
|
||||
GVArrayPtr loop_normals = mesh_component.attribute_try_adapt_domain(
|
||||
std::move(face_normals), ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER);
|
||||
return scope.add_value(std::move(loop_normals), __func__).get();
|
||||
}
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
class NormalFieldInput final : public fn::FieldInput {
|
||||
public:
|
||||
NormalFieldInput() : fn::FieldInput(CPPType::get<float3>(), "Normal")
|
||||
{
|
||||
}
|
||||
|
||||
const GVArray *get_varray_for_context(const fn::FieldContext &context,
|
||||
IndexMask mask,
|
||||
ResourceScope &scope) const final
|
||||
{
|
||||
if (const GeometryComponentFieldContext *geometry_context =
|
||||
dynamic_cast<const GeometryComponentFieldContext *>(&context)) {
|
||||
|
||||
const GeometryComponent &component = geometry_context->geometry_component();
|
||||
const AttributeDomain domain = geometry_context->domain();
|
||||
|
||||
if (component.type() == GEO_COMPONENT_TYPE_MESH) {
|
||||
const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component);
|
||||
const Mesh *mesh = mesh_component.get_for_read();
|
||||
if (mesh == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return construct_mesh_normals_gvarray(mesh_component, *mesh, mask, domain, scope);
|
||||
}
|
||||
if (component.type() == GEO_COMPONENT_TYPE_CURVE) {
|
||||
/* TODO: Add curve normals support. */
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint64_t hash() const override
|
||||
{
|
||||
/* Some random constant hash. */
|
||||
return 669605641;
|
||||
}
|
||||
|
||||
bool is_equal_to(const fn::FieldNode &other) const override
|
||||
{
|
||||
return dynamic_cast<const NormalFieldInput *>(&other) != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
static void geo_node_input_normal_exec(GeoNodeExecParams params)
|
||||
{
|
||||
Field<float3> normal_field{std::make_shared<NormalFieldInput>()};
|
||||
params.set_output("Normal", std::move(normal_field));
|
||||
}
|
||||
|
||||
} // namespace blender::nodes
|
||||
|
||||
void register_node_type_geo_input_normal()
|
||||
{
|
||||
static bNodeType ntype;
|
||||
|
||||
geo_node_type_base(&ntype, GEO_NODE_INPUT_NORMAL, "Normal", NODE_CLASS_INPUT, 0);
|
||||
ntype.geometry_node_execute = blender::nodes::geo_node_input_normal_exec;
|
||||
ntype.declare = blender::nodes::geo_node_input_normal_declare;
|
||||
nodeRegisterType(&ntype);
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
static void geo_node_input_position_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_output<decl::Vector>("Position");
|
||||
}
|
||||
|
||||
static void geo_node_input_position_exec(GeoNodeExecParams params)
|
||||
{
|
||||
Field<float3> position_field{
|
||||
std::make_shared<bke::AttributeFieldInput>("position", CPPType::get<float3>())};
|
||||
params.set_output("Position", std::move(position_field));
|
||||
}
|
||||
|
||||
} // namespace blender::nodes
|
||||
|
||||
void register_node_type_geo_input_position()
|
||||
{
|
||||
static bNodeType ntype;
|
||||
|
||||
geo_node_type_base(&ntype, GEO_NODE_INPUT_POSITION, "Position", NODE_CLASS_INPUT, 0);
|
||||
ntype.geometry_node_execute = blender::nodes::geo_node_input_position_exec;
|
||||
ntype.declare = blender::nodes::geo_node_input_position_declare;
|
||||
nodeRegisterType(&ntype);
|
||||
}
|
@@ -161,34 +161,35 @@ static Array<const GeometryComponent *> to_base_components(Span<const Component
|
||||
return components;
|
||||
}
|
||||
|
||||
static Map<std::string, AttributeMetaData> get_final_attribute_info(
|
||||
static Map<AttributeIDRef, AttributeMetaData> get_final_attribute_info(
|
||||
Span<const GeometryComponent *> components, Span<StringRef> ignored_attributes)
|
||||
{
|
||||
Map<std::string, AttributeMetaData> info;
|
||||
Map<AttributeIDRef, AttributeMetaData> info;
|
||||
|
||||
for (const GeometryComponent *component : components) {
|
||||
component->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
|
||||
if (ignored_attributes.contains(name)) {
|
||||
return true;
|
||||
}
|
||||
info.add_or_modify(
|
||||
name,
|
||||
[&](AttributeMetaData *meta_data_final) { *meta_data_final = meta_data; },
|
||||
[&](AttributeMetaData *meta_data_final) {
|
||||
meta_data_final->data_type = blender::bke::attribute_data_type_highest_complexity(
|
||||
{meta_data_final->data_type, meta_data.data_type});
|
||||
meta_data_final->domain = blender::bke::attribute_domain_highest_priority(
|
||||
{meta_data_final->domain, meta_data.domain});
|
||||
});
|
||||
return true;
|
||||
});
|
||||
component->attribute_foreach(
|
||||
[&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
|
||||
if (attribute_id.is_named() && ignored_attributes.contains(attribute_id.name())) {
|
||||
return true;
|
||||
}
|
||||
info.add_or_modify(
|
||||
attribute_id,
|
||||
[&](AttributeMetaData *meta_data_final) { *meta_data_final = meta_data; },
|
||||
[&](AttributeMetaData *meta_data_final) {
|
||||
meta_data_final->data_type = blender::bke::attribute_data_type_highest_complexity(
|
||||
{meta_data_final->data_type, meta_data.data_type});
|
||||
meta_data_final->domain = blender::bke::attribute_domain_highest_priority(
|
||||
{meta_data_final->domain, meta_data.domain});
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
static void fill_new_attribute(Span<const GeometryComponent *> src_components,
|
||||
StringRef attribute_name,
|
||||
const AttributeIDRef &attribute_id,
|
||||
const CustomDataType data_type,
|
||||
const AttributeDomain domain,
|
||||
GMutableSpan dst_span)
|
||||
@@ -203,7 +204,7 @@ static void fill_new_attribute(Span<const GeometryComponent *> src_components,
|
||||
continue;
|
||||
}
|
||||
GVArrayPtr read_attribute = component->attribute_get_for_read(
|
||||
attribute_name, domain, data_type, nullptr);
|
||||
attribute_id, domain, data_type, nullptr);
|
||||
|
||||
GVArray_GSpan src_span{*read_attribute};
|
||||
const void *src_buffer = src_span.data();
|
||||
@@ -218,20 +219,21 @@ static void join_attributes(Span<const GeometryComponent *> src_components,
|
||||
GeometryComponent &result,
|
||||
Span<StringRef> ignored_attributes = {})
|
||||
{
|
||||
const Map<std::string, AttributeMetaData> info = get_final_attribute_info(src_components,
|
||||
ignored_attributes);
|
||||
const Map<AttributeIDRef, AttributeMetaData> info = get_final_attribute_info(src_components,
|
||||
ignored_attributes);
|
||||
|
||||
for (const Map<std::string, AttributeMetaData>::Item &item : info.items()) {
|
||||
const StringRef name = item.key;
|
||||
for (const Map<AttributeIDRef, AttributeMetaData>::Item &item : info.items()) {
|
||||
const AttributeIDRef attribute_id = item.key;
|
||||
const AttributeMetaData &meta_data = item.value;
|
||||
|
||||
OutputAttribute write_attribute = result.attribute_try_get_for_output_only(
|
||||
name, meta_data.domain, meta_data.data_type);
|
||||
attribute_id, meta_data.domain, meta_data.data_type);
|
||||
if (!write_attribute) {
|
||||
continue;
|
||||
}
|
||||
GMutableSpan dst_span = write_attribute.as_span();
|
||||
fill_new_attribute(src_components, name, meta_data.data_type, meta_data.domain, dst_span);
|
||||
fill_new_attribute(
|
||||
src_components, attribute_id, meta_data.data_type, meta_data.domain, dst_span);
|
||||
write_attribute.save();
|
||||
}
|
||||
}
|
||||
@@ -306,7 +308,7 @@ static void join_components(Span<const VolumeComponent *> src_components, Geomet
|
||||
* \note This takes advantage of the fact that creating attributes on joined curves never
|
||||
* changes a point attribute into a spline attribute; it is always the other way around.
|
||||
*/
|
||||
static void ensure_control_point_attribute(const StringRef name,
|
||||
static void ensure_control_point_attribute(const AttributeIDRef &attribute_id,
|
||||
const CustomDataType data_type,
|
||||
Span<CurveComponent *> src_components,
|
||||
CurveEval &result)
|
||||
@@ -321,7 +323,7 @@ static void ensure_control_point_attribute(const StringRef name,
|
||||
const CurveEval *current_curve = src_components[src_component_index]->get_for_read();
|
||||
|
||||
for (SplinePtr &spline : splines) {
|
||||
std::optional<GSpan> attribute = spline->attributes.get_for_read(name);
|
||||
std::optional<GSpan> attribute = spline->attributes.get_for_read(attribute_id);
|
||||
|
||||
if (attribute) {
|
||||
if (attribute->type() != type) {
|
||||
@@ -334,22 +336,22 @@ static void ensure_control_point_attribute(const StringRef name,
|
||||
conversions.try_convert(std::make_unique<GVArray_For_GSpan>(*attribute), type)
|
||||
->materialize(converted_buffer);
|
||||
|
||||
spline->attributes.remove(name);
|
||||
spline->attributes.create_by_move(name, data_type, converted_buffer);
|
||||
spline->attributes.remove(attribute_id);
|
||||
spline->attributes.create_by_move(attribute_id, data_type, converted_buffer);
|
||||
}
|
||||
}
|
||||
else {
|
||||
spline->attributes.create(name, data_type);
|
||||
spline->attributes.create(attribute_id, data_type);
|
||||
|
||||
if (current_curve->attributes.get_for_read(name)) {
|
||||
if (current_curve->attributes.get_for_read(attribute_id)) {
|
||||
/* In this case the attribute did not exist, but there is a spline domain attribute
|
||||
* we can retrieve a value from, as a spline to point domain conversion. So fill the
|
||||
* new attribute with the value for this spline. */
|
||||
GVArrayPtr current_curve_attribute = current_curve->attributes.get_for_read(
|
||||
name, data_type, nullptr);
|
||||
attribute_id, data_type, nullptr);
|
||||
|
||||
BLI_assert(spline->attributes.get_for_read(name));
|
||||
std::optional<GMutableSpan> new_attribute = spline->attributes.get_for_write(name);
|
||||
BLI_assert(spline->attributes.get_for_read(attribute_id));
|
||||
std::optional<GMutableSpan> new_attribute = spline->attributes.get_for_write(attribute_id);
|
||||
|
||||
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
|
||||
current_curve_attribute->get(spline_index_in_component, buffer);
|
||||
@@ -371,15 +373,15 @@ static void ensure_control_point_attribute(const StringRef name,
|
||||
/**
|
||||
* Fill data for an attribute on the new curve based on all source curves.
|
||||
*/
|
||||
static void ensure_spline_attribute(const StringRef name,
|
||||
static void ensure_spline_attribute(const AttributeIDRef &attribute_id,
|
||||
const CustomDataType data_type,
|
||||
Span<CurveComponent *> src_components,
|
||||
CurveEval &result)
|
||||
{
|
||||
const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type);
|
||||
|
||||
result.attributes.create(name, data_type);
|
||||
GMutableSpan result_attribute = *result.attributes.get_for_write(name);
|
||||
result.attributes.create(attribute_id, data_type);
|
||||
GMutableSpan result_attribute = *result.attributes.get_for_write(attribute_id);
|
||||
|
||||
int offset = 0;
|
||||
for (const CurveComponent *component : src_components) {
|
||||
@@ -388,7 +390,7 @@ static void ensure_spline_attribute(const StringRef name,
|
||||
if (size == 0) {
|
||||
continue;
|
||||
}
|
||||
GVArrayPtr read_attribute = curve.attributes.get_for_read(name, data_type, nullptr);
|
||||
GVArrayPtr read_attribute = curve.attributes.get_for_read(attribute_id, data_type, nullptr);
|
||||
GVArray_GSpan src_span{*read_attribute};
|
||||
|
||||
const void *src_buffer = src_span.data();
|
||||
@@ -406,19 +408,19 @@ static void ensure_spline_attribute(const StringRef name,
|
||||
* \warning Splines have been moved out of the source components at this point, so it
|
||||
* is important to only read curve-level data (spline domain attributes) from them.
|
||||
*/
|
||||
static void join_curve_attributes(const Map<std::string, AttributeMetaData> &info,
|
||||
static void join_curve_attributes(const Map<AttributeIDRef, AttributeMetaData> &info,
|
||||
Span<CurveComponent *> src_components,
|
||||
CurveEval &result)
|
||||
{
|
||||
for (const Map<std::string, AttributeMetaData>::Item &item : info.items()) {
|
||||
const StringRef name = item.key;
|
||||
for (const Map<AttributeIDRef, AttributeMetaData>::Item &item : info.items()) {
|
||||
const AttributeIDRef attribute_id = item.key;
|
||||
const AttributeMetaData meta_data = item.value;
|
||||
|
||||
if (meta_data.domain == ATTR_DOMAIN_CURVE) {
|
||||
ensure_spline_attribute(name, meta_data.data_type, src_components, result);
|
||||
ensure_spline_attribute(attribute_id, meta_data.data_type, src_components, result);
|
||||
}
|
||||
else {
|
||||
ensure_control_point_attribute(name, meta_data.data_type, src_components, result);
|
||||
ensure_control_point_attribute(attribute_id, meta_data.data_type, src_components, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,7 +448,7 @@ static void join_curve_components(MutableSpan<GeometrySet> src_geometry_sets, Ge
|
||||
}
|
||||
|
||||
/* Retrieve attribute info before moving the splines out of the input components. */
|
||||
const Map<std::string, AttributeMetaData> info = get_final_attribute_info(
|
||||
const Map<AttributeIDRef, AttributeMetaData> info = get_final_attribute_info(
|
||||
{(const GeometryComponent **)src_components.data(), src_components.size()},
|
||||
{"position", "radius", "tilt", "cyclic", "resolution"});
|
||||
|
||||
|
@@ -52,10 +52,10 @@ static void copy_attributes_to_points(CurveEval &curve,
|
||||
Span<Vector<int>> point_to_vert_maps)
|
||||
{
|
||||
MutableSpan<SplinePtr> splines = curve.splines();
|
||||
Set<std::string> source_attribute_names = mesh_component.attribute_names();
|
||||
Set<AttributeIDRef> source_attribute_ids = mesh_component.attribute_ids();
|
||||
|
||||
/* Copy builtin control point attributes. */
|
||||
if (source_attribute_names.contains_as("tilt")) {
|
||||
if (source_attribute_ids.contains("tilt")) {
|
||||
const GVArray_Typed<float> tilt_attribute = mesh_component.attribute_get_for_read<float>(
|
||||
"tilt", ATTR_DOMAIN_POINT, 0.0f);
|
||||
threading::parallel_for(splines.index_range(), 256, [&](IndexRange range) {
|
||||
@@ -64,9 +64,9 @@ static void copy_attributes_to_points(CurveEval &curve,
|
||||
*tilt_attribute, point_to_vert_maps[i], splines[i]->tilts());
|
||||
}
|
||||
});
|
||||
source_attribute_names.remove_contained_as("tilt");
|
||||
source_attribute_ids.remove_contained("tilt");
|
||||
}
|
||||
if (source_attribute_names.contains_as("radius")) {
|
||||
if (source_attribute_ids.contains("radius")) {
|
||||
const GVArray_Typed<float> radius_attribute = mesh_component.attribute_get_for_read<float>(
|
||||
"radius", ATTR_DOMAIN_POINT, 1.0f);
|
||||
threading::parallel_for(splines.index_range(), 256, [&](IndexRange range) {
|
||||
@@ -75,15 +75,15 @@ static void copy_attributes_to_points(CurveEval &curve,
|
||||
*radius_attribute, point_to_vert_maps[i], splines[i]->radii());
|
||||
}
|
||||
});
|
||||
source_attribute_names.remove_contained_as("radius");
|
||||
source_attribute_ids.remove_contained("radius");
|
||||
}
|
||||
|
||||
/* Don't copy other builtin control point attributes. */
|
||||
source_attribute_names.remove_as("position");
|
||||
source_attribute_ids.remove("position");
|
||||
|
||||
/* Copy dynamic control point attributes. */
|
||||
for (const StringRef name : source_attribute_names) {
|
||||
const GVArrayPtr mesh_attribute = mesh_component.attribute_try_get_for_read(name,
|
||||
for (const AttributeIDRef &attribute_id : source_attribute_ids) {
|
||||
const GVArrayPtr mesh_attribute = mesh_component.attribute_try_get_for_read(attribute_id,
|
||||
ATTR_DOMAIN_POINT);
|
||||
/* Some attributes might not exist if they were builtin attribute on domains that don't
|
||||
* have any elements, i.e. a face attribute on the output of the line primitive node. */
|
||||
@@ -96,8 +96,9 @@ static void copy_attributes_to_points(CurveEval &curve,
|
||||
threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) {
|
||||
for (const int i : range) {
|
||||
/* Create attribute on the spline points. */
|
||||
splines[i]->attributes.create(name, data_type);
|
||||
std::optional<GMutableSpan> spline_attribute = splines[i]->attributes.get_for_write(name);
|
||||
splines[i]->attributes.create(attribute_id, data_type);
|
||||
std::optional<GMutableSpan> spline_attribute = splines[i]->attributes.get_for_write(
|
||||
attribute_id);
|
||||
BLI_assert(spline_attribute);
|
||||
|
||||
/* Copy attribute based on the map for this spline. */
|
||||
|
@@ -277,17 +277,17 @@ BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh,
|
||||
BLI_NOINLINE static void interpolate_existing_attributes(
|
||||
Span<GeometryInstanceGroup> set_groups,
|
||||
Span<int> instance_start_offsets,
|
||||
const Map<std::string, AttributeKind> &attributes,
|
||||
const Map<AttributeIDRef, AttributeKind> &attributes,
|
||||
GeometryComponent &component,
|
||||
Span<Vector<float3>> bary_coords_array,
|
||||
Span<Vector<int>> looptri_indices_array)
|
||||
{
|
||||
for (Map<std::string, AttributeKind>::Item entry : attributes.items()) {
|
||||
StringRef attribute_name = entry.key;
|
||||
for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) {
|
||||
const AttributeIDRef attribute_id = entry.key;
|
||||
const CustomDataType output_data_type = entry.value.data_type;
|
||||
/* The output domain is always #ATTR_DOMAIN_POINT, since we are creating a point cloud. */
|
||||
OutputAttribute attribute_out = component.attribute_try_get_for_output_only(
|
||||
attribute_name, ATTR_DOMAIN_POINT, output_data_type);
|
||||
attribute_id, ATTR_DOMAIN_POINT, output_data_type);
|
||||
if (!attribute_out) {
|
||||
continue;
|
||||
}
|
||||
@@ -301,7 +301,7 @@ BLI_NOINLINE static void interpolate_existing_attributes(
|
||||
const Mesh &mesh = *source_component.get_for_read();
|
||||
|
||||
std::optional<AttributeMetaData> attribute_info = component.attribute_get_meta_data(
|
||||
attribute_name);
|
||||
attribute_id);
|
||||
if (!attribute_info) {
|
||||
i_instance += set_group.transforms.size();
|
||||
continue;
|
||||
@@ -309,7 +309,7 @@ BLI_NOINLINE static void interpolate_existing_attributes(
|
||||
|
||||
const AttributeDomain source_domain = attribute_info->domain;
|
||||
GVArrayPtr source_attribute = source_component.attribute_get_for_read(
|
||||
attribute_name, source_domain, output_data_type, nullptr);
|
||||
attribute_id, source_domain, output_data_type, nullptr);
|
||||
if (!source_attribute) {
|
||||
i_instance += set_group.transforms.size();
|
||||
continue;
|
||||
@@ -406,7 +406,7 @@ BLI_NOINLINE static void compute_special_attributes(Span<GeometryInstanceGroup>
|
||||
BLI_NOINLINE static void add_remaining_point_attributes(
|
||||
Span<GeometryInstanceGroup> set_groups,
|
||||
Span<int> instance_start_offsets,
|
||||
const Map<std::string, AttributeKind> &attributes,
|
||||
const Map<AttributeIDRef, AttributeKind> &attributes,
|
||||
GeometryComponent &component,
|
||||
Span<Vector<float3>> bary_coords_array,
|
||||
Span<Vector<int>> looptri_indices_array)
|
||||
@@ -629,7 +629,7 @@ static void geo_node_point_distribute_exec(GeoNodeExecParams params)
|
||||
PointCloudComponent &point_component =
|
||||
geometry_set_out.get_component_for_write<PointCloudComponent>();
|
||||
|
||||
Map<std::string, AttributeKind> attributes;
|
||||
Map<AttributeIDRef, AttributeKind> attributes;
|
||||
bke::geometry_set_gather_instances_attribute_info(
|
||||
set_groups, {GEO_COMPONENT_TYPE_MESH}, {"position", "normal", "id"}, attributes);
|
||||
add_remaining_point_attributes(set_groups,
|
||||
|
@@ -53,8 +53,8 @@ void copy_point_attributes_based_on_mask(const GeometryComponent &in_component,
|
||||
Span<bool> masks,
|
||||
const bool invert)
|
||||
{
|
||||
for (const std::string &name : in_component.attribute_names()) {
|
||||
ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(name);
|
||||
for (const AttributeIDRef &attribute_id : in_component.attribute_ids()) {
|
||||
ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(attribute_id);
|
||||
const CustomDataType data_type = bke::cpp_type_to_custom_data_type(attribute.varray->type());
|
||||
|
||||
/* Only copy point attributes. Theoretically this could interpolate attributes on other
|
||||
@@ -65,7 +65,7 @@ void copy_point_attributes_based_on_mask(const GeometryComponent &in_component,
|
||||
}
|
||||
|
||||
OutputAttribute result_attribute = result_component.attribute_try_get_for_output_only(
|
||||
name, ATTR_DOMAIN_POINT, data_type);
|
||||
attribute_id, ATTR_DOMAIN_POINT, data_type);
|
||||
|
||||
attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
|
79
source/blender/nodes/geometry/nodes/node_geo_set_position.cc
Normal file
79
source/blender/nodes/geometry/nodes/node_geo_set_position.cc
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "DEG_depsgraph_query.h"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
static void geo_node_set_position_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_input<decl::Geometry>("Geometry");
|
||||
b.add_input<decl::Vector>("Position");
|
||||
b.add_input<decl::Bool>("Selection").default_value(true);
|
||||
b.add_output<decl::Geometry>("Geometry");
|
||||
}
|
||||
|
||||
static void set_position_in_component(GeometryComponent &component,
|
||||
const Field<bool> &selection_field,
|
||||
const Field<float3> &position_field)
|
||||
{
|
||||
GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT};
|
||||
const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT);
|
||||
|
||||
fn::FieldEvaluator selection_evaluator{field_context, domain_size};
|
||||
selection_evaluator.add(selection_field);
|
||||
selection_evaluator.evaluate();
|
||||
const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0);
|
||||
|
||||
OutputAttribute_Typed<float3> positions = component.attribute_try_get_for_output<float3>(
|
||||
"position", ATTR_DOMAIN_POINT, {0, 0, 0});
|
||||
fn::FieldEvaluator position_evaluator{field_context, &selection};
|
||||
position_evaluator.add_with_destination(position_field, positions.varray());
|
||||
position_evaluator.evaluate();
|
||||
positions.save();
|
||||
}
|
||||
|
||||
static void geo_node_set_position_exec(GeoNodeExecParams params)
|
||||
{
|
||||
GeometrySet geometry = params.extract_input<GeometrySet>("Geometry");
|
||||
geometry = geometry_set_realize_instances(geometry);
|
||||
Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
|
||||
Field<float3> position_field = params.extract_input<Field<float3>>("Position");
|
||||
|
||||
for (const GeometryComponentType type :
|
||||
{GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE}) {
|
||||
if (geometry.has(type)) {
|
||||
set_position_in_component(
|
||||
geometry.get_component_for_write(type), selection_field, position_field);
|
||||
}
|
||||
}
|
||||
|
||||
params.set_output("Geometry", std::move(geometry));
|
||||
}
|
||||
|
||||
} // namespace blender::nodes
|
||||
|
||||
void register_node_type_geo_set_position()
|
||||
{
|
||||
static bNodeType ntype;
|
||||
|
||||
geo_node_type_base(&ntype, GEO_NODE_SET_POSITION, "Set Position", NODE_CLASS_GEOMETRY, 0);
|
||||
ntype.geometry_node_execute = blender::nodes::geo_node_set_position_exec;
|
||||
ntype.declare = blender::nodes::geo_node_set_position_declare;
|
||||
nodeRegisterType(&ntype);
|
||||
}
|
@@ -161,8 +161,10 @@ GeometryValueLog::GeometryValueLog(const GeometrySet &geometry_set, bool log_ful
|
||||
{
|
||||
bke::geometry_set_instances_attribute_foreach(
|
||||
geometry_set,
|
||||
[&](StringRefNull attribute_name, const AttributeMetaData &meta_data) {
|
||||
this->attributes_.append({attribute_name, meta_data.domain, meta_data.data_type});
|
||||
[&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
|
||||
if (attribute_id.is_named()) {
|
||||
this->attributes_.append({attribute_id.name(), meta_data.domain, meta_data.data_type});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
8);
|
||||
|
@@ -48,6 +48,7 @@
|
||||
#include "NOD_socket.h"
|
||||
|
||||
#include "FN_cpp_type_make.hh"
|
||||
#include "FN_field.hh"
|
||||
|
||||
using namespace blender;
|
||||
using blender::nodes::SocketDeclarationPtr;
|
||||
@@ -701,8 +702,14 @@ static bNodeSocketType *make_socket_type_bool()
|
||||
socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) {
|
||||
*(bool *)r_value = ((bNodeSocketValueBoolean *)socket.default_value)->value;
|
||||
};
|
||||
socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type;
|
||||
socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value;
|
||||
socktype->get_geometry_nodes_cpp_type = []() {
|
||||
return &blender::fn::CPPType::get<blender::fn::Field<bool>>();
|
||||
};
|
||||
socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) {
|
||||
bool value;
|
||||
socket.typeinfo->get_base_cpp_value(socket, &value);
|
||||
new (r_value) blender::fn::Field<bool>(blender::fn::make_constant_field(value));
|
||||
};
|
||||
return socktype;
|
||||
}
|
||||
|
||||
@@ -713,8 +720,14 @@ static bNodeSocketType *make_socket_type_float(PropertySubType subtype)
|
||||
socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) {
|
||||
*(float *)r_value = ((bNodeSocketValueFloat *)socket.default_value)->value;
|
||||
};
|
||||
socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type;
|
||||
socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value;
|
||||
socktype->get_geometry_nodes_cpp_type = []() {
|
||||
return &blender::fn::CPPType::get<blender::fn::Field<float>>();
|
||||
};
|
||||
socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) {
|
||||
float value;
|
||||
socket.typeinfo->get_base_cpp_value(socket, &value);
|
||||
new (r_value) blender::fn::Field<float>(blender::fn::make_constant_field(value));
|
||||
};
|
||||
return socktype;
|
||||
}
|
||||
|
||||
@@ -725,8 +738,14 @@ static bNodeSocketType *make_socket_type_int(PropertySubType subtype)
|
||||
socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) {
|
||||
*(int *)r_value = ((bNodeSocketValueInt *)socket.default_value)->value;
|
||||
};
|
||||
socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type;
|
||||
socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value;
|
||||
socktype->get_geometry_nodes_cpp_type = []() {
|
||||
return &blender::fn::CPPType::get<blender::fn::Field<int>>();
|
||||
};
|
||||
socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) {
|
||||
int value;
|
||||
socket.typeinfo->get_base_cpp_value(socket, &value);
|
||||
new (r_value) blender::fn::Field<int>(blender::fn::make_constant_field(value));
|
||||
};
|
||||
return socktype;
|
||||
}
|
||||
|
||||
@@ -737,8 +756,14 @@ static bNodeSocketType *make_socket_type_vector(PropertySubType subtype)
|
||||
socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) {
|
||||
*(blender::float3 *)r_value = ((bNodeSocketValueVector *)socket.default_value)->value;
|
||||
};
|
||||
socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type;
|
||||
socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value;
|
||||
socktype->get_geometry_nodes_cpp_type = []() {
|
||||
return &blender::fn::CPPType::get<blender::fn::Field<blender::float3>>();
|
||||
};
|
||||
socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) {
|
||||
blender::float3 value;
|
||||
socket.typeinfo->get_base_cpp_value(socket, &value);
|
||||
new (r_value) blender::fn::Field<blender::float3>(blender::fn::make_constant_field(value));
|
||||
};
|
||||
return socktype;
|
||||
}
|
||||
|
||||
@@ -751,8 +776,15 @@ static bNodeSocketType *make_socket_type_rgba()
|
||||
socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) {
|
||||
*(blender::ColorGeometry4f *)r_value = ((bNodeSocketValueRGBA *)socket.default_value)->value;
|
||||
};
|
||||
socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type;
|
||||
socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value;
|
||||
socktype->get_geometry_nodes_cpp_type = []() {
|
||||
return &blender::fn::CPPType::get<blender::fn::Field<blender::ColorGeometry4f>>();
|
||||
};
|
||||
socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) {
|
||||
blender::ColorGeometry4f value;
|
||||
socket.typeinfo->get_base_cpp_value(socket, &value);
|
||||
new (r_value)
|
||||
blender::fn::Field<blender::ColorGeometry4f>(blender::fn::make_constant_field(value));
|
||||
};
|
||||
return socktype;
|
||||
}
|
||||
|
||||
@@ -763,8 +795,15 @@ static bNodeSocketType *make_socket_type_string()
|
||||
socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) {
|
||||
new (r_value) std::string(((bNodeSocketValueString *)socket.default_value)->value);
|
||||
};
|
||||
socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type;
|
||||
socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value;
|
||||
socktype->get_geometry_nodes_cpp_type = []() {
|
||||
return &blender::fn::CPPType::get<blender::fn::Field<std::string>>();
|
||||
};
|
||||
socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) {
|
||||
std::string value;
|
||||
value.~basic_string();
|
||||
socket.typeinfo->get_base_cpp_value(socket, &value);
|
||||
new (r_value) blender::fn::Field<std::string>(blender::fn::make_constant_field(value));
|
||||
};
|
||||
return socktype;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user