diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index 1a129661cfc..10cb756da60 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -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; diff --git a/source/blender/geometry/GEO_mesh_to_curve.hh b/source/blender/geometry/GEO_mesh_to_curve.hh index d32b5c97307..feeaaabcda0 100644 --- a/source/blender/geometry/GEO_mesh_to_curve.hh +++ b/source/blender/geometry/GEO_mesh_to_curve.hh @@ -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 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 diff --git a/source/blender/geometry/intern/mesh_to_curve_convert.cc b/source/blender/geometry/intern/mesh_to_curve_convert.cc index 6b26b94ccaf..c651f7aabc4 100644 --- a/source/blender/geometry/intern/mesh_to_curve_convert.cc +++ b/source/blender/geometry/intern/mesh_to_curve_convert.cc @@ -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 &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 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 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 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 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 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 create_point_to_vert_map(const Mesh &mesh, + const OffsetIndices faces, + const OffsetIndices points_by_curve, + const IndexMask &selection, + Array &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 point_to_vert_data; + const Span point_to_vert_map = create_point_to_vert_map( + mesh, faces, points_by_curve, selection, point_to_vert_data); + + Set 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 diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc index 82be221a3b8..6b178d7f711 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc @@ -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("Mesh").supported_type(GeometryComponent::Type::Mesh); @@ -17,8 +26,14 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output("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("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>("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>("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>("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)