Blender V5.0
node_geo_split_to_instances.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
6
8#include "GEO_randomize.hh"
9
10#include "BKE_curves.hh"
11#include "BKE_instances.hh"
12#include "BKE_pointcloud.hh"
13
14#include "NOD_rna_define.hh"
15
17#include "UI_resources.hh"
18
19#include "RNA_enum_types.hh"
20
21#include "BLI_array_utils.hh"
22
24
26{
27 b.add_input<decl::Geometry>("Geometry")
28 .supported_type({GeometryComponent::Type::Mesh,
29 GeometryComponent::Type::PointCloud,
30 GeometryComponent::Type::Curve,
31 GeometryComponent::Type::Instance})
32 .description("Geometry to split into instances");
33 b.add_input<decl::Bool>("Selection").default_value(true).field_on_all().hide_value();
34 b.add_input<decl::Int>("Group ID").field_on_all().hide_value();
35 b.add_output<decl::Geometry>("Instances")
36 .propagate_all()
37 .description("All geometry groups as separate instances");
38 b.add_output<decl::Int>("Group ID")
39 .field_on_all()
40 .description("The group ID of each group instance");
41}
42
43static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
44{
45 layout->use_property_split_set(true);
46 layout->use_property_decorate_set(false);
47 layout->prop(ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
48}
49
50static void ensure_group_geometries(Map<int, std::unique_ptr<GeometrySet>> &geometry_by_group_id,
51 const Span<int> group_ids)
52{
53 for (const int group_id : group_ids) {
54 geometry_by_group_id.lookup_or_add_cb(group_id,
55 []() { return std::make_unique<GeometrySet>(); });
56 }
57}
58
60 std::optional<bke::GeometryFieldContext> field_context;
61 std::optional<FieldEvaluator> field_evaluator;
62
64
67};
68
72[[nodiscard]] static bool do_common_split(
73 const GeometryComponent &src_component,
74 const AttrDomain domain,
75 const Field<bool> &selection_field,
76 const Field<int> &group_id_field,
77 Map<int, std::unique_ptr<GeometrySet>> &geometry_by_group_id,
78 SplitGroups &r_groups)
79{
80 const int domain_size = src_component.attribute_domain_size(domain);
81
82 r_groups.field_context.emplace(src_component, domain);
83 FieldEvaluator &field_evaluator = r_groups.field_evaluator.emplace(*r_groups.field_context,
84 domain_size);
85 field_evaluator.set_selection(selection_field);
86 field_evaluator.add(group_id_field);
87 field_evaluator.evaluate();
88
89 const IndexMask selection = field_evaluator.get_evaluated_selection_as_mask();
90 if (selection.is_empty()) {
91 return true;
92 }
93
95 selection, field_evaluator.get_evaluated<int>(0), r_groups.memory, r_groups.group_ids);
96
97 ensure_group_geometries(geometry_by_group_id, r_groups.group_ids);
98 return false;
99}
100
101static void split_mesh_groups(const MeshComponent &component,
102 const AttrDomain domain,
103 const Field<bool> &selection_field,
104 const Field<int> &group_id_field,
105 const AttributeFilter &attribute_filter,
106 Map<int, std::unique_ptr<GeometrySet>> &geometry_by_group_id)
107{
108 SplitGroups split_groups;
109 if (do_common_split(
110 component, domain, selection_field, group_id_field, geometry_by_group_id, split_groups))
111 {
112 return;
113 }
114 const Mesh &src_mesh = *component.get();
115 const int domain_size = component.attribute_domain_size(domain);
116
117 threading::EnumerableThreadSpecific<Array<bool>> group_selection_per_thread{
118 [&]() { return Array<bool>(domain_size, false); }};
119
120 threading::parallel_for(split_groups.group_masks.index_range(), 16, [&](const IndexRange range) {
121 /* Need task isolation because of the thread local variable. */
122 threading::isolate_task([&]() {
123 MutableSpan<bool> group_selection = group_selection_per_thread.local();
124 const VArray<bool> group_selection_varray = VArray<bool>::from_span(group_selection);
125 for (const int group_index : range) {
126 const IndexMask &mask = split_groups.group_masks[group_index];
127 index_mask::masked_fill(group_selection, true, mask);
128 const int group_id = split_groups.group_ids[group_index];
129
130 /* Using #mesh_copy_selection here is not ideal, because it can lead to O(n^2) behavior
131 * when there are many groups. */
132 std::optional<Mesh *> group_mesh_opt = geometry::mesh_copy_selection(
133 src_mesh, group_selection_varray, domain, attribute_filter);
134 GeometrySet &group_geometry = *geometry_by_group_id.lookup(group_id);
135 if (group_mesh_opt.has_value()) {
136 if (Mesh *group_mesh = *group_mesh_opt) {
137 group_geometry.replace_mesh(group_mesh);
138 }
139 else {
140 group_geometry.replace_mesh(nullptr);
141 }
142 }
143 else {
144 group_geometry.add(component);
145 }
146
147 index_mask::masked_fill(group_selection, false, mask);
148 }
149 });
150 });
151}
152
153static void split_pointcloud_groups(const PointCloudComponent &component,
154 const Field<bool> &selection_field,
155 const Field<int> &group_id_field,
156 const AttributeFilter &attribute_filter,
157 Map<int, std::unique_ptr<GeometrySet>> &geometry_by_group_id)
158{
159 SplitGroups split_groups;
160 if (do_common_split(component,
161 AttrDomain::Point,
162 selection_field,
163 group_id_field,
164 geometry_by_group_id,
165 split_groups))
166 {
167 return;
168 }
169 const PointCloud &src_pointcloud = *component.get();
170 threading::parallel_for(split_groups.group_ids.index_range(), 16, [&](const IndexRange range) {
171 for (const int group_index : range) {
172 const IndexMask &mask = split_groups.group_masks[group_index];
173 const int group_id = split_groups.group_ids[group_index];
174
175 PointCloud *group_pointcloud = BKE_pointcloud_new_nomain(mask.size());
176
177 const AttributeAccessor src_attributes = src_pointcloud.attributes();
178 MutableAttributeAccessor dst_attributes = group_pointcloud->attributes_for_write();
179 bke::gather_attributes(src_attributes,
180 AttrDomain::Point,
181 AttrDomain::Point,
182 attribute_filter,
183 mask,
184 dst_attributes);
185
186 GeometrySet &group_geometry = *geometry_by_group_id.lookup(group_id);
187 group_geometry.replace_pointcloud(group_pointcloud);
188 }
189 });
190}
191
192static void split_curve_groups(const bke::CurveComponent &component,
193 const AttrDomain domain,
194 const Field<bool> &selection_field,
195 const Field<int> &group_id_field,
196 const AttributeFilter &attribute_filter,
197 Map<int, std::unique_ptr<GeometrySet>> &geometry_by_group_id)
198{
199 SplitGroups split_groups;
200 if (do_common_split(
201 component, domain, selection_field, group_id_field, geometry_by_group_id, split_groups))
202 {
203 return;
204 }
205 const bke::CurvesGeometry &src_curves = component.get()->geometry.wrap();
206 threading::parallel_for(split_groups.group_ids.index_range(), 16, [&](const IndexRange range) {
207 for (const int group_index : range) {
208 const IndexMask &mask = split_groups.group_masks[group_index];
209 const int group_id = split_groups.group_ids[group_index];
210
211 bke::CurvesGeometry group_curves;
212 if (domain == AttrDomain::Point) {
213 group_curves = bke::curves_copy_point_selection(src_curves, mask, attribute_filter);
214 }
215 else {
216 group_curves = bke::curves_copy_curve_selection(src_curves, mask, attribute_filter);
217 }
218 Curves *group_curves_id = bke::curves_new_nomain(std::move(group_curves));
219 GeometrySet &group_geometry = *geometry_by_group_id.lookup(group_id);
220 group_geometry.replace_curves(group_curves_id);
221 }
222 });
223}
224
225static void split_instance_groups(const InstancesComponent &component,
226 const Field<bool> &selection_field,
227 const Field<int> &group_id_field,
228 const AttributeFilter &attribute_filter,
229 Map<int, std::unique_ptr<GeometrySet>> &geometry_by_group_id)
230{
231 SplitGroups split_groups;
232 if (do_common_split(component,
233 AttrDomain::Instance,
234 selection_field,
235 group_id_field,
236 geometry_by_group_id,
237 split_groups))
238 {
239 return;
240 }
241 const bke::Instances &src_instances = *component.get();
242 threading::parallel_for(split_groups.group_ids.index_range(), 16, [&](const IndexRange range) {
243 for (const int group_index : range) {
244 const IndexMask &mask = split_groups.group_masks[group_index];
245 const int group_id = split_groups.group_ids[group_index];
246
247 bke::Instances *group_instances = new bke::Instances();
248 group_instances->resize(mask.size());
249
250 for (const bke::InstanceReference &reference : src_instances.references()) {
251 group_instances->add_reference(reference);
252 }
253
254 bke::gather_attributes(src_instances.attributes(),
255 AttrDomain::Instance,
256 AttrDomain::Instance,
257 attribute_filter,
258 mask,
259 group_instances->attributes_for_write());
260 group_instances->remove_unused_references();
261
262 GeometrySet &group_geometry = *geometry_by_group_id.lookup(group_id);
263 group_geometry.replace_instances(group_instances);
264 }
265 });
266}
267
269{
270 const bNode &node = params.node();
271 const AttrDomain domain = AttrDomain(node.custom1);
272
273 GeometrySet src_geometry = params.extract_input<GeometrySet>("Geometry");
274 const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
275 const Field<int> group_id_field = params.extract_input<Field<int>>("Group ID");
276
277 const NodeAttributeFilter &attribute_filter = params.get_attribute_filter("Instances");
278
279 Map<int, std::unique_ptr<GeometrySet>> geometry_by_group_id;
280
281 if (src_geometry.has_mesh() &&
282 ELEM(domain, AttrDomain::Point, AttrDomain::Edge, AttrDomain::Face))
283 {
284 const auto &component = *src_geometry.get_component<MeshComponent>();
285 split_mesh_groups(component,
286 domain,
287 selection_field,
288 group_id_field,
289 attribute_filter,
290 geometry_by_group_id);
291 }
292 if (src_geometry.has_pointcloud() && domain == AttrDomain::Point) {
293 const auto &component = *src_geometry.get_component<PointCloudComponent>();
295 component, selection_field, group_id_field, attribute_filter, geometry_by_group_id);
296 }
297 if (src_geometry.has_curves() && ELEM(domain, AttrDomain::Point, AttrDomain::Curve)) {
298 const auto &component = *src_geometry.get_component<bke::CurveComponent>();
299 split_curve_groups(component,
300 domain,
301 selection_field,
302 group_id_field,
303 attribute_filter,
304 geometry_by_group_id);
305 }
306 if (src_geometry.has_instances() && domain == AttrDomain::Instance) {
307 const auto &component = *src_geometry.get_component<bke::InstancesComponent>();
309 component, selection_field, group_id_field, attribute_filter, geometry_by_group_id);
310 }
311
312 bke::Instances *dst_instances = new bke::Instances();
313 GeometrySet dst_geometry = GeometrySet::from_instances(dst_instances);
314 const int total_groups_num = geometry_by_group_id.size();
315 dst_instances->resize(total_groups_num);
316
317 std::optional<std::string> dst_group_id_attribute_id =
318 params.get_output_anonymous_attribute_id_if_needed("Group ID");
319 if (dst_group_id_attribute_id) {
320 SpanAttributeWriter<int> dst_group_id =
322 *dst_group_id_attribute_id, AttrDomain::Instance);
323 std::copy(geometry_by_group_id.keys().begin(),
324 geometry_by_group_id.keys().end(),
325 dst_group_id.span.begin());
326 dst_group_id.finish();
327 }
328
329 dst_instances->transforms_for_write().fill(float4x4::identity());
331
332 for (auto item : geometry_by_group_id.items()) {
333 std::unique_ptr<GeometrySet> &group_geometry = item.value;
334 dst_instances->add_reference(std::move(group_geometry));
335 }
336
337 dst_geometry.name = src_geometry.name;
338
340
341 params.set_output("Instances", std::move(dst_geometry));
342}
343
344static void node_rna(StructRNA *srna)
345{
347 "domain",
348 "Domain",
349 "Attribute domain for the Selection and Group ID inputs",
352 int(AttrDomain::Point));
353}
354
355static void node_register()
356{
357 static blender::bke::bNodeType ntype;
358 geo_node_type_base(&ntype, "GeometryNodeSplitToInstances", GEO_NODE_SPLIT_TO_INSTANCES);
359 ntype.ui_name = "Split to Instances";
360 ntype.ui_description = "Create separate geometries containing the elements from the same group";
361 ntype.enum_name_legacy = "Split to Instances";
364 ntype.declare = node_declare;
367
368 node_rna(ntype.rna_ext.srna);
369}
370NOD_REGISTER_NODE(node_register)
371
372} // namespace blender::nodes::node_geo_split_to_instances_cc
Low-level operations for curves.
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:461
#define GEO_NODE_SPLIT_TO_INSTANCES
General operations for point clouds.
#define ELEM(...)
#define NOD_REGISTER_NODE(REGISTER_FUNC)
#define NOD_inline_enum_accessors(member)
#define UI_ITEM_NONE
AttrDomain
SubIterator begin() const
Definition BLI_map.hh:768
SubIterator end() const
Definition BLI_map.hh:778
IndexRange index_range() const
static Vector< IndexMask, 4 > from_group_ids(const VArray< int > &group_ids, IndexMaskMemory &memory, VectorSet< int > &r_index_by_group_id)
int64_t size() const
Definition BLI_map.hh:976
KeyIterator keys() const &
Definition BLI_map.hh:875
ItemIterator items() const &
Definition BLI_map.hh:902
int attribute_domain_size(AttrDomain domain) const
MutableSpan< int > reference_handles_for_write()
Definition instances.cc:222
int add_reference(const InstanceReference &reference)
Definition instances.cc:261
void resize(int capacity)
Definition instances.cc:191
bke::MutableAttributeAccessor attributes_for_write()
Definition instances.cc:69
MutableSpan< float4x4 > transforms_for_write()
Definition instances.cc:235
GSpanAttributeWriter lookup_or_add_for_write_span(StringRef attribute_id, AttrDomain domain, AttrType data_type, const AttributeInit &initializer=AttributeInitDefaultValue())
void set_selection(Field< bool > selection)
Definition FN_field.hh:383
int add(GField field, GVArray *varray_ptr)
Definition field.cc:751
IndexMask get_evaluated_selection_as_mask() const
Definition field.cc:817
const GVArray & get_evaluated(const int field_index) const
Definition FN_field.hh:448
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void fill_index_range(MutableSpan< T > span, const T start=0)
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
void debug_randomize_instance_order(bke::Instances *instances)
Definition randomize.cc:299
static void node_declare(NodeDeclarationBuilder &b)
static void split_pointcloud_groups(const PointCloudComponent &component, const Field< bool > &selection_field, const Field< int > &group_id_field, const AttributeFilter &attribute_filter, Map< int, std::unique_ptr< GeometrySet > > &geometry_by_group_id)
static bool do_common_split(const GeometryComponent &src_component, const AttrDomain domain, const Field< bool > &selection_field, const Field< int > &group_id_field, Map< int, std::unique_ptr< GeometrySet > > &geometry_by_group_id, SplitGroups &r_groups)
static void node_layout(uiLayout *layout, bContext *, PointerRNA *ptr)
static void split_mesh_groups(const MeshComponent &component, const AttrDomain domain, const Field< bool > &selection_field, const Field< int > &group_id_field, const AttributeFilter &attribute_filter, Map< int, std::unique_ptr< GeometrySet > > &geometry_by_group_id)
static void split_instance_groups(const InstancesComponent &component, const Field< bool > &selection_field, const Field< int > &group_id_field, const AttributeFilter &attribute_filter, Map< int, std::unique_ptr< GeometrySet > > &geometry_by_group_id)
static void ensure_group_geometries(Map< int, std::unique_ptr< GeometrySet > > &geometry_by_group_id, const Span< int > group_ids)
static void split_curve_groups(const bke::CurveComponent &component, const AttrDomain domain, const Field< bool > &selection_field, const Field< int > &group_id_field, const AttributeFilter &attribute_filter, Map< int, std::unique_ptr< GeometrySet > > &geometry_by_group_id)
PropertyRNA * RNA_def_node_enum(StructRNA *srna, const char *identifier, const char *ui_name, const char *ui_description, const EnumPropertyItem *static_items, const EnumRNAAccessors accessors, std::optional< int > default_value, const EnumPropertyItemFunc item_func, const bool allow_animation)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
void geo_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
const EnumPropertyItem rna_enum_attribute_domain_without_corner_items[]
CurvesGeometry geometry
StructRNA * srna
static GeometrySet from_instances(Instances *instances, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
int16_t custom1
const GeometryComponent * get_component(GeometryComponent::Type component_type) const
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:354
const char * enum_name_legacy
Definition BKE_node.hh:247
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:259
NodeDeclareFunction declare
Definition BKE_node.hh:362
void use_property_decorate_set(bool is_sep)
void use_property_split_set(bool value)
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
PointerRNA * ptr
Definition wm_files.cc:4238