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:
Charlie Jolly 2021-03-13 11:49:56 -05:00 committed by Hans Goudey
parent 8ab6450abb
commit 670453d1ec
10 changed files with 225 additions and 0 deletions

View File

@ -485,6 +485,7 @@ geometry_node_categories = [
NodeItem("GeometryNodeAttributeRandomize"),
NodeItem("GeometryNodeAttributeMath"),
NodeItem("GeometryNodeAttributeCompare"),
NodeItem("GeometryNodeAttributeConvert"),
NodeItem("GeometryNodeAttributeFill"),
NodeItem("GeometryNodeAttributeMix"),
NodeItem("GeometryNodeAttributeProximity"),

View File

@ -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 */

View File

@ -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
/** \} */

View File

@ -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();

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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 &params,
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);
}