Geometry Nodes: Add "Face" option to Mesh to Curve node

Adds a mode option to the node to choose between the existing
behavior and new behavior that converts each face to a cyclic curve.
Generally this is much faster than the existing mode because it's
easy to parallelize and because curve offsets and face and corner
attributes can be implicitly shared to avoid copies.
Resolves #134671.

Pull Request: https://projects.blender.org/blender/blender/pulls/134773
This commit is contained in:
Hans Goudey
2025-03-04 19:16:36 +01:00
committed by Hans Goudey
parent c821b7d2e7
commit a48d155c87
4 changed files with 212 additions and 27 deletions

View File

@@ -955,6 +955,14 @@ void gather_attributes_group_to_group(const AttributeAccessor src_attributes,
const IndexMask &selection,
MutableAttributeAccessor dst_attributes)
{
if (selection.size() == src_offsets.size()) {
if (src_attributes.domain_size(src_domain) == dst_attributes.domain_size(src_domain)) {
/* When all groups are selected and the domains are the same size, all values are copied,
* because corresponding groups are required to be the same size. */
copy_attributes(src_attributes, src_domain, dst_domain, attribute_filter, dst_attributes);
return;
}
}
src_attributes.foreach_attribute([&](const AttributeIter &iter) {
if (iter.domain != src_domain) {
return;

View File

@@ -21,9 +21,9 @@ namespace blender::geometry {
* intersections of more than three edges will become breaks in curves. Attributes that
* are not built-in on meshes and not curves are transferred to the result curve.
*/
bke::CurvesGeometry mesh_to_curve_convert(const Mesh &mesh,
const IndexMask &selection,
const bke::AttributeFilter &attribute_filter);
bke::CurvesGeometry mesh_edges_to_curves_convert(const Mesh &mesh,
const IndexMask &selection,
const bke::AttributeFilter &attribute_filter);
bke::CurvesGeometry create_curve_from_vert_indices(const bke::AttributeAccessor &mesh_attributes,
Span<int> vert_indices,
@@ -31,4 +31,9 @@ bke::CurvesGeometry create_curve_from_vert_indices(const bke::AttributeAccessor
IndexRange cyclic_curves,
const bke::AttributeFilter &attribute_filter);
/** Convert each mesh face into a cyclic curve. */
bke::CurvesGeometry mesh_faces_to_curves_convert(const Mesh &mesh,
const IndexMask &selection,
const bke::AttributeFilter &attribute_filter);
} // namespace blender::geometry

View File

@@ -2,6 +2,7 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_deform.hh"
#include "BLI_array.hh"
#include "BLI_array_utils.hh"
#include "BLI_set.hh"
@@ -16,6 +17,20 @@
namespace blender::geometry {
/* Don't copy attributes that are built-in on meshes but not on curves. */
static auto filter_builtin_attributes(const bke::AttributeAccessor &mesh_attributes,
const bke::AttributeAccessor &curves_attributes,
Set<StringRef> &storage,
const bke::AttributeFilter &attribute_filter)
{
for (const StringRef id : mesh_attributes.all_ids()) {
if (mesh_attributes.is_builtin(id) && !curves_attributes.is_builtin(id)) {
storage.add(id);
}
}
return bke::attribute_filter_with_skip_ref(attribute_filter, storage);
}
BLI_NOINLINE bke::CurvesGeometry create_curve_from_vert_indices(
const bke::AttributeAccessor &mesh_attributes,
const Span<int> vert_indices,
@@ -34,15 +49,9 @@ BLI_NOINLINE bke::CurvesGeometry create_curve_from_vert_indices(
curves.cyclic_for_write().slice(cyclic_curves).fill(true);
}
/* Don't copy attributes that are built-in on meshes but not on curves. */
Set<std::string> skip;
for (const StringRef id : mesh_attributes.all_ids()) {
if (mesh_attributes.is_builtin(id) && !curves_attributes.is_builtin(id)) {
skip.add(id);
}
}
const auto attribute_filter_with_skip = bke::attribute_filter_with_skip_ref(attribute_filter,
skip);
Set<StringRef> skip_storage;
const auto attribute_filter_with_skip = filter_builtin_attributes(
mesh_attributes, curves_attributes, skip_storage, attribute_filter);
bke::gather_attributes(mesh_attributes,
bke::AttrDomain::Point,
@@ -211,9 +220,9 @@ BLI_NOINLINE static bke::CurvesGeometry edges_to_curves_convert(
attribute_filter);
}
bke::CurvesGeometry mesh_to_curve_convert(const Mesh &mesh,
const IndexMask &selection,
const bke::AttributeFilter &attribute_filter)
bke::CurvesGeometry mesh_edges_to_curves_convert(const Mesh &mesh,
const IndexMask &selection,
const bke::AttributeFilter &attribute_filter)
{
const Span<int2> edges = mesh.edges();
if (selection.size() == edges.size()) {
@@ -224,4 +233,109 @@ bke::CurvesGeometry mesh_to_curve_convert(const Mesh &mesh,
return edges_to_curves_convert(mesh, selected_edges, attribute_filter);
}
static bke::CurvesGeometry create_curves_for_faces(const Mesh &mesh,
const OffsetIndices<int> faces,
const IndexMask &selection)
{
bke::CurvesGeometry curves;
if (selection.size() == faces.size()) {
implicit_sharing::copy_shared_pointer(mesh.face_offset_indices,
mesh.runtime->face_offsets_sharing_info,
&curves.curve_offsets,
&curves.runtime->curve_offsets_sharing_info);
curves.curve_num = faces.size();
curves.resize(mesh.corners_num, faces.size());
}
else {
curves.resize(0, selection.size());
offset_indices::gather_selected_offsets(faces, selection, curves.offsets_for_write());
curves.resize(curves.offsets().last(), curves.curves_num());
}
BKE_defgroup_copy_list(&curves.vertex_group_names, &mesh.vertex_group_names);
curves.cyclic_for_write().fill(true);
curves.fill_curve_types(CURVE_TYPE_POLY);
return curves;
}
static Span<int> create_point_to_vert_map(const Mesh &mesh,
const OffsetIndices<int> faces,
const OffsetIndices<int> points_by_curve,
const IndexMask &selection,
Array<int> &map_data)
{
if (selection.size() == faces.size()) {
return mesh.corner_verts();
}
map_data.reinitialize(points_by_curve.total_size());
array_utils::gather_group_to_group(
faces, points_by_curve, selection, mesh.corner_verts(), map_data.as_mutable_span());
return map_data;
}
bke::CurvesGeometry mesh_faces_to_curves_convert(const Mesh &mesh,
const IndexMask &selection,
const bke::AttributeFilter &attribute_filter)
{
const OffsetIndices faces = mesh.faces();
const bke::AttributeAccessor src_attributes = mesh.attributes();
bke::CurvesGeometry curves = create_curves_for_faces(mesh, faces, selection);
const OffsetIndices points_by_curve = curves.points_by_curve();
bke::MutableAttributeAccessor dst_attributes = curves.attributes_for_write();
Array<int> point_to_vert_data;
const Span<int> point_to_vert_map = create_point_to_vert_map(
mesh, faces, points_by_curve, selection, point_to_vert_data);
Set<StringRef> skip_storage;
const auto attribute_filter_with_skip = filter_builtin_attributes(
src_attributes, dst_attributes, skip_storage, attribute_filter);
bke::gather_attributes(src_attributes,
bke::AttrDomain::Point,
bke::AttrDomain::Point,
attribute_filter_with_skip,
point_to_vert_map,
dst_attributes);
src_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
if (iter.domain != bke::AttrDomain::Edge) {
return;
}
if (iter.data_type == CD_PROP_STRING) {
return;
}
if (attribute_filter_with_skip.allow_skip(iter.name)) {
return;
}
const GVArray src = *iter.get(bke::AttrDomain::Point);
bke::GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
iter.name, bke::AttrDomain::Point, iter.data_type);
if (!dst) {
return;
}
bke::attribute_math::gather(src, point_to_vert_map, dst.span);
dst.finish();
});
bke::gather_attributes(src_attributes,
bke::AttrDomain::Face,
bke::AttrDomain::Curve,
attribute_filter_with_skip,
selection,
dst_attributes);
bke::gather_attributes_group_to_group(src_attributes,
bke::AttrDomain::Corner,
bke::AttrDomain::Point,
attribute_filter_with_skip,
faces,
points_by_curve,
selection,
dst_attributes);
return curves;
}
} // namespace blender::geometry

View File

@@ -4,12 +4,21 @@
#include "DNA_mesh_types.h"
#include "NOD_rna_define.hh"
#include "GEO_mesh_to_curve.hh"
#include "UI_interface_c.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_mesh_to_curve_cc {
enum class Mode : int8_t {
Edges = 0,
Faces = 1,
};
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Mesh").supported_type(GeometryComponent::Type::Mesh);
@@ -17,8 +26,14 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Geometry>("Curve").propagate_all();
}
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, std::nullopt, 0);
}
static void node_geo_exec(GeoNodeExecParams params)
{
const Mode mode = Mode(params.node().custom1);
GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh");
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
@@ -28,25 +43,66 @@ static void node_geo_exec(GeoNodeExecParams params)
return;
}
const bke::MeshFieldContext context{*mesh, AttrDomain::Edge};
fn::FieldEvaluator evaluator{context, mesh->edges_num};
evaluator.add(params.get_input<Field<bool>>("Selection"));
evaluator.evaluate();
const IndexMask selection = evaluator.get_evaluated_as_mask(0);
if (selection.is_empty()) {
geometry_set.remove_geometry_during_modify();
return;
}
switch (mode) {
case Mode::Edges: {
const bke::MeshFieldContext context{*mesh, AttrDomain::Edge};
fn::FieldEvaluator evaluator{context, mesh->edges_num};
evaluator.add(params.get_input<Field<bool>>("Selection"));
evaluator.evaluate();
const IndexMask selection = evaluator.get_evaluated_as_mask(0);
if (selection.is_empty()) {
geometry_set.remove_geometry_during_modify();
return;
}
bke::CurvesGeometry curves = geometry::mesh_to_curve_convert(
*mesh, selection, params.get_attribute_filter("Curve"));
geometry_set.replace_curves(bke::curves_new_nomain(std::move(curves)));
bke::CurvesGeometry curves = geometry::mesh_edges_to_curves_convert(
*mesh, selection, params.get_attribute_filter("Curve"));
geometry_set.replace_curves(bke::curves_new_nomain(std::move(curves)));
break;
}
case Mode::Faces: {
const bke::MeshFieldContext context{*mesh, AttrDomain::Face};
fn::FieldEvaluator evaluator{context, mesh->faces_num};
evaluator.add(params.get_input<Field<bool>>("Selection"));
evaluator.evaluate();
const IndexMask selection = evaluator.get_evaluated_as_mask(0);
if (selection.is_empty()) {
geometry_set.remove_geometry_during_modify();
return;
}
bke::CurvesGeometry curves = geometry::mesh_faces_to_curves_convert(
*mesh, selection, params.get_attribute_filter("Curve"));
geometry_set.replace_curves(bke::curves_new_nomain(std::move(curves)));
break;
}
}
geometry_set.keep_only_during_modify({GeometryComponent::Type::Curve});
});
params.set_output("Curve", std::move(geometry_set));
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem mode_items[] = {
{int(Mode::Edges),
"EDGES",
0,
"Edges",
"Convert mesh edges to curve segments. Attributes are propagated to curve points."},
{int(Mode::Faces),
"FACES",
0,
"Faces",
"Convert each mesh face to a cyclic curve. Face attributes are propagated to curves."},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(
srna, "mode", "Mode", "", mode_items, NOD_inline_enum_accessors(custom1), int(Mode::Edges));
}
static void node_register()
{
static blender::bke::bNodeType ntype;
@@ -57,8 +113,10 @@ static void node_register()
ntype.enum_name_legacy = "MESH_TO_CURVE";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.draw_buttons = node_layout;
ntype.geometry_node_execute = node_geo_exec;
blender::bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)