Geometry Nodes: Add Attribute Convert node
The Attribute Convert node provides functionality to change attributes between different domains and data types. Before it was impossible to write to a UV Map attribute with the attribute math nodes since they did not output a 2D vector type. This makes it possible to "convert into" a UV map attribute. The data type conversion uses the implicit conversions provided by `\nodes\intern\node_tree_multi_function.cc`. The `Auto` domain mode chooses the domain based on the following rules: 1. If the result attribute already exists, use that domain. 2. If the result attribute doesn't exist, use the source attribute domain. 3. Otherwise use the default domain (points). See {T85700} Differential Revision: https://developer.blender.org/D10624
This commit is contained in:
parent
8ab6450abb
commit
670453d1ec
|
@ -485,6 +485,7 @@ geometry_node_categories = [
|
|||
NodeItem("GeometryNodeAttributeRandomize"),
|
||||
NodeItem("GeometryNodeAttributeMath"),
|
||||
NodeItem("GeometryNodeAttributeCompare"),
|
||||
NodeItem("GeometryNodeAttributeConvert"),
|
||||
NodeItem("GeometryNodeAttributeFill"),
|
||||
NodeItem("GeometryNodeAttributeMix"),
|
||||
NodeItem("GeometryNodeAttributeProximity"),
|
||||
|
|
|
@ -39,6 +39,7 @@ struct ReportList;
|
|||
|
||||
/* Attribute.domain */
|
||||
typedef enum AttributeDomain {
|
||||
ATTR_DOMAIN_AUTO = -1, /* Use for nodes to choose automatically based on other data. */
|
||||
ATTR_DOMAIN_POINT = 0, /* Mesh, Hair or PointCloud Point */
|
||||
ATTR_DOMAIN_EDGE = 1, /* Mesh Edge */
|
||||
ATTR_DOMAIN_CORNER = 2, /* Mesh Corner */
|
||||
|
|
|
@ -1373,6 +1373,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
|
|||
#define GEO_NODE_ATTRIBUTE_SEPARATE_XYZ 1028
|
||||
#define GEO_NODE_SUBDIVIDE 1029
|
||||
#define GEO_NODE_ATTRIBUTE_REMOVE 1030
|
||||
#define GEO_NODE_ATTRIBUTE_CONVERT 1031
|
||||
|
||||
/** \} */
|
||||
|
||||
|
|
|
@ -4788,6 +4788,7 @@ static void registerGeometryNodes()
|
|||
register_node_type_geo_attribute_color_ramp();
|
||||
register_node_type_geo_attribute_combine_xyz();
|
||||
register_node_type_geo_attribute_compare();
|
||||
register_node_type_geo_attribute_convert();
|
||||
register_node_type_geo_attribute_fill();
|
||||
register_node_type_geo_attribute_math();
|
||||
register_node_type_geo_attribute_mix();
|
||||
|
|
|
@ -1226,6 +1226,14 @@ typedef struct NodeAttributeSeparateXYZ {
|
|||
uint8_t input_type;
|
||||
} NodeAttributeSeparateXYZ;
|
||||
|
||||
typedef struct NodeAttributeConvert {
|
||||
/* CustomDataType. */
|
||||
uint8_t data_type;
|
||||
char _pad[1];
|
||||
/* AttributeDomain. */
|
||||
int16_t domain;
|
||||
} NodeAttributeConvert;
|
||||
|
||||
/* script node mode */
|
||||
#define NODE_SCRIPT_INTERNAL 0
|
||||
#define NODE_SCRIPT_EXTERNAL 1
|
||||
|
|
|
@ -1929,6 +1929,23 @@ static void rna_GeometryNodeAttributeRandomize_data_type_update(Main *bmain,
|
|||
rna_Node_socket_update(bmain, scene, ptr);
|
||||
}
|
||||
|
||||
static bool attribute_convert_type_supported(const EnumPropertyItem *item)
|
||||
{
|
||||
return ELEM(item->value,
|
||||
CD_PROP_FLOAT,
|
||||
CD_PROP_FLOAT2,
|
||||
CD_PROP_FLOAT3,
|
||||
CD_PROP_COLOR,
|
||||
CD_PROP_BOOL,
|
||||
CD_PROP_INT32);
|
||||
}
|
||||
static const EnumPropertyItem *rna_GeometryNodeAttributeConvert_type_itemf(
|
||||
bContext *UNUSED(C), PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free)
|
||||
{
|
||||
*r_free = true;
|
||||
return itemf_function_check(rna_enum_attribute_type_items, attribute_convert_type_supported);
|
||||
}
|
||||
|
||||
static bool attribute_fill_type_supported(const EnumPropertyItem *item)
|
||||
{
|
||||
return ELEM(
|
||||
|
@ -8647,6 +8664,35 @@ static void def_geo_attribute_fill(StructRNA *srna)
|
|||
"rna_GeometryNodeAttributeFill_domain_itemf");
|
||||
}
|
||||
|
||||
static void def_geo_attribute_convert(StructRNA *srna)
|
||||
{
|
||||
static const EnumPropertyItem rna_enum_attribute_convert_domain_items[] = {
|
||||
{ATTR_DOMAIN_AUTO, "AUTO", 0, "Auto", ""},
|
||||
{ATTR_DOMAIN_POINT, "POINT", 0, "Point", "Attribute on point"},
|
||||
{ATTR_DOMAIN_EDGE, "EDGE", 0, "Edge", "Attribute on mesh edge"},
|
||||
{ATTR_DOMAIN_CORNER, "CORNER", 0, "Corner", "Attribute on mesh polygon corner"},
|
||||
{ATTR_DOMAIN_POLYGON, "POLYGON", 0, "Polygon", "Attribute on mesh polygons"},
|
||||
{0, NULL, 0, NULL, NULL},
|
||||
};
|
||||
|
||||
PropertyRNA *prop;
|
||||
|
||||
RNA_def_struct_sdna_from(srna, "NodeAttributeConvert", "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_GeometryNodeAttributeConvert_type_itemf");
|
||||
RNA_def_property_enum_default(prop, CD_PROP_FLOAT);
|
||||
RNA_def_property_ui_text(prop, "Data Type", "The data type to save the result attribute with");
|
||||
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_convert_domain_items);
|
||||
RNA_def_property_enum_default(prop, ATTR_DOMAIN_AUTO);
|
||||
RNA_def_property_ui_text(prop, "Domain", "The geometry domain to save the result attribute in");
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
|
||||
}
|
||||
|
||||
static void def_geo_attribute_math(StructRNA *srna)
|
||||
{
|
||||
PropertyRNA *prop;
|
||||
|
|
|
@ -146,6 +146,7 @@ set(SRC
|
|||
geometry/nodes/node_geo_attribute_color_ramp.cc
|
||||
geometry/nodes/node_geo_attribute_combine_xyz.cc
|
||||
geometry/nodes/node_geo_attribute_compare.cc
|
||||
geometry/nodes/node_geo_attribute_convert.cc
|
||||
geometry/nodes/node_geo_attribute_fill.cc
|
||||
geometry/nodes/node_geo_attribute_math.cc
|
||||
geometry/nodes/node_geo_attribute_mix.cc
|
||||
|
|
|
@ -30,6 +30,7 @@ void register_node_type_geo_align_rotation_to_vector(void);
|
|||
void register_node_type_geo_attribute_color_ramp(void);
|
||||
void register_node_type_geo_attribute_combine_xyz(void);
|
||||
void register_node_type_geo_attribute_compare(void);
|
||||
void register_node_type_geo_attribute_convert(void);
|
||||
void register_node_type_geo_attribute_fill(void);
|
||||
void register_node_type_geo_attribute_math(void);
|
||||
void register_node_type_geo_attribute_mix(void);
|
||||
|
|
|
@ -300,6 +300,7 @@ DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMBINE_XYZ, def_geo_attribute_combine_
|
|||
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_SEPARATE_XYZ, def_geo_attribute_separate_xyz, "ATTRIBUTE_SEPARATE_XYZ", AttributeSeparateXYZ, "Attribute Separate XYZ", "")
|
||||
DefNode(GeometryNode, GEO_NODE_SUBDIVIDE, 0, "SUBDIVIDE", Subdivide, "Subdivide", "")
|
||||
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_REMOVE, 0, "ATTRIBUTE_REMOVE", AttributeRemove, "Attribute Remove", "")
|
||||
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CONVERT, def_geo_attribute_convert, "ATTRIBUTE_CONVERT", AttributeConvert, "Attribute Convert", "")
|
||||
|
||||
/* undefine macros */
|
||||
#undef DefNode
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* 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"
|
||||
|
||||
#include "UI_interface.h"
|
||||
#include "UI_resources.h"
|
||||
|
||||
static bNodeSocketTemplate geo_node_attribute_convert_in[] = {
|
||||
{SOCK_GEOMETRY, N_("Geometry")},
|
||||
{SOCK_STRING, N_("Attribute")},
|
||||
{SOCK_STRING, N_("Result")},
|
||||
{-1, ""},
|
||||
};
|
||||
|
||||
static bNodeSocketTemplate geo_node_attribute_convert_out[] = {
|
||||
{SOCK_GEOMETRY, N_("Geometry")},
|
||||
{-1, ""},
|
||||
};
|
||||
|
||||
static void geo_node_attribute_convert_layout(uiLayout *layout,
|
||||
bContext *UNUSED(C),
|
||||
PointerRNA *ptr)
|
||||
{
|
||||
uiItemR(layout, ptr, "domain", 0, "", ICON_NONE);
|
||||
uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE);
|
||||
}
|
||||
|
||||
static void geo_node_attribute_convert_init(bNodeTree *UNUSED(tree), bNode *node)
|
||||
{
|
||||
NodeAttributeConvert *data = (NodeAttributeConvert *)MEM_callocN(sizeof(NodeAttributeConvert),
|
||||
__func__);
|
||||
|
||||
data->data_type = CD_PROP_FLOAT;
|
||||
data->domain = ATTR_DOMAIN_AUTO;
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
static AttributeDomain get_result_domain(const GeometryComponent &component,
|
||||
StringRef source_name,
|
||||
StringRef result_name)
|
||||
{
|
||||
ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name);
|
||||
if (result_attribute) {
|
||||
return result_attribute->domain();
|
||||
}
|
||||
ReadAttributePtr source_attribute = component.attribute_try_get_for_read(source_name);
|
||||
if (source_attribute) {
|
||||
return source_attribute->domain();
|
||||
}
|
||||
return ATTR_DOMAIN_POINT;
|
||||
}
|
||||
|
||||
static void attribute_convert_calc(GeometryComponent &component,
|
||||
const GeoNodeExecParams ¶ms,
|
||||
const StringRef source_name,
|
||||
const StringRef result_name,
|
||||
const CustomDataType result_type,
|
||||
const AttributeDomain domain)
|
||||
{
|
||||
const AttributeDomain result_domain = (domain == ATTR_DOMAIN_AUTO) ?
|
||||
get_result_domain(
|
||||
component, source_name, result_name) :
|
||||
domain;
|
||||
|
||||
ReadAttributePtr source_attribute = component.attribute_try_get_for_read(
|
||||
source_name, result_domain, result_type);
|
||||
if (!source_attribute) {
|
||||
params.error_message_add(NodeWarningType::Error,
|
||||
TIP_("No attribute with name \"") + source_name + "\"");
|
||||
return;
|
||||
}
|
||||
|
||||
OutputAttributePtr result_attribute = component.attribute_try_get_for_output(
|
||||
result_name, result_domain, result_type);
|
||||
if (!result_attribute) {
|
||||
return;
|
||||
}
|
||||
|
||||
fn::GSpan source_span = source_attribute->get_span();
|
||||
fn::GMutableSpan result_span = result_attribute->get_span_for_write_only();
|
||||
if (source_span.is_empty() || result_span.is_empty()) {
|
||||
return;
|
||||
}
|
||||
BLI_assert(source_span.size() == result_span.size());
|
||||
|
||||
const CPPType *cpp_type = bke::custom_data_type_to_cpp_type(result_type);
|
||||
BLI_assert(cpp_type != nullptr);
|
||||
|
||||
cpp_type->copy_to_initialized_n(source_span.data(), result_span.data(), result_span.size());
|
||||
|
||||
result_attribute.apply_span_and_save();
|
||||
}
|
||||
|
||||
static void geo_node_attribute_convert_exec(GeoNodeExecParams params)
|
||||
{
|
||||
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
|
||||
|
||||
geometry_set = geometry_set_realize_instances(geometry_set);
|
||||
|
||||
const std::string result_name = params.extract_input<std::string>("Result");
|
||||
const std::string source_name = params.extract_input<std::string>("Attribute");
|
||||
const NodeAttributeConvert &node_storage = *(const NodeAttributeConvert *)params.node().storage;
|
||||
const CustomDataType data_type = static_cast<CustomDataType>(node_storage.data_type);
|
||||
const AttributeDomain domain = static_cast<AttributeDomain>(node_storage.domain);
|
||||
|
||||
if (result_name.empty()) {
|
||||
params.set_output("Geometry", geometry_set);
|
||||
return;
|
||||
}
|
||||
|
||||
if (geometry_set.has<MeshComponent>()) {
|
||||
attribute_convert_calc(geometry_set.get_component_for_write<MeshComponent>(),
|
||||
params,
|
||||
source_name,
|
||||
result_name,
|
||||
data_type,
|
||||
domain);
|
||||
}
|
||||
if (geometry_set.has<PointCloudComponent>()) {
|
||||
attribute_convert_calc(geometry_set.get_component_for_write<PointCloudComponent>(),
|
||||
params,
|
||||
source_name,
|
||||
result_name,
|
||||
data_type,
|
||||
domain);
|
||||
}
|
||||
|
||||
params.set_output("Geometry", geometry_set);
|
||||
}
|
||||
|
||||
} // namespace blender::nodes
|
||||
|
||||
void register_node_type_geo_attribute_convert()
|
||||
{
|
||||
static bNodeType ntype;
|
||||
|
||||
geo_node_type_base(
|
||||
&ntype, GEO_NODE_ATTRIBUTE_CONVERT, "Attribute Convert", NODE_CLASS_ATTRIBUTE, 0);
|
||||
node_type_socket_templates(
|
||||
&ntype, geo_node_attribute_convert_in, geo_node_attribute_convert_out);
|
||||
ntype.geometry_node_execute = blender::nodes::geo_node_attribute_convert_exec;
|
||||
ntype.draw_buttons = geo_node_attribute_convert_layout;
|
||||
node_type_init(&ntype, geo_node_attribute_convert_init);
|
||||
node_type_storage(
|
||||
&ntype, "NodeAttributeConvert", node_free_standard_storage, node_copy_standard_storage);
|
||||
|
||||
nodeRegisterType(&ntype);
|
||||
}
|
Loading…
Reference in New Issue