Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--release/scripts/startup/nodeitems_builtins.py1
-rw-r--r--source/blender/blenkernel/BKE_node.h2
-rw-r--r--source/blender/blenkernel/intern/node.cc1
-rw-r--r--source/blender/makesdna/DNA_node_types.h5
-rw-r--r--source/blender/makesrna/intern/rna_nodetree.c21
-rw-r--r--source/blender/nodes/NOD_geometry.h1
-rw-r--r--source/blender/nodes/NOD_static_types.h1
-rw-r--r--source/blender/nodes/geometry/CMakeLists.txt1
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc1098
9 files changed, 1131 insertions, 0 deletions
diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index 21e20c3b734..cea938bf1a4 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -164,6 +164,7 @@ def geometry_node_items(context):
yield NodeItem("GeometryNodeBoundBox")
yield NodeItem("GeometryNodeConvexHull")
yield NodeItem("GeometryNodeDeleteGeometry")
+ yield NodeItem("GeometryNodeDuplicateElements")
yield NodeItem("GeometryNodeGeometryToInstance")
yield NodeItem("GeometryNodeMergeByDistance")
yield NodeItem("GeometryNodeProximity")
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index 933585b1f8f..024e98daea5 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -1515,6 +1515,8 @@ struct TexResult;
#define GEO_NODE_EXTRUDE_MESH 1152
#define GEO_NODE_MERGE_BY_DISTANCE 1153
+#define GEO_NODE_DUPLICATE_ELEMENTS 1160
+
/** \} */
/* -------------------------------------------------------------------- */
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index ad24b3c0eff..474859128dc 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -4751,6 +4751,7 @@ static void registerGeometryNodes()
register_node_type_geo_curve_to_points();
register_node_type_geo_curve_trim();
register_node_type_geo_delete_geometry();
+ register_node_type_geo_duplicate_elements();
register_node_type_geo_distribute_points_on_faces();
register_node_type_geo_dual_mesh();
register_node_type_geo_edge_split();
diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h
index 963a34aa645..18b79a6fc25 100644
--- a/source/blender/makesdna/DNA_node_types.h
+++ b/source/blender/makesdna/DNA_node_types.h
@@ -1610,6 +1610,11 @@ typedef struct NodeGeometryDeleteGeometry {
int8_t mode;
} NodeGeometryDeleteGeometry;
+typedef struct NodeGeometryDuplicateElements {
+ /* AttributeDomain. */
+ int8_t domain;
+} NodeGeometryDuplicateElements;
+
typedef struct NodeGeometrySeparateGeometry {
/* AttributeDomain. */
int8_t domain;
diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c
index 85bf98914eb..387166e77b4 100644
--- a/source/blender/makesrna/intern/rna_nodetree.c
+++ b/source/blender/makesrna/intern/rna_nodetree.c
@@ -11287,6 +11287,27 @@ static void def_geo_delete_geometry(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}
+static void def_geo_duplicate_elements(StructRNA *srna)
+{
+ PropertyRNA *prop;
+
+ static const EnumPropertyItem domain_items[] = {
+ {ATTR_DOMAIN_POINT, "POINT", 0, "Point", ""},
+ {ATTR_DOMAIN_EDGE, "EDGE", 0, "Edge", ""},
+ {ATTR_DOMAIN_FACE, "FACE", 0, "Face", ""},
+ {ATTR_DOMAIN_CURVE, "SPLINE", 0, "Spline", ""},
+ {ATTR_DOMAIN_INSTANCE, "INSTANCE", 0, "Instance", ""},
+ {0, NULL, 0, NULL, NULL},
+ };
+ RNA_def_struct_sdna_from(srna, "NodeGeometryDuplicateElements", "storage");
+
+ prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, domain_items);
+ RNA_def_property_enum_default(prop, ATTR_DOMAIN_POINT);
+ RNA_def_property_ui_text(prop, "Domain", "Which domain to duplicate");
+ RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
+}
+
static void def_geo_string_to_curves(StructRNA *srna)
{
static const EnumPropertyItem rna_node_geometry_string_to_curves_overflow_items[] = {
diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h
index 86dde999ffc..0a53d9cc019 100644
--- a/source/blender/nodes/NOD_geometry.h
+++ b/source/blender/nodes/NOD_geometry.h
@@ -82,6 +82,7 @@ void register_node_type_geo_curve_to_mesh(void);
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_duplicate_elements(void);
void register_node_type_geo_distribute_points_on_faces(void);
void register_node_type_geo_dual_mesh(void);
void register_node_type_geo_edge_split(void);
diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h
index 1f1d7e4dc3a..a3033651a3a 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -338,6 +338,7 @@ DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_TYPE, def_geo_curve_spline_type, "CU
DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "")
DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, def_geo_curve_to_points, "CURVE_TO_POINTS", CurveToPoints, "Curve to Points", "")
DefNode(GeometryNode, GEO_NODE_DELETE_GEOMETRY, def_geo_delete_geometry, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "")
+DefNode(GeometryNode, GEO_NODE_DUPLICATE_ELEMENTS, def_geo_duplicate_elements, "DUPLICATE_ELEMENTS", DuplicateElements, "Duplicate Elements", "")
DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, def_geo_distribute_points_on_faces, "DISTRIBUTE_POINTS_ON_FACES", DistributePointsOnFaces, "Distribute Points on Faces", "")
DefNode(GeometryNode, GEO_NODE_ACCUMULATE_FIELD, def_geo_accumulate_field, "ACCUMULATE_FIELD", AccumulateField, "Accumulate Field", "")
DefNode(GeometryNode, GEO_NODE_DUAL_MESH, 0, "DUAL_MESH", DualMesh, "Dual Mesh", "")
diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt
index f38562a8926..fbf584e0f4b 100644
--- a/source/blender/nodes/geometry/CMakeLists.txt
+++ b/source/blender/nodes/geometry/CMakeLists.txt
@@ -98,6 +98,7 @@ set(SRC
nodes/node_geo_curve_to_points.cc
nodes/node_geo_curve_trim.cc
nodes/node_geo_delete_geometry.cc
+ nodes/node_geo_duplicate_elements.cc
nodes/node_geo_distribute_points_on_faces.cc
nodes/node_geo_dual_mesh.cc
nodes/node_geo_edge_split.cc
diff --git a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
new file mode 100644
index 00000000000..4c1d26e1012
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
@@ -0,0 +1,1098 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BLI_map.hh"
+#include "BLI_noise.hh"
+#include "BLI_span.hh"
+#include "BLI_task.hh"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_pointcloud_types.h"
+
+#include "BKE_attribute_math.hh"
+#include "BKE_mesh.h"
+#include "BKE_pointcloud.h"
+#include "BKE_spline.hh"
+
+#include "node_geometry_util.hh"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+namespace blender::nodes::node_geo_duplicate_elements_cc {
+
+NODE_STORAGE_FUNCS(NodeGeometryDuplicateElements);
+
+static void node_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Geometry>(N_("Geometry"));
+ b.add_input<decl::Bool>(N_("Selection")).hide_value().default_value(true).supports_field();
+ b.add_input<decl::Int>(N_("Amount"))
+ .min(0)
+ .default_value(1)
+ .supports_field()
+ .description(N_("The number of duplicates to create for each element"));
+
+ b.add_output<decl::Geometry>(N_("Geometry"))
+ .description(
+ N_("The duplicated geometry only. The output does not contain the original geometry"));
+ b.add_output<decl::Int>(N_("Duplicate Index"))
+ .field_source()
+ .description(N_("The indices of the duplicates for each element"));
+}
+
+static void node_init(bNodeTree *UNUSED(tree), bNode *node)
+{
+ NodeGeometryDuplicateElements *data = MEM_cnew<NodeGeometryDuplicateElements>(__func__);
+ data->domain = ATTR_DOMAIN_POINT;
+ node->storage = data;
+}
+
+static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
+{
+ uiItemR(layout, ptr, "domain", 0, "", ICON_NONE);
+}
+
+struct IndexAttributes {
+ StrongAnonymousAttributeID duplicate_index;
+};
+
+/* --------------------------------------------------------------------
+ * Attribute Copy/Creation Functions.
+ */
+
+static void gather_attributes_without_id(const GeometrySet &geometry_set,
+ const GeometryComponentType component_type,
+ const Span<std::string> skip_attributes,
+ const bool include_instances,
+ Map<AttributeIDRef, AttributeKind> &r_gathered_attributes)
+{
+ geometry_set.gather_attributes_for_propagation(
+ {component_type}, component_type, include_instances, r_gathered_attributes);
+ for (const std::string &attribute : skip_attributes) {
+ r_gathered_attributes.remove(attribute);
+ }
+ r_gathered_attributes.remove("id");
+};
+
+static IndexRange range_for_offsets_index(const Span<int> offsets, const int index)
+{
+ return {offsets[index], offsets[index + 1] - offsets[index]};
+}
+
+static Array<int> accumulate_counts_to_offsets(const IndexMask selection,
+ const VArray<int> &counts)
+{
+ Array<int> offsets(selection.size() + 1);
+ int dst_points_size = 0;
+ for (const int i_point : selection.index_range()) {
+ offsets[i_point] = dst_points_size;
+ dst_points_size += std::max(counts[selection[i_point]], 0);
+ }
+ offsets.last() = dst_points_size;
+ return offsets;
+}
+
+/* Utility functions for threaded copying of attribute data where possible. */
+template<typename T>
+static void threaded_slice_fill(Span<int> offsets, Span<T> src, MutableSpan<T> dst)
+{
+ BLI_assert(offsets.last() == dst.size());
+ threading::parallel_for(IndexRange(offsets.size() - 1), 512, [&](IndexRange range) {
+ for (const int i : range) {
+ dst.slice(offsets[i], offsets[i + 1] - offsets[i]).fill(src[i]);
+ }
+ });
+}
+
+template<typename T>
+static void threaded_mapped_copy(const Span<int> mapping, const Span<T> src, MutableSpan<T> dst)
+{
+ threading::parallel_for(mapping.index_range(), 512, [&](IndexRange range) {
+ for (const int i : range) {
+ dst[i] = src[mapping[i]];
+ }
+ });
+}
+
+static void threaded_id_offset_copy(const Span<int> offsets,
+ const Span<int> src,
+ MutableSpan<int> dst)
+{
+ BLI_assert(offsets.last() == dst.size());
+ threading::parallel_for(IndexRange(offsets.size() - 1), 512, [&](IndexRange range) {
+ for (const int i : range) {
+ dst[offsets[i]] = src[i];
+ const int count = offsets[i + 1] - offsets[i];
+ for (const int i_duplicate : IndexRange(1, count - 1)) {
+ dst[offsets[i] + i_duplicate] = noise::hash(src[i], i_duplicate);
+ }
+ }
+ });
+}
+
+/* Create the copy indices for the duplication domain. */
+static void create_duplicate_index_attribute(GeometryComponent &component,
+ const AttributeDomain output_domain,
+ const IndexMask selection,
+ const IndexAttributes &attributes,
+ const Span<int> offsets)
+{
+ OutputAttribute_Typed<int> copy_attribute = component.attribute_try_get_for_output_only<int>(
+ attributes.duplicate_index.get(), output_domain);
+ MutableSpan<int> duplicate_indices = copy_attribute.as_span();
+ for (const int i : IndexRange(selection.size())) {
+ const IndexRange range = range_for_offsets_index(offsets, i);
+ MutableSpan<int> indices = duplicate_indices.slice(range);
+ for (const int i : indices.index_range()) {
+ indices[i] = i;
+ }
+ }
+ copy_attribute.save();
+}
+
+/* Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
+ * and the duplicate number. This function is used for the point domain elements. */
+static void copy_stable_id_point(const Span<int> offsets,
+ const GeometryComponent &src_component,
+ GeometryComponent &dst_component)
+{
+ ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read("id");
+ if (!src_attribute) {
+ return;
+ }
+ OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+ "id", ATTR_DOMAIN_POINT, CD_PROP_INT32);
+ if (!dst_attribute) {
+ return;
+ }
+
+ VArray_Span<int> src{src_attribute.varray.typed<int>()};
+ MutableSpan<int> dst = dst_attribute.as_span<int>();
+ threaded_id_offset_copy(offsets, src, dst);
+ dst_attribute.save();
+}
+
+/* Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
+ * and the duplicate number. This function is used for points when duplicating the edge domain.
+ */
+static void copy_stable_id_edges(const Mesh &mesh,
+ const IndexMask selection,
+ const Span<int> edge_offsets,
+ const GeometryComponent &src_component,
+ GeometryComponent &dst_component)
+{
+ ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read("id");
+ if (!src_attribute) {
+ return;
+ }
+ OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+ "id", ATTR_DOMAIN_POINT, CD_PROP_INT32);
+ if (!dst_attribute) {
+ return;
+ }
+
+ Span<MEdge> edges(mesh.medge, mesh.totedge);
+
+ VArray_Span<int> src{src_attribute.varray.typed<int>()};
+ MutableSpan<int> dst = dst_attribute.as_span<int>();
+ threading::parallel_for(IndexRange(selection.size()), 1024, [&](IndexRange range) {
+ for (const int i_edge : range) {
+ const IndexRange edge_range = range_for_offsets_index(edge_offsets, i_edge);
+ if (edge_range.size() == 0) {
+ continue;
+ }
+ const MEdge &edge = edges[i_edge];
+ const IndexRange vert_range = {edge_range.start() * 2, edge_range.size() * 2};
+
+ dst[vert_range[0]] = src[edge.v1];
+ dst[vert_range[1]] = src[edge.v2];
+ for (const int i_duplicate : IndexRange(1, edge_range.size() - 1)) {
+ dst[vert_range[i_duplicate * 2]] = noise::hash(src[edge.v1], i_duplicate);
+ dst[vert_range[i_duplicate * 2 + 1]] = noise::hash(src[edge.v2], i_duplicate);
+ }
+ }
+ });
+ dst_attribute.save();
+}
+
+/* Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
+ * and the duplicate number. This function is used for points when duplicating the face domain.
+ *
+ * This function could be threaded in the future, but since it is only 1 attribute and the
+ * face->edge->vert mapping would mean creating a 1/1 mapping to allow for it, is it worth it?
+ */
+static void copy_stable_id_faces(const Mesh &mesh,
+ const IndexMask selection,
+ const Span<int> poly_offsets,
+ const Span<int> vert_mapping,
+ const GeometryComponent &src_component,
+ GeometryComponent &dst_component)
+{
+ ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read("id");
+ if (!src_attribute) {
+ return;
+ }
+ OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+ "id", ATTR_DOMAIN_POINT, CD_PROP_INT32);
+ if (!dst_attribute) {
+ return;
+ }
+
+ VArray_Span<int> src{src_attribute.varray.typed<int>()};
+ MutableSpan<int> dst = dst_attribute.as_span<int>();
+
+ Span<MPoly> polys(mesh.mpoly, mesh.totpoly);
+ int loop_index = 0;
+ for (const int i_poly : selection.index_range()) {
+ const IndexRange range = range_for_offsets_index(poly_offsets, i_poly);
+ if (range.size() == 0) {
+ continue;
+ }
+ const MPoly &source = polys[i_poly];
+ for ([[maybe_unused]] const int i_duplicate : IndexRange(range.size())) {
+ for ([[maybe_unused]] const int i_loops : IndexRange(source.totloop)) {
+ if (i_duplicate == 0) {
+ dst[loop_index] = src[vert_mapping[loop_index]];
+ }
+ else {
+ dst[loop_index] = noise::hash(src[vert_mapping[loop_index]], i_duplicate);
+ }
+ loop_index++;
+ }
+ }
+ }
+
+ dst_attribute.save();
+}
+
+/* Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
+ * and the duplicate number. In the spline case, copy the entire spline's points to the
+ * destination,
+ * then loop over the remaining ones point by point, hashing their ids to the new ids. */
+static void copy_stable_id_splines(const CurveEval &curve,
+ const IndexMask selection,
+ const Span<int> curve_offsets,
+ const GeometryComponent &src_component,
+ GeometryComponent &dst_component)
+{
+ ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read("id");
+ if (!src_attribute) {
+ return;
+ }
+ OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+ "id", ATTR_DOMAIN_POINT, CD_PROP_INT32);
+ if (!dst_attribute) {
+ return;
+ }
+
+ Array<int> control_point_offsets = curve.control_point_offsets();
+ VArray_Span<int> src{src_attribute.varray.typed<int>()};
+ MutableSpan<int> dst = dst_attribute.as_span<int>();
+
+ Array<int> curve_point_offsets(selection.size() + 1);
+ int dst_point_size = 0;
+ for (const int i_curve : selection.index_range()) {
+ const int spline_size = curve.splines()[i_curve]->size();
+ const IndexRange curve_range = range_for_offsets_index(curve_offsets, i_curve);
+
+ curve_point_offsets[i_curve] = dst_point_size;
+ dst_point_size += curve_range.size() * spline_size;
+ }
+ curve_point_offsets.last() = dst_point_size;
+
+ threading::parallel_for(IndexRange(curve_point_offsets.size() - 1), 512, [&](IndexRange range) {
+ for (const int i_curve : range) {
+ const int spline_size = curve.splines()[i_curve]->size();
+ const IndexRange curve_range = range_for_offsets_index(curve_offsets, i_curve);
+
+ dst.slice(curve_point_offsets[i_curve], spline_size)
+ .copy_from(src.slice(control_point_offsets[i_curve], spline_size));
+ for (const int i_duplicate : IndexRange(1, curve_range.size() - 1)) {
+ for (const int i_point : IndexRange(spline_size)) {
+ dst[curve_point_offsets[i_curve] + i_duplicate * spline_size + i_point] = noise::hash(
+ src[control_point_offsets[i_curve] + i_point], i_duplicate);
+ }
+ }
+ }
+ });
+ dst_attribute.save();
+}
+
+/* The attributes for the point (also instance) duplicated elements are stored sequentially
+ * (1,1,1,2,2,2,3,3,3,etc) They can be copied by using a simple offset array. For each domain, if
+ * elements are ordered differently a custom function is called to copy the attributes.
+ */
+
+static void copy_point_attributes_without_id(GeometrySet &geometry_set,
+ const GeometryComponentType component_type,
+ const bool include_instances,
+ const Span<int> offsets,
+ const GeometryComponent &src_component,
+ GeometryComponent &dst_component)
+{
+ Map<AttributeIDRef, AttributeKind> gathered_attributes;
+ gather_attributes_without_id(
+ geometry_set, component_type, {}, include_instances, gathered_attributes);
+
+ for (const Map<AttributeIDRef, AttributeKind>::Item entry : gathered_attributes.items()) {
+ const AttributeIDRef attribute_id = entry.key;
+ ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id);
+ if (!src_attribute || src_attribute.domain != ATTR_DOMAIN_POINT) {
+ continue;
+ }
+ AttributeDomain out_domain = src_attribute.domain;
+ const CustomDataType data_type = bke::cpp_type_to_custom_data_type(
+ src_attribute.varray.type());
+ OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+ attribute_id, out_domain, data_type);
+ if (!dst_attribute) {
+ continue;
+ }
+ attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ using T = decltype(dummy);
+ VArray_Span<T> src = src_attribute.varray.typed<T>();
+ MutableSpan<T> dst = dst_attribute.as_span<T>();
+ threaded_slice_fill<T>(offsets, src, dst);
+ });
+ dst_attribute.save();
+ }
+}
+
+/* Copies the attributes for spline duplciates. If copying the spline domain, the attributes are
+ * copied with an offset fill, otherwise a mapping is used. */
+static void copy_spline_attributes_without_id(const GeometrySet &geometry_set,
+ const Span<int> point_mapping,
+ const Span<int> offsets,
+ const Span<std::string> attributes_to_ignore,
+ const GeometryComponent &src_component,
+ GeometryComponent &dst_component)
+{
+ Map<AttributeIDRef, AttributeKind> gathered_attributes;
+ gather_attributes_without_id(
+ geometry_set, GEO_COMPONENT_TYPE_CURVE, attributes_to_ignore, false, gathered_attributes);
+
+ for (const Map<AttributeIDRef, AttributeKind>::Item entry : gathered_attributes.items()) {
+
+ const AttributeIDRef attribute_id = entry.key;
+ ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id);
+ if (!src_attribute) {
+ continue;
+ }
+
+ AttributeDomain out_domain = src_attribute.domain;
+ const CustomDataType data_type = bke::cpp_type_to_custom_data_type(
+ src_attribute.varray.type());
+ OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+ attribute_id, out_domain, data_type);
+ if (!dst_attribute) {
+ continue;
+ }
+
+ attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ using T = decltype(dummy);
+ VArray_Span<T> src{src_attribute.varray.typed<T>()};
+ MutableSpan<T> dst = dst_attribute.as_span<T>();
+
+ switch (out_domain) {
+ case ATTR_DOMAIN_CURVE:
+ threaded_slice_fill<T>(offsets, src, dst);
+ break;
+ case ATTR_DOMAIN_POINT:
+ threaded_mapped_copy<T>(point_mapping, src, dst);
+ break;
+ default:
+ break;
+ }
+ });
+ dst_attribute.save();
+ }
+}
+
+/* Copies the attributes for edge duplciates. If copying the edge domain, the attributes are
+ * copied with an offset fill, for point domain a mapping is used. */
+static void copy_edge_attributes_without_id(GeometrySet &geometry_set,
+ const Span<int> point_mapping,
+ const Span<int> offsets,
+ const GeometryComponent &src_component,
+ GeometryComponent &dst_component)
+{
+ Map<AttributeIDRef, AttributeKind> gathered_attributes;
+ gather_attributes_without_id(
+ geometry_set, GEO_COMPONENT_TYPE_MESH, {}, false, gathered_attributes);
+
+ for (const Map<AttributeIDRef, AttributeKind>::Item entry : gathered_attributes.items()) {
+ const AttributeIDRef attribute_id = entry.key;
+ ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id);
+ if (!src_attribute) {
+ continue;
+ }
+
+ const AttributeDomain out_domain = src_attribute.domain;
+ const CustomDataType data_type = bke::cpp_type_to_custom_data_type(
+ src_attribute.varray.type());
+ OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+ attribute_id, out_domain, data_type);
+ if (!dst_attribute) {
+ continue;
+ }
+ attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ using T = decltype(dummy);
+ VArray_Span<T> src{src_attribute.varray.typed<T>()};
+ MutableSpan<T> dst = dst_attribute.as_span<T>();
+
+ switch (out_domain) {
+ case ATTR_DOMAIN_EDGE:
+ threaded_slice_fill<T>(offsets, src, dst);
+ break;
+ case ATTR_DOMAIN_POINT:
+ threaded_mapped_copy<T>(point_mapping, src, dst);
+ break;
+ default:
+ break;
+ }
+ });
+ dst_attribute.save();
+ }
+}
+
+/* Copies the attributes for face duplciates. If copying the face domain, the attributes are
+ * copied with an offset fill, otherwise a mapping is used. */
+static void copy_face_attributes_without_id(GeometrySet &geometry_set,
+ const Span<int> edge_mapping,
+ const Span<int> vert_mapping,
+ const Span<int> loop_mapping,
+ const Span<int> offsets,
+ const GeometryComponent &src_component,
+ GeometryComponent &dst_component)
+{
+ Map<AttributeIDRef, AttributeKind> gathered_attributes;
+ gather_attributes_without_id(
+ geometry_set, GEO_COMPONENT_TYPE_MESH, {}, false, gathered_attributes);
+
+ for (const Map<AttributeIDRef, AttributeKind>::Item entry : gathered_attributes.items()) {
+ const AttributeIDRef attribute_id = entry.key;
+ ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id);
+ if (!src_attribute) {
+ continue;
+ }
+
+ AttributeDomain out_domain = src_attribute.domain;
+ const CustomDataType data_type = bke::cpp_type_to_custom_data_type(
+ src_attribute.varray.type());
+ OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+ attribute_id, out_domain, data_type);
+ if (!dst_attribute) {
+ continue;
+ }
+
+ attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ using T = decltype(dummy);
+ VArray_Span<T> src{src_attribute.varray.typed<T>()};
+ MutableSpan<T> dst = dst_attribute.as_span<T>();
+
+ switch (out_domain) {
+ case ATTR_DOMAIN_FACE:
+ threaded_slice_fill<T>(offsets, src, dst);
+ break;
+ case ATTR_DOMAIN_EDGE:
+ threaded_mapped_copy<T>(edge_mapping, src, dst);
+ break;
+ case ATTR_DOMAIN_POINT:
+ threaded_mapped_copy<T>(vert_mapping, src, dst);
+ break;
+ case ATTR_DOMAIN_CORNER:
+ threaded_mapped_copy<T>(loop_mapping, src, dst);
+ break;
+ default:
+ break;
+ }
+ });
+ dst_attribute.save();
+ }
+}
+
+/* --------------------------------------------------------------------
+ * Duplication Functions.
+ */
+
+static void duplicate_splines(GeometrySet &geometry_set,
+ const Field<int> &count_field,
+ const Field<bool> &selection_field,
+ IndexAttributes &attributes)
+{
+ if (!geometry_set.has_curve()) {
+ geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
+ return;
+ }
+ geometry_set.keep_only({GEO_COMPONENT_TYPE_CURVE, GEO_COMPONENT_TYPE_INSTANCES});
+
+ const GeometryComponent &src_component = *geometry_set.get_component_for_read(
+ GEO_COMPONENT_TYPE_CURVE);
+ const CurveEval &curve = *geometry_set.get_curve_for_read();
+ const int domain_size = src_component.attribute_domain_size(ATTR_DOMAIN_CURVE);
+ GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_CURVE};
+ FieldEvaluator evaluator{field_context, domain_size};
+ evaluator.add(count_field);
+ evaluator.set_selection(selection_field);
+ evaluator.evaluate();
+ const VArray<int> counts = evaluator.get_evaluated<int>(0);
+ const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+
+ Array<int> curve_offsets(selection.size() + 1);
+
+ int dst_splines_size = 0;
+ int dst_points_size = 0;
+ for (const int i_spline : selection.index_range()) {
+ int count = std::max(counts[selection[i_spline]], 0);
+ curve_offsets[i_spline] = dst_splines_size;
+ dst_splines_size += count;
+ dst_points_size += count * curve.splines()[selection[i_spline]]->size();
+ }
+ curve_offsets.last() = dst_splines_size;
+
+ Array<int> control_point_offsets = curve.control_point_offsets();
+ Array<int> point_mapping(dst_points_size);
+
+ std::unique_ptr<CurveEval> new_curve = std::make_unique<CurveEval>();
+ int point_index = 0;
+ for (const int i_spline : selection.index_range()) {
+ const IndexRange spline_range = range_for_offsets_index(curve_offsets, i_spline);
+ for ([[maybe_unused]] const int i_duplicate : IndexRange(spline_range.size())) {
+ SplinePtr spline = curve.splines()[selection[i_spline]]->copy();
+ for (const int i_point : IndexRange(curve.splines()[selection[i_spline]]->size())) {
+ point_mapping[point_index++] = control_point_offsets[selection[i_spline]] + i_point;
+ }
+ new_curve->add_spline(std::move(spline));
+ }
+ }
+ new_curve->attributes.reallocate(new_curve->splines().size());
+
+ CurveComponent dst_component;
+ dst_component.replace(new_curve.release(), GeometryOwnershipType::Editable);
+
+ Vector<std::string> skip(
+ {"position", "radius", "resolution", "cyclic", "tilt", "handle_left", "handle_right"});
+
+ copy_spline_attributes_without_id(
+ geometry_set, point_mapping, curve_offsets, skip, src_component, dst_component);
+
+ copy_stable_id_splines(curve, selection, curve_offsets, src_component, dst_component);
+
+ if (attributes.duplicate_index) {
+ create_duplicate_index_attribute(
+ dst_component, ATTR_DOMAIN_CURVE, selection, attributes, curve_offsets);
+ }
+
+ geometry_set.replace_curve(dst_component.get_for_write());
+}
+
+static void duplicate_faces(GeometrySet &geometry_set,
+ const Field<int> &count_field,
+ const Field<bool> &selection_field,
+ IndexAttributes &attributes)
+{
+ if (!geometry_set.has_mesh()) {
+ geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
+ return;
+ }
+ geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES});
+
+ GeometryComponent &component = geometry_set.get_component_for_write(GEO_COMPONENT_TYPE_MESH);
+ const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_FACE);
+
+ GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_FACE};
+ FieldEvaluator evaluator(field_context, domain_size);
+
+ evaluator.add(count_field);
+ evaluator.set_selection(selection_field);
+ evaluator.evaluate();
+ const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+ const VArray<int> counts = evaluator.get_evaluated<int>(0);
+
+ MeshComponent &mesh_component = static_cast<MeshComponent &>(component);
+ const Mesh &mesh = *mesh_component.get_for_read();
+ 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);
+
+ int total_polys = 0;
+ int total_loops = 0;
+ Array<int> offsets(selection.size() + 1);
+ for (const int i_selection : selection.index_range()) {
+ const int count = std::max(counts[selection[i_selection]], 0);
+ offsets[i_selection] = total_polys;
+ total_polys += count;
+ total_loops += count * polys[selection[i_selection]].totloop;
+ }
+ offsets[selection.size()] = total_polys;
+
+ Array<int> vert_mapping(total_loops);
+ Array<int> edge_mapping(total_loops);
+ Array<int> loop_mapping(total_loops);
+
+ Mesh *new_mesh = BKE_mesh_new_nomain(total_loops, total_loops, 0, total_loops, total_polys);
+
+ MutableSpan<MVert> new_verts(new_mesh->mvert, new_mesh->totvert);
+ MutableSpan<MEdge> new_edges(new_mesh->medge, new_mesh->totedge);
+ MutableSpan<MLoop> new_loops(new_mesh->mloop, new_mesh->totloop);
+ MutableSpan<MPoly> new_poly(new_mesh->mpoly, new_mesh->totpoly);
+
+ int poly_index = 0;
+ int loop_index = 0;
+ for (const int i_selection : selection.index_range()) {
+ const IndexRange poly_range = range_for_offsets_index(offsets, i_selection);
+
+ const MPoly &source = polys[selection[i_selection]];
+ for ([[maybe_unused]] const int i_duplicate : IndexRange(poly_range.size())) {
+ new_poly[poly_index] = source;
+ new_poly[poly_index].loopstart = loop_index;
+ for (const int i_loops : IndexRange(source.totloop)) {
+ const MLoop &current_loop = loops[source.loopstart + i_loops];
+ loop_mapping[loop_index] = source.loopstart + i_loops;
+ new_verts[loop_index] = verts[current_loop.v];
+ vert_mapping[loop_index] = current_loop.v;
+ new_edges[loop_index] = edges[current_loop.e];
+ edge_mapping[loop_index] = current_loop.e;
+ new_edges[loop_index].v1 = loop_index;
+ if (i_loops + 1 != source.totloop) {
+ new_edges[loop_index].v2 = loop_index + 1;
+ }
+ else {
+ new_edges[loop_index].v2 = new_poly[poly_index].loopstart;
+ }
+ new_loops[loop_index].v = loop_index;
+ new_loops[loop_index].e = loop_index;
+ loop_index++;
+ }
+ poly_index++;
+ }
+ }
+ MeshComponent dst_component;
+ dst_component.replace(new_mesh, GeometryOwnershipType::Editable);
+
+ copy_face_attributes_without_id(geometry_set,
+ edge_mapping,
+ vert_mapping,
+ loop_mapping,
+ offsets,
+ mesh_component,
+ dst_component);
+
+ copy_stable_id_faces(mesh, selection, offsets, vert_mapping, mesh_component, dst_component);
+ mesh_component.replace(dst_component.get_for_write());
+
+ if (attributes.duplicate_index) {
+ create_duplicate_index_attribute(
+ dst_component, ATTR_DOMAIN_FACE, selection, attributes, offsets);
+ }
+}
+
+static void duplicate_edges(GeometrySet &geometry_set,
+ const Field<int> &count_field,
+ const Field<bool> &selection_field,
+ IndexAttributes &attributes)
+{
+ if (!geometry_set.has_mesh()) {
+ geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
+ return;
+ };
+ const GeometryComponent &src_component = *geometry_set.get_component_for_read(
+ GEO_COMPONENT_TYPE_MESH);
+ const int domain_size = src_component.attribute_domain_size(ATTR_DOMAIN_EDGE);
+
+ GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_EDGE};
+ FieldEvaluator evaluator{field_context, domain_size};
+ evaluator.add(count_field);
+ evaluator.set_selection(selection_field);
+ evaluator.evaluate();
+ const VArray<int> counts = evaluator.get_evaluated<int>(0);
+ const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+
+ Array<int> edge_offsets = accumulate_counts_to_offsets(selection, counts);
+
+ const Mesh *mesh = geometry_set.get_mesh_for_read();
+ Span<MVert> verts(mesh->mvert, mesh->totvert);
+ Span<MEdge> edges(mesh->medge, mesh->totedge);
+
+ Mesh *new_mesh = BKE_mesh_new_nomain(edge_offsets.last() * 2, edge_offsets.last(), 0, 0, 0);
+ MutableSpan<MVert> new_verts(new_mesh->mvert, new_mesh->totvert);
+ MutableSpan<MEdge> new_edges(new_mesh->medge, new_mesh->totedge);
+
+ Array<int> vert_orig_indices(edge_offsets.last() * 2);
+ threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
+ for (const int i_edge : range) {
+ const MEdge &edge = edges[i_edge];
+ const IndexRange edge_range = range_for_offsets_index(edge_offsets, i_edge);
+ const IndexRange vert_range(edge_range.start() * 2, edge_range.size() * 2);
+
+ for (const int i_duplicate : IndexRange(edge_range.size())) {
+ vert_orig_indices[vert_range[i_duplicate * 2]] = edge.v1;
+ vert_orig_indices[vert_range[i_duplicate * 2 + 1]] = edge.v2;
+ }
+ }
+ });
+
+ threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
+ for (const int i_edge : range) {
+ const IndexRange edge_range = range_for_offsets_index(edge_offsets, i_edge);
+ const IndexRange vert_range(edge_range.start() * 2, edge_range.size() * 2);
+ for (const int i_duplicate : IndexRange(edge_range.size())) {
+ MEdge &new_edge = new_edges[edge_range[i_duplicate]];
+ new_edge.v1 = vert_range[i_duplicate * 2];
+ new_edge.v2 = vert_range[i_duplicate * 2] + 1;
+ }
+ }
+ });
+
+ MeshComponent dst_component;
+ dst_component.replace(new_mesh, GeometryOwnershipType::Editable);
+
+ copy_edge_attributes_without_id(
+ geometry_set, vert_orig_indices, edge_offsets, src_component, dst_component);
+
+ copy_stable_id_edges(*mesh, selection, edge_offsets, src_component, dst_component);
+
+ if (attributes.duplicate_index) {
+ create_duplicate_index_attribute(
+ dst_component, ATTR_DOMAIN_EDGE, selection, attributes, edge_offsets);
+ }
+
+ MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
+ mesh_component.replace(dst_component.get_for_write());
+}
+
+static void duplicate_points_curve(const GeometryComponentType component_type,
+ const Field<int> &count_field,
+ const Field<bool> &selection_field,
+ GeometrySet &geometry_set,
+ IndexAttributes &attributes)
+{
+ const GeometryComponent &src_component = *geometry_set.get_component_for_read(component_type);
+ const int domain_size = src_component.attribute_domain_size(ATTR_DOMAIN_POINT);
+
+ GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_POINT};
+ FieldEvaluator evaluator{field_context, domain_size};
+ evaluator.add(count_field);
+ evaluator.set_selection(selection_field);
+ evaluator.evaluate();
+ const VArray<int> counts = evaluator.get_evaluated<int>(0);
+ const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+
+ Array<int> offsets = accumulate_counts_to_offsets(selection, counts);
+
+ CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>();
+ const CurveEval &curve = *geometry_set.get_curve_for_read();
+ Array<int> control_point_offsets = curve.control_point_offsets();
+ std::unique_ptr<CurveEval> new_curve = std::make_unique<CurveEval>();
+
+ Array<int> parent(domain_size);
+ int spline = 0;
+ for (const int i_spline : IndexRange(domain_size)) {
+ if (i_spline == control_point_offsets[spline + 1]) {
+ spline++;
+ }
+ parent[i_spline] = spline;
+ }
+
+ for (const int i_point : selection) {
+ const IndexRange point_range = range_for_offsets_index(offsets, i_point);
+ for ([[maybe_unused]] const int i_duplicate : IndexRange(point_range.size())) {
+ const SplinePtr &parent_spline = curve.splines()[parent[i_point]];
+ switch (parent_spline->type()) {
+ case CurveType::CURVE_TYPE_BEZIER: {
+ std::unique_ptr<BezierSpline> spline = std::make_unique<BezierSpline>();
+ spline->resize(1);
+ spline->set_resolution(2);
+ new_curve->add_spline(std::move(spline));
+ break;
+ }
+ case CurveType::CURVE_TYPE_NURBS: {
+ std::unique_ptr<NURBSpline> spline = std::make_unique<NURBSpline>();
+ spline->resize(1);
+ spline->set_resolution(2);
+ new_curve->add_spline(std::move(spline));
+ break;
+ }
+ case CurveType::CURVE_TYPE_POLY: {
+ std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>();
+ spline->resize(1);
+ new_curve->add_spline(std::move(spline));
+ break;
+ }
+ case CurveType::CURVE_TYPE_CATMULL_ROM: {
+ /* Catmull Rom curves are not supported yet. */
+ break;
+ }
+ }
+ }
+ }
+ new_curve->attributes.reallocate(new_curve->splines().size());
+ CurveComponent dst_component;
+ dst_component.replace(new_curve.release(), GeometryOwnershipType::Editable);
+
+ copy_point_attributes_without_id(
+ geometry_set, GEO_COMPONENT_TYPE_CURVE, false, offsets, src_component, dst_component);
+
+ copy_stable_id_point(offsets, src_component, dst_component);
+
+ if (attributes.duplicate_index) {
+ create_duplicate_index_attribute(
+ dst_component, ATTR_DOMAIN_POINT, selection, attributes, offsets.as_span());
+ }
+
+ curve_component.replace(dst_component.get_for_write());
+}
+
+static void duplicate_points_mesh(const GeometryComponentType component_type,
+ const Field<int> &count_field,
+ const Field<bool> &selection_field,
+ GeometrySet &geometry_set,
+ IndexAttributes &attributes)
+{
+ const GeometryComponent &src_component = *geometry_set.get_component_for_read(component_type);
+ const int domain_size = src_component.attribute_domain_size(ATTR_DOMAIN_POINT);
+
+ GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_POINT};
+ FieldEvaluator evaluator{field_context, domain_size};
+ evaluator.add(count_field);
+ evaluator.set_selection(selection_field);
+ evaluator.evaluate();
+ const VArray<int> counts = evaluator.get_evaluated<int>(0);
+ const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+
+ Array<int> offsets = accumulate_counts_to_offsets(selection, counts);
+
+ const Mesh *mesh = geometry_set.get_mesh_for_read();
+ Span<MVert> src_verts(mesh->mvert, mesh->totvert);
+
+ Mesh *new_mesh = BKE_mesh_new_nomain(offsets.last(), 0, 0, 0, 0);
+ MutableSpan<MVert> dst_verts(new_mesh->mvert, new_mesh->totvert);
+
+ threaded_slice_fill<MVert>(offsets.as_span(), src_verts, dst_verts);
+
+ MeshComponent dst_component;
+ dst_component.replace(new_mesh, GeometryOwnershipType::Editable);
+ copy_point_attributes_without_id(
+ geometry_set, GEO_COMPONENT_TYPE_MESH, false, offsets, src_component, dst_component);
+
+ copy_stable_id_point(offsets, src_component, dst_component);
+
+ if (attributes.duplicate_index) {
+ create_duplicate_index_attribute(
+ dst_component, ATTR_DOMAIN_POINT, selection, attributes, offsets.as_span());
+ }
+
+ MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
+ mesh_component.replace(dst_component.get_for_write());
+}
+
+static void duplicate_points_pointcloud(const GeometryComponentType component_type,
+ const Field<int> &count_field,
+ const Field<bool> &selection_field,
+ GeometrySet &geometry_set,
+ IndexAttributes &attributes)
+{
+ const GeometryComponent &src_component = *geometry_set.get_component_for_read(component_type);
+ const int domain_size = src_component.attribute_domain_size(ATTR_DOMAIN_POINT);
+
+ GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_POINT};
+ FieldEvaluator evaluator{field_context, domain_size};
+ evaluator.add(count_field);
+ evaluator.set_selection(selection_field);
+ evaluator.evaluate();
+ const VArray<int> counts = evaluator.get_evaluated<int>(0);
+ const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+
+ Array<int> offsets = accumulate_counts_to_offsets(selection, counts);
+
+ PointCloud *pointcloud = BKE_pointcloud_new_nomain(offsets.last());
+ PointCloudComponent dst_component;
+ dst_component.replace(pointcloud, GeometryOwnershipType::Editable);
+
+ copy_point_attributes_without_id(
+ geometry_set, GEO_COMPONENT_TYPE_POINT_CLOUD, false, offsets, src_component, dst_component);
+
+ copy_stable_id_point(offsets, src_component, dst_component);
+
+ if (attributes.duplicate_index) {
+ create_duplicate_index_attribute(
+ dst_component, ATTR_DOMAIN_POINT, selection, attributes, offsets);
+ }
+ geometry_set.replace_pointcloud(pointcloud);
+}
+
+static void duplicate_points(GeometrySet &geometry_set,
+ const Field<int> &count_field,
+ const Field<bool> &selection_field,
+ IndexAttributes &attributes)
+{
+ if (!geometry_set.has_mesh() && !geometry_set.has_curve() && !geometry_set.has_pointcloud()) {
+ geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
+ return;
+ }
+
+ Vector<GeometryComponentType> component_types = geometry_set.gather_component_types(true, true);
+ Vector<GeometryComponentType> types_to_keep;
+ for (const GeometryComponentType component_type : component_types) {
+ switch (component_type) {
+ case GEO_COMPONENT_TYPE_POINT_CLOUD:
+ types_to_keep.append(component_type);
+ duplicate_points_pointcloud(
+ component_type, count_field, selection_field, geometry_set, attributes);
+ break;
+ case GEO_COMPONENT_TYPE_MESH:
+ types_to_keep.append(component_type);
+ duplicate_points_mesh(
+ component_type, count_field, selection_field, geometry_set, attributes);
+ break;
+ case GEO_COMPONENT_TYPE_CURVE:
+ types_to_keep.append(component_type);
+ duplicate_points_curve(
+ component_type, count_field, selection_field, geometry_set, attributes);
+ break;
+ default:
+ break;
+ }
+ }
+ types_to_keep.append(GEO_COMPONENT_TYPE_INSTANCES);
+ geometry_set.keep_only(types_to_keep);
+}
+
+static void duplicate_instances(GeometrySet &geometry_set,
+ const Field<int> &count_field,
+ const Field<bool> &selection_field,
+ IndexAttributes &attributes)
+{
+ if (!geometry_set.has_instances()) {
+ geometry_set.clear();
+ return;
+ }
+
+ const InstancesComponent &src_instances =
+ *geometry_set.get_component_for_read<InstancesComponent>();
+
+ const int domain_size = src_instances.attribute_domain_size(ATTR_DOMAIN_INSTANCE);
+ GeometryComponentFieldContext field_context{src_instances, ATTR_DOMAIN_INSTANCE};
+ FieldEvaluator evaluator{field_context, domain_size};
+ evaluator.add(count_field);
+ evaluator.set_selection(selection_field);
+ evaluator.evaluate();
+ IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+ const VArray<int> counts = evaluator.get_evaluated<int>(0);
+
+ Array<int> offsets = accumulate_counts_to_offsets(selection, counts);
+
+ if (offsets.last() == 0) {
+ geometry_set.clear();
+ return;
+ }
+
+ GeometrySet instances_geometry;
+ InstancesComponent &dst_instances =
+ instances_geometry.get_component_for_write<InstancesComponent>();
+ dst_instances.resize(offsets.last());
+ for (const int i_selection : selection.index_range()) {
+ const int count = offsets[i_selection + 1] - offsets[i_selection];
+ if (count == 0) {
+ continue;
+ }
+ const int old_handle = src_instances.instance_reference_handles()[i_selection];
+ const InstanceReference reference = src_instances.references()[old_handle];
+ const int new_handle = dst_instances.add_reference(reference);
+ const float4x4 transform = src_instances.instance_transforms()[i_selection];
+ dst_instances.instance_transforms().slice(offsets[i_selection], count).fill(transform);
+ dst_instances.instance_reference_handles().slice(offsets[i_selection], count).fill(new_handle);
+ }
+
+ copy_point_attributes_without_id(
+ geometry_set, GEO_COMPONENT_TYPE_INSTANCES, true, offsets, src_instances, dst_instances);
+
+ if (attributes.duplicate_index) {
+ create_duplicate_index_attribute(
+ dst_instances, ATTR_DOMAIN_INSTANCE, selection, attributes, offsets);
+ }
+
+ geometry_set.remove(GEO_COMPONENT_TYPE_INSTANCES);
+ geometry_set.add(dst_instances);
+}
+
+static void node_geo_exec(GeoNodeExecParams params)
+{
+ GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
+
+ const NodeGeometryDuplicateElements &storage = node_storage(params.node());
+ const AttributeDomain duplicate_domain = AttributeDomain(storage.domain);
+
+ Field<int> count_field = params.extract_input<Field<int>>("Amount");
+ Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
+ IndexAttributes attributes;
+ if (params.output_is_required("Duplicate Index")) {
+ attributes.duplicate_index = StrongAnonymousAttributeID("duplicate_index");
+ }
+
+ if (duplicate_domain == ATTR_DOMAIN_INSTANCE) {
+ geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
+ duplicate_instances(geometry_set, count_field, selection_field, attributes);
+ }
+ else {
+ if (geometry_set.is_empty()) {
+ params.set_default_remaining_outputs();
+ return;
+ }
+ geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
+ switch (duplicate_domain) {
+ case ATTR_DOMAIN_CURVE:
+ duplicate_splines(geometry_set, count_field, selection_field, attributes);
+ break;
+ case ATTR_DOMAIN_FACE:
+ duplicate_faces(geometry_set, count_field, selection_field, attributes);
+ break;
+ case ATTR_DOMAIN_EDGE:
+ duplicate_edges(geometry_set, count_field, selection_field, attributes);
+ break;
+ case ATTR_DOMAIN_POINT:
+ duplicate_points(geometry_set, count_field, selection_field, attributes);
+ break;
+ default:
+ BLI_assert_unreachable();
+ break;
+ }
+ });
+ }
+
+ if (geometry_set.is_empty()) {
+ params.set_default_remaining_outputs();
+ return;
+ }
+
+ if (attributes.duplicate_index) {
+ params.set_output(
+ "Duplicate Index",
+ AnonymousAttributeFieldInput::Create<int>(std::move(attributes.duplicate_index),
+ params.attribute_producer_name()));
+ }
+ params.set_output("Geometry", geometry_set);
+}
+
+} // namespace blender::nodes::node_geo_duplicate_elements_cc
+
+void register_node_type_geo_duplicate_elements()
+{
+ namespace file_ns = blender::nodes::node_geo_duplicate_elements_cc;
+ static bNodeType ntype;
+ geo_node_type_base(
+ &ntype, GEO_NODE_DUPLICATE_ELEMENTS, "Duplicate Elements", NODE_CLASS_GEOMETRY);
+
+ node_type_storage(&ntype,
+ "NodeGeometryDuplicateElements",
+ node_free_standard_storage,
+ node_copy_standard_storage);
+
+ node_type_init(&ntype, file_ns::node_init);
+ ntype.draw_buttons = file_ns::node_layout;
+ ntype.geometry_node_execute = file_ns::node_geo_exec;
+ ntype.declare = file_ns::node_declare;
+ nodeRegisterType(&ntype);
+}