Blender V4.3
node_geo_extrude_mesh.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
5#include "BLI_array_utils.hh"
6#include "BLI_task.hh"
7#include "BLI_vector_set.hh"
8
9#include "DNA_mesh_types.h"
10#include "DNA_meshdata_types.h"
11
12#include "BKE_attribute_math.hh"
13#include "BKE_customdata.hh"
14#include "BKE_deform.hh"
15#include "BKE_mesh.hh"
16#include "BKE_mesh_mapping.hh"
17#include "BKE_mesh_runtime.hh"
18
19#include "GEO_mesh_selection.hh"
20#include "GEO_randomize.hh"
21
22#include "NOD_rna_define.hh"
23
24#include "UI_interface.hh"
25#include "UI_resources.hh"
26
27#include "node_geometry_util.hh"
28
30
32
34{
35 b.add_input<decl::Geometry>("Mesh").supported_type(GeometryComponent::Type::Mesh);
36 b.add_input<decl::Bool>("Selection").default_value(true).field_on_all().hide_value();
37 b.add_input<decl::Vector>("Offset")
38 .subtype(PROP_TRANSLATION)
39 .implicit_field_on_all(implicit_field_inputs::normal)
40 .hide_value();
41 b.add_input<decl::Float>("Offset Scale").default_value(1.0f).field_on_all();
42 auto &individual =
43 b.add_input<decl::Bool>("Individual").default_value(true).make_available([](bNode &node) {
44 node_storage(node).mode = GEO_NODE_EXTRUDE_MESH_FACES;
45 });
46 b.add_output<decl::Geometry>("Mesh").propagate_all();
47 b.add_output<decl::Bool>("Top").field_on_all().translation_context(BLT_I18NCONTEXT_ID_NODETREE);
48 b.add_output<decl::Bool>("Side").field_on_all();
49
50 const bNode *node = b.node_or_null();
51 if (node != nullptr) {
52 const NodeGeometryExtrudeMesh &storage = node_storage(*node);
54 individual.available(mode == GEO_NODE_EXTRUDE_MESH_FACES);
55 }
56}
57
58static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
59{
60 uiLayoutSetPropSep(layout, true);
61 uiLayoutSetPropDecorate(layout, false);
62 uiItemR(layout, ptr, "mode", UI_ITEM_NONE, "", ICON_NONE);
63}
64
65static void node_init(bNodeTree * /*tree*/, bNode *node)
66{
67 NodeGeometryExtrudeMesh *data = MEM_cnew<NodeGeometryExtrudeMesh>(__func__);
68 data->mode = GEO_NODE_EXTRUDE_MESH_FACES;
69 node->storage = data;
70}
71
73 std::optional<std::string> top_id;
74 std::optional<std::string> side_id;
75};
76
78 const StringRef id,
79 const AttrDomain domain,
80 const IndexMask &selection)
81{
82 BLI_assert(!attributes.contains(id));
83
84 SpanAttributeWriter<bool> attribute = attributes.lookup_or_add_for_write_span<bool>(id, domain);
85 selection.to_bools(attribute.span);
86 attribute.finish();
87}
88
90 const AttributeFilter &attribute_filter)
91{
92 Set<StringRefNull> ids_to_remove = attributes.all_ids();
93 ids_to_remove.remove_if([&](const StringRef id) { return !attribute_filter.allow_skip(id); });
94 for (const StringRef id : ids_to_remove) {
95 attributes.remove(id);
96 }
97}
98
100{
101 CustomData_free_layers(&mesh.vert_data, CD_ORCO, mesh.verts_num);
102 CustomData_free_layers(&mesh.vert_data, CD_SHAPEKEY, mesh.verts_num);
103 CustomData_free_layers(&mesh.vert_data, CD_CLOTH_ORCO, mesh.verts_num);
104 CustomData_free_layers(&mesh.vert_data, CD_MVERT_SKIN, mesh.verts_num);
105}
106
108{
109 CustomData_free_layers(&mesh.edge_data, CD_FREESTYLE_EDGE, mesh.edges_num);
110}
111
113{
114 CustomData_free_layers(&mesh.face_data, CD_FREESTYLE_FACE, mesh.faces_num);
115}
116
118{
119 CustomData_free_layers(&mesh.corner_data, CD_MDISPS, mesh.corners_num);
120 CustomData_free_layers(&mesh.corner_data, CD_TANGENT, mesh.corners_num);
121 CustomData_free_layers(&mesh.corner_data, CD_MLOOPTANGENT, mesh.corners_num);
122 CustomData_free_layers(&mesh.corner_data, CD_GRID_PAINT_MASK, mesh.corners_num);
123 CustomData_free_layers(&mesh.corner_data, CD_CUSTOMLOOPNORMAL, mesh.corners_num);
124}
125
126static void expand_mesh(Mesh &mesh,
127 const int vert_expand,
128 const int edge_expand,
129 const int face_expand,
130 const int loop_expand)
131{
132 /* Remove types that aren't supported for interpolation in this node. */
133 if (vert_expand != 0) {
134 const int old_verts_num = mesh.verts_num;
135 mesh.verts_num += vert_expand;
136 CustomData_realloc(&mesh.vert_data, old_verts_num, mesh.verts_num);
137 }
138 if (edge_expand != 0) {
139 if (mesh.edges_num == 0) {
140 mesh.attributes_for_write().add(
141 ".edge_verts", AttrDomain::Edge, CD_PROP_INT32_2D, bke::AttributeInitConstruct());
142 }
143 const int old_edges_num = mesh.edges_num;
144 mesh.edges_num += edge_expand;
145 CustomData_realloc(&mesh.edge_data, old_edges_num, mesh.edges_num);
146 }
147 if (face_expand != 0) {
148 const int old_faces_num = mesh.faces_num;
149 mesh.faces_num += face_expand;
150 CustomData_realloc(&mesh.face_data, old_faces_num, mesh.faces_num);
151 implicit_sharing::resize_trivial_array(&mesh.face_offset_indices,
152 &mesh.runtime->face_offsets_sharing_info,
153 old_faces_num == 0 ? 0 : (old_faces_num + 1),
154 mesh.faces_num + 1);
155 /* Set common values for convenience. */
156 mesh.face_offset_indices[0] = 0;
157 mesh.face_offset_indices[mesh.faces_num] = mesh.corners_num + loop_expand;
158 }
159 if (loop_expand != 0) {
160 if (mesh.corners_num == 0) {
161 mesh.attributes_for_write().add(
162 ".corner_vert", AttrDomain::Corner, CD_PROP_INT32, bke::AttributeInitConstruct());
163 mesh.attributes_for_write().add(
164 ".corner_edge", AttrDomain::Corner, CD_PROP_INT32, bke::AttributeInitConstruct());
165 }
166 const int old_loops_num = mesh.corners_num;
167 mesh.corners_num += loop_expand;
168 CustomData_realloc(&mesh.corner_data, old_loops_num, mesh.corners_num);
169 }
170}
171
172static CustomData &mesh_custom_data_for_domain(Mesh &mesh, const AttrDomain domain)
173{
174 switch (domain) {
175 case AttrDomain::Point:
176 return mesh.vert_data;
177 case AttrDomain::Edge:
178 return mesh.edge_data;
179 case AttrDomain::Face:
180 return mesh.face_data;
181 case AttrDomain::Corner:
182 return mesh.corner_data;
183 default:
185 return mesh.vert_data;
186 }
187}
188
189static std::optional<MutableSpan<int>> get_orig_index_layer(Mesh &mesh, const AttrDomain domain)
190{
191 const bke::AttributeAccessor attributes = mesh.attributes();
192 CustomData &custom_data = mesh_custom_data_for_domain(mesh, domain);
193 if (int *orig_indices = static_cast<int *>(CustomData_get_layer_for_write(
194 &custom_data, CD_ORIGINDEX, attributes.domain_size(domain))))
195 {
196 return MutableSpan<int>(orig_indices, attributes.domain_size(domain));
197 }
198 return std::nullopt;
199}
200
201template<typename T>
203 const GroupedSpan<int> src_groups,
204 const IndexMask &selection,
205 MutableSpan<T> dst)
206{
207 selection.foreach_segment(
208 GrainSize(512), [&](const IndexMaskSegment segment, const int64_t segment_pos) {
209 const IndexRange dst_range(segment_pos, segment.size());
211 for (const int i : segment.index_range()) {
212 for (const int src_i : src_groups[segment[i]]) {
213 mixer.mix_in(i, src[src_i]);
214 }
215 }
216 mixer.finalize();
217 });
218}
219
220static void copy_with_mixing(const GSpan src,
221 const GroupedSpan<int> src_groups,
222 const IndexMask &selection,
223 GMutableSpan dst)
224{
225 BLI_assert(selection.size() == dst.size());
227 using T = decltype(dummy);
228 copy_with_mixing(src.typed<T>(), src_groups, selection, dst.typed<T>());
229 });
230}
231
232template<typename T>
234 const GroupedSpan<int> src_groups,
235 const Span<int> selection,
236 MutableSpan<T> dst)
237{
238 threading::parallel_for(dst.index_range(), 512, [&](const IndexRange range) {
239 bke::attribute_math::DefaultPropagationMixer<T> mixer{dst.slice(range)};
240 for (const int i : range.index_range()) {
241 const int group_i = selection[i];
242 for (const int i_src : src_groups[group_i]) {
243 mixer.mix_in(i, src[i_src]);
244 }
245 }
246 mixer.finalize();
247 });
248}
249
250static void copy_with_mixing(const GSpan src,
251 const GroupedSpan<int> src_groups,
252 const Span<int> selection,
253 GMutableSpan dst)
254{
255 bke::attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
256 using T = decltype(dummy);
257 copy_with_mixing(src.typed<T>(), src_groups, selection, dst.typed<T>());
258 });
259}
260
261using IDsByDomain = std::array<Vector<StringRef>, ATTR_DOMAIN_NUM>;
262
264 const Set<StringRef> &skip)
265{
266 IDsByDomain ids_by_domain;
267 attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
268 if (iter.data_type == CD_PROP_STRING) {
269 return;
270 }
271 if (skip.contains(iter.name)) {
272 return;
273 }
274 ids_by_domain[int(iter.domain)].append(iter.name);
275 });
276 return ids_by_domain;
277}
278
279static bool is_empty_domain(const AttributeAccessor attributes,
280 const Set<StringRef> &skip,
281 const AttrDomain domain)
282{
283 bool is_empty = true;
284 attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
285 if (iter.data_type == CD_PROP_STRING) {
286 return;
287 }
288 if (iter.domain != domain) {
289 return;
290 }
291 if (skip.contains(iter.name)) {
292 return;
293 }
294 is_empty = false;
295 iter.stop();
296 });
297 return is_empty;
298}
299
300static void gather_attributes(MutableAttributeAccessor attributes,
301 const Span<StringRef> ids,
302 const Span<int> indices,
303 const IndexRange new_range)
304{
305 for (const StringRef id : ids) {
306 GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
307 bke::attribute_math::gather(attribute.span, indices, attribute.span.slice(new_range));
308 attribute.finish();
309 }
310}
311
312static void gather_attributes(MutableAttributeAccessor attributes,
313 const Span<StringRef> ids,
314 const IndexMask &indices,
315 const IndexRange new_range)
316{
317 for (const StringRef id : ids) {
318 GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
319 array_utils::gather(attribute.span, indices, attribute.span.slice(new_range));
320 attribute.finish();
321 }
322}
323
324static void gather_vert_attributes(Mesh &mesh,
325 const Span<StringRef> ids,
326 const Span<int> indices,
327 const IndexRange new_range)
328{
329 Set<StringRef> vertex_group_names;
330 LISTBASE_FOREACH (bDeformGroup *, group, &mesh.vertex_group_names) {
331 vertex_group_names.add(group->name);
332 }
333
334 if (!vertex_group_names.is_empty() && !mesh.deform_verts().is_empty()) {
335 MutableSpan<MDeformVert> dverts = mesh.deform_verts_for_write();
336 bke::gather_deform_verts(dverts, indices, dverts.slice(new_range));
337 }
338
339 MutableAttributeAccessor attributes = mesh.attributes_for_write();
340 for (const StringRef id : ids) {
341 if (!vertex_group_names.contains(id)) {
342 GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
343 bke::attribute_math::gather(attribute.span, indices, attribute.span.slice(new_range));
344 attribute.finish();
345 }
346 }
347}
348
349static void gather_vert_attributes(Mesh &mesh,
350 const Span<StringRef> ids,
351 const IndexMask &indices,
352 const IndexRange new_range)
353{
354 Set<StringRef> vertex_group_names;
355 LISTBASE_FOREACH (bDeformGroup *, group, &mesh.vertex_group_names) {
356 vertex_group_names.add(group->name);
357 }
358
359 if (!vertex_group_names.is_empty() && !mesh.deform_verts().is_empty()) {
360 MutableSpan<MDeformVert> dverts = mesh.deform_verts_for_write();
361 bke::gather_deform_verts(dverts, indices, dverts.slice(new_range));
362 }
363
364 MutableAttributeAccessor attributes = mesh.attributes_for_write();
365 for (const StringRef id : ids) {
366 if (!vertex_group_names.contains(id)) {
367 GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
368 array_utils::gather(attribute.span, indices, attribute.span.slice(new_range));
369 attribute.finish();
370 }
371 }
372}
373
374static void extrude_mesh_vertices(Mesh &mesh,
375 const Field<bool> &selection_field,
376 const Field<float3> &offset_field,
377 const AttributeOutputs &attribute_outputs,
378 const AttributeFilter &attribute_filter)
379{
380 const int orig_vert_size = mesh.verts_num;
381 const int orig_edge_size = mesh.edges_num;
382
383 /* Use an array for the result of the evaluation because the mesh is reallocated before
384 * the vertices are moved, and the evaluated result might reference an attribute. */
385 Array<float3> offsets(orig_vert_size);
386 const bke::MeshFieldContext context{mesh, AttrDomain::Point};
387 FieldEvaluator evaluator{context, mesh.verts_num};
388 evaluator.add_with_destination(offset_field, offsets.as_mutable_span());
389 evaluator.set_selection(selection_field);
390 evaluator.evaluate();
391 const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
392 if (selection.is_empty()) {
393 return;
394 }
395
396 MutableAttributeAccessor attributes = mesh.attributes_for_write();
397 remove_non_propagated_attributes(attributes, attribute_filter);
398
399 const IDsByDomain ids_by_domain = attribute_ids_by_domain(attributes,
400 {"position", ".edge_verts"});
401
402 Array<int> vert_to_edge_offsets;
403 Array<int> vert_to_edge_indices;
404 GroupedSpan<int> vert_to_edge_map;
405 if (!ids_by_domain[int(AttrDomain::Edge)].is_empty()) {
406 vert_to_edge_map = bke::mesh::build_vert_to_edge_map(
407 mesh.edges(), orig_vert_size, vert_to_edge_offsets, vert_to_edge_indices);
408 }
409
412 expand_mesh(mesh, selection.size(), selection.size(), 0, 0);
413
414 const IndexRange new_vert_range{orig_vert_size, selection.size()};
415 const IndexRange new_edge_range{orig_edge_size, selection.size()};
416
417 MutableSpan<int2> new_edges = mesh.edges_for_write().slice(new_edge_range);
418 selection.foreach_index_optimized<int>(
419 GrainSize(4096), [&](const int index, const int i_selection) {
420 new_edges[i_selection] = int2(index, new_vert_range[i_selection]);
421 });
422
423 /* New vertices copy the attribute values from their source vertex. */
424 gather_vert_attributes(mesh, ids_by_domain[int(AttrDomain::Point)], selection, new_vert_range);
425
426 /* New edge values are mixed from of all the edges connected to the source vertex. */
427 for (const StringRef id : ids_by_domain[int(AttrDomain::Edge)]) {
428 GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
430 attribute.span, vert_to_edge_map, selection, attribute.span.slice(new_edge_range));
431 attribute.finish();
432 }
433
434 MutableSpan<float3> positions = mesh.vert_positions_for_write();
435 MutableSpan<float3> new_positions = positions.slice(new_vert_range);
436 selection.foreach_index_optimized<int>(GrainSize(1024), [&](const int index, const int i) {
437 new_positions[i] = positions[index] + offsets[index];
438 });
439
440 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Point)) {
441 array_utils::gather(indices->as_span(), selection, indices->slice(new_vert_range));
442 }
443 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Edge)) {
444 indices->slice(new_edge_range).fill(ORIGINDEX_NONE);
445 }
446
447 if (attribute_outputs.top_id) {
449 attributes, *attribute_outputs.top_id, AttrDomain::Point, new_vert_range);
450 }
451 if (attribute_outputs.side_id) {
453 attributes, *attribute_outputs.side_id, AttrDomain::Edge, new_edge_range);
454 }
455
456 const bool no_loose_vert_hint = mesh.runtime->loose_verts_cache.is_cached() &&
457 mesh.runtime->loose_verts_cache.data().count == 0;
458 const bool no_overlapping_hint = mesh.no_overlapping_topology();
460 if (no_loose_vert_hint) {
461 mesh.tag_loose_verts_none();
462 }
463 if (no_overlapping_hint) {
464 mesh.tag_overlapping_none();
465 }
466}
467
468static void fill_quad_consistent_direction(const Span<int> other_face_verts,
469 const Span<int> other_face_edges,
470 MutableSpan<int> new_corner_verts,
471 MutableSpan<int> new_corner_edges,
472 const int vert_connected_to_face_1,
473 const int vert_connected_to_face_2,
474 const int vert_across_from_face_1,
475 const int vert_across_from_face_2,
476 const int edge_connected_to_face,
477 const int connecting_edge_1,
478 const int edge_across_from_face,
479 const int connecting_edge_2)
480{
481 /* Find the loop on the face connected to the new quad that uses the duplicate edge. */
482 bool start_with_connecting_edge = true;
483 for (const int i : other_face_edges.index_range()) {
484 if (other_face_edges[i] == edge_connected_to_face) {
485 start_with_connecting_edge = other_face_verts[i] == vert_connected_to_face_1;
486 break;
487 }
488 }
489 if (start_with_connecting_edge) {
490 new_corner_verts[0] = vert_connected_to_face_1;
491 new_corner_edges[0] = connecting_edge_1;
492 new_corner_verts[1] = vert_across_from_face_1;
493 new_corner_edges[1] = edge_across_from_face;
494 new_corner_verts[2] = vert_across_from_face_2;
495 new_corner_edges[2] = connecting_edge_2;
496 new_corner_verts[3] = vert_connected_to_face_2;
497 new_corner_edges[3] = edge_connected_to_face;
498 }
499 else {
500 new_corner_verts[0] = vert_connected_to_face_1;
501 new_corner_edges[0] = edge_connected_to_face;
502 new_corner_verts[1] = vert_connected_to_face_2;
503 new_corner_edges[1] = connecting_edge_2;
504 new_corner_verts[2] = vert_across_from_face_2;
505 new_corner_edges[2] = edge_across_from_face;
506 new_corner_verts[3] = vert_across_from_face_1;
507 new_corner_edges[3] = connecting_edge_1;
508 }
509}
510
511static GroupedSpan<int> build_vert_to_edge_map(const Span<int2> edges,
512 const IndexMask &edge_mask,
513 const int verts_num,
514 Array<int> &r_offsets,
515 Array<int> &r_indices)
516{
517 if (edge_mask.size() == edges.size()) {
518 return bke::mesh::build_vert_to_edge_map(edges, verts_num, r_offsets, r_indices);
519 }
520 Array<int2> masked_edges(edge_mask.size());
521 array_utils::gather(edges, edge_mask, masked_edges.as_mutable_span());
522
523 bke::mesh::build_vert_to_edge_map(masked_edges, verts_num, r_offsets, r_indices);
524
525 Array<int> masked_edge_to_edge(edge_mask.size());
526 edge_mask.to_indices<int>(masked_edge_to_edge);
527
528 threading::parallel_for(r_indices.index_range(), 4096, [&](const IndexRange range) {
529 for (const int i : range) {
530 r_indices[i] = masked_edge_to_edge[r_indices[i]];
531 }
532 });
533
534 return {r_offsets.as_span(), r_indices.as_span()};
535}
536static void tag_mesh_added_faces(Mesh &mesh)
537{
538 const bool no_loose_vert_hint = mesh.runtime->loose_verts_cache.is_cached() &&
539 mesh.runtime->loose_verts_cache.data().count == 0;
540 const bool no_loose_edge_hint = mesh.runtime->loose_edges_cache.is_cached() &&
541 mesh.runtime->loose_edges_cache.data().count == 0;
542 const bool no_overlapping_hint = mesh.no_overlapping_topology();
544 if (no_loose_vert_hint) {
545 mesh.tag_loose_verts_none();
546 }
547 if (no_loose_edge_hint) {
548 mesh.tag_loose_edges_none();
549 }
550 if (no_overlapping_hint) {
551 mesh.tag_overlapping_none();
552 }
553}
554
555static void extrude_mesh_edges(Mesh &mesh,
556 const Field<bool> &selection_field,
557 const Field<float3> &offset_field,
558 const AttributeOutputs &attribute_outputs,
559 const AttributeFilter &attribute_filter)
560{
561 const int orig_vert_size = mesh.verts_num;
562 const Span<int2> orig_edges = mesh.edges();
563 const OffsetIndices orig_faces = mesh.faces();
564 const int orig_loop_size = mesh.corners_num;
565
566 const bke::MeshFieldContext edge_context{mesh, AttrDomain::Edge};
567 FieldEvaluator edge_evaluator{edge_context, mesh.edges_num};
568 edge_evaluator.set_selection(selection_field);
569 edge_evaluator.add(offset_field);
570 edge_evaluator.evaluate();
571 const IndexMask edge_selection = edge_evaluator.get_evaluated_selection_as_mask();
572 const VArray<float3> edge_offsets = edge_evaluator.get_evaluated<float3>(0);
573 if (edge_selection.is_empty()) {
574 return;
575 }
576
577 /* Find the offsets on the vertex domain for translation. This must be done before the mesh's
578 * custom data layers are reallocated, in case the virtual array references one of them. */
579 Array<float3> vert_offsets;
580 if (!edge_offsets.is_single()) {
581 vert_offsets.reinitialize(orig_vert_size);
583 edge_selection.foreach_index([&](const int i_edge) {
584 const int2 edge = orig_edges[i_edge];
585 const float3 offset = edge_offsets[i_edge];
586 mixer.mix_in(edge[0], offset);
587 mixer.mix_in(edge[1], offset);
588 });
589 mixer.finalize();
590 }
591
592 IndexMaskMemory memory;
593 const IndexMask new_verts = geometry::vert_selection_from_edge(
594 orig_edges, edge_selection, orig_vert_size, memory);
595
596 const IndexRange new_vert_range{orig_vert_size, new_verts.size()};
597 /* The extruded edges connect the original and duplicate edges. */
598 const IndexRange connect_edge_range{orig_edges.size(), new_vert_range.size()};
599 /* The duplicate edges are extruded copies of the selected edges. */
600 const IndexRange duplicate_edge_range = connect_edge_range.after(edge_selection.size());
601 /* There is a new face for every selected edge. */
602 const IndexRange new_face_range{orig_faces.size(), edge_selection.size()};
603 /* Every new face is a quad with four corners. */
604 const IndexRange new_loop_range{orig_loop_size, new_face_range.size() * 4};
605
606 MutableAttributeAccessor attributes = mesh.attributes_for_write();
607 remove_non_propagated_attributes(attributes, attribute_filter);
608
609 Array<int> edge_to_face_offsets;
610 Array<int> edge_to_face_indices;
611 const GroupedSpan<int> edge_to_face_map = bke::mesh::build_edge_to_face_map(
612 orig_faces, mesh.corner_edges(), mesh.edges_num, edge_to_face_offsets, edge_to_face_indices);
613
614 Array<int> vert_to_edge_offsets;
615 Array<int> vert_to_edge_indices;
616 GroupedSpan<int> vert_to_selected_edge_map;
617 if (!is_empty_domain(attributes, {".edge_verts"}, AttrDomain::Edge)) {
618 vert_to_selected_edge_map = build_vert_to_edge_map(
619 orig_edges, edge_selection, orig_vert_size, vert_to_edge_offsets, vert_to_edge_indices);
620 }
621
626 expand_mesh(mesh,
627 new_vert_range.size(),
628 connect_edge_range.size() + duplicate_edge_range.size(),
629 new_face_range.size(),
630 new_loop_range.size());
631
632 const IDsByDomain ids_by_domain = attribute_ids_by_domain(
633 attributes, {"position", ".edge_verts", ".corner_vert", ".corner_edge"});
634
635 MutableSpan<int2> edges = mesh.edges_for_write();
636 MutableSpan<int2> connect_edges = edges.slice(connect_edge_range);
637 MutableSpan<int2> duplicate_edges = edges.slice(duplicate_edge_range);
638 MutableSpan<int> face_offsets = mesh.face_offsets_for_write();
639 MutableSpan<int> new_face_offsets = face_offsets.slice(new_face_range);
640 MutableSpan<int> corner_verts = mesh.corner_verts_for_write();
641 MutableSpan<int> new_corner_verts = corner_verts.slice(new_loop_range);
642 MutableSpan<int> corner_edges = mesh.corner_edges_for_write();
643 MutableSpan<int> new_corner_edges = corner_edges.slice(new_loop_range);
644
645 offset_indices::fill_constant_group_size(4, orig_loop_size, new_face_offsets);
646 const OffsetIndices faces = mesh.faces();
647
648 new_verts.foreach_index_optimized<int>(GrainSize(4096), [&](const int src, const int dst) {
649 connect_edges[dst] = int2(src, new_vert_range[dst]);
650 });
651
652 {
653 Array<int> vert_to_new_vert(orig_vert_size);
654 index_mask::build_reverse_map<int>(new_verts, vert_to_new_vert);
655 for (const int i : duplicate_edges.index_range()) {
656 const int2 orig_edge = edges[edge_selection[i]];
657 const int i_new_vert_1 = vert_to_new_vert[orig_edge[0]];
658 const int i_new_vert_2 = vert_to_new_vert[orig_edge[1]];
659 duplicate_edges[i] = int2(new_vert_range[i_new_vert_1], new_vert_range[i_new_vert_2]);
660 }
661 }
662
663 edge_selection.foreach_index([&](const int64_t orig_edge_index, const int64_t i) {
664 const int2 duplicate_edge = duplicate_edges[i];
665 const int new_vert_1 = duplicate_edge[0];
666 const int new_vert_2 = duplicate_edge[1];
667 const int extrude_index_1 = new_vert_1 - orig_vert_size;
668 const int extrude_index_2 = new_vert_2 - orig_vert_size;
669
670 const int2 orig_edge = edges[orig_edge_index];
671 const Span<int> connected_faces = edge_to_face_map[orig_edge_index];
672
673 /* When there was a single face connected to the new face, we can use the old one to keep
674 * the face direction consistent. When there is more than one connected face, the new face
675 * direction is totally arbitrary and the only goal for the behavior is to be deterministic. */
676 Span<int> connected_face_verts;
677 Span<int> connected_face_edges;
678 if (connected_faces.size() == 1) {
679 const IndexRange connected_face = faces[connected_faces.first()];
680 connected_face_verts = corner_verts.slice(connected_face);
681 connected_face_edges = corner_edges.slice(connected_face);
682 }
683 fill_quad_consistent_direction(connected_face_verts,
684 connected_face_edges,
685 new_corner_verts.slice(4 * i, 4),
686 new_corner_edges.slice(4 * i, 4),
687 orig_edge[0],
688 orig_edge[1],
689 new_vert_1,
690 new_vert_2,
691 orig_edge_index,
692 connect_edge_range[extrude_index_1],
693 duplicate_edge_range[i],
694 connect_edge_range[extrude_index_2]);
695 });
696
697 /* New vertices copy the attribute values from their source vertex. */
698 gather_vert_attributes(mesh, ids_by_domain[int(AttrDomain::Point)], new_verts, new_vert_range);
699
700 /* Edges parallel to original edges copy the edge attributes from the original edges. */
701 gather_attributes(
702 attributes, ids_by_domain[int(AttrDomain::Edge)], edge_selection, duplicate_edge_range);
703
704 /* Edges connected to original vertices mix values of selected connected edges. */
705 for (const StringRef id : ids_by_domain[int(AttrDomain::Edge)]) {
706 GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
707 copy_with_mixing(attribute.span,
708 vert_to_selected_edge_map,
709 new_verts,
710 attribute.span.slice(connect_edge_range));
711 attribute.finish();
712 }
713
714 /* Attribute values for new faces are a mix of values connected to its original edge. */
715 for (const StringRef id : ids_by_domain[int(AttrDomain::Face)]) {
716 GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
718 attribute.span, edge_to_face_map, edge_selection, attribute.span.slice(new_face_range));
719 attribute.finish();
720 }
721
722 /* New corners get the average value of all adjacent corners on original faces connected
723 * to the original edge of their face. */
724 for (const StringRef id : ids_by_domain[int(AttrDomain::Corner)]) {
725 GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
726 bke::attribute_math::convert_to_static_type(attribute.span.type(), [&](auto dummy) {
727 using T = decltype(dummy);
728 MutableSpan<T> data = attribute.span.typed<T>();
729 MutableSpan<T> new_data = data.slice(new_loop_range);
730 edge_selection.foreach_index(
731 GrainSize(256), [&](const int64_t orig_edge_index, const int64_t i_edge_selection) {
732 const Span<int> connected_faces = edge_to_face_map[orig_edge_index];
733 if (connected_faces.is_empty()) {
734 /* If there are no connected faces, there is no corner data to interpolate. */
735 new_data.slice(4 * i_edge_selection, 4).fill(T());
736 return;
737 }
738
739 /* Both corners on each vertical edge of the side face get the same value,
740 * so there are only two unique values to mix. */
741 Array<T> side_face_corner_data(2);
742 bke::attribute_math::DefaultPropagationMixer<T> mixer{side_face_corner_data};
743
744 const int new_vert_1 = duplicate_edges[i_edge_selection][0];
745 const int new_vert_2 = duplicate_edges[i_edge_selection][1];
746 const int orig_vert_1 = edges[orig_edge_index][0];
747 const int orig_vert_2 = edges[orig_edge_index][1];
748
749 /* Average the corner data from the corners that share a vertex from the
750 * faces that share an edge with the extruded edge. */
751 for (const int connected_face : connected_faces) {
752 for (const int i_loop : faces[connected_face]) {
753 if (corner_verts[i_loop] == orig_vert_1) {
754 mixer.mix_in(0, data[i_loop]);
755 }
756 if (corner_verts[i_loop] == orig_vert_2) {
757 mixer.mix_in(1, data[i_loop]);
758 }
759 }
760 }
761
762 mixer.finalize();
763
764 /* Instead of replicating the order in #fill_quad_consistent_direction here, it's
765 * simpler (though probably slower) to just match the corner data based on the
766 * vertex indices. */
767 for (const int i : IndexRange(4 * i_edge_selection, 4)) {
768 if (ELEM(new_corner_verts[i], new_vert_1, orig_vert_1)) {
769 new_data[i] = side_face_corner_data.first();
770 }
771 else if (ELEM(new_corner_verts[i], new_vert_2, orig_vert_2)) {
772 new_data[i] = side_face_corner_data.last();
773 }
774 }
775 });
776 });
777
778 attribute.finish();
779 }
780
781 MutableSpan<float3> positions = mesh.vert_positions_for_write();
782 MutableSpan<float3> new_positions = positions.slice(new_vert_range);
783 if (edge_offsets.is_single()) {
784 const float3 offset = edge_offsets.get_internal_single();
785 new_verts.foreach_index_optimized<int>(GrainSize(1024), [&](const int src, const int dst) {
786 new_positions[dst] = positions[src] + offset;
787 });
788 }
789 else {
790 new_verts.foreach_index_optimized<int>(GrainSize(1024), [&](const int src, const int dst) {
791 new_positions[dst] = positions[src] + vert_offsets[src];
792 });
793 }
794
795 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Point)) {
796 array_utils::gather(indices->as_span(), new_verts, indices->slice(new_vert_range));
797 }
798 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Edge)) {
799 indices->slice(connect_edge_range).fill(ORIGINDEX_NONE);
800 array_utils::gather(indices->as_span(), edge_selection, indices->slice(duplicate_edge_range));
801 }
802 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Face)) {
803 indices->slice(new_face_range).fill(ORIGINDEX_NONE);
804 }
805
806 if (attribute_outputs.top_id) {
808 attributes, *attribute_outputs.top_id, AttrDomain::Edge, duplicate_edge_range);
809 }
810 if (attribute_outputs.side_id) {
812 attributes, *attribute_outputs.side_id, AttrDomain::Face, new_face_range);
813 }
814
816}
817
818static VectorSet<int> vert_indices_from_edges(const Mesh &mesh, const Span<int> edge_indices)
819{
820 const Span<int2> edges = mesh.edges();
821
822 VectorSet<int> vert_indices;
823 vert_indices.reserve(edge_indices.size());
824 for (const int i_edge : edge_indices) {
825 const int2 &edge = edges[i_edge];
826 vert_indices.add(edge[0]);
827 vert_indices.add(edge[1]);
828 }
829 return vert_indices;
830}
831
837 const Field<bool> &selection_field,
838 const Field<float3> &offset_field,
839 const AttributeOutputs &attribute_outputs,
840 const AttributeFilter &attribute_filter)
841{
842 const int orig_vert_size = mesh.verts_num;
843 const Span<int2> orig_edges = mesh.edges();
844 const OffsetIndices orig_faces = mesh.faces();
845 const Span<int> orig_corner_verts = mesh.corner_verts();
846 const int orig_loop_size = orig_corner_verts.size();
847
848 const bke::MeshFieldContext face_context{mesh, AttrDomain::Face};
849 FieldEvaluator face_evaluator{face_context, mesh.faces_num};
850 face_evaluator.set_selection(selection_field);
851 face_evaluator.add(offset_field);
852 face_evaluator.evaluate();
853 const IndexMask face_selection = face_evaluator.get_evaluated_selection_as_mask();
854 const VArray<float3> face_position_offsets = face_evaluator.get_evaluated<float3>(0);
855 if (face_selection.is_empty()) {
856 return;
857 }
858
859 Array<bool> face_selection_array(orig_faces.size());
860 face_selection.to_bools(face_selection_array);
861
862 /* Mix the offsets from the face domain to the vertex domain. Evaluate on the face domain above
863 * in order to be consistent with the selection, and to use the face normals rather than vertex
864 * normals as an offset, for example. */
865 Array<float3> vert_offsets;
866 if (!face_position_offsets.is_single()) {
867 vert_offsets.reinitialize(orig_vert_size);
869 face_selection.foreach_index([&](const int i_face) {
870 const float3 offset = face_position_offsets[i_face];
871 for (const int vert : orig_corner_verts.slice(orig_faces[i_face])) {
872 mixer.mix_in(vert, offset);
873 }
874 });
875 mixer.finalize();
876 }
877
878 /* All of the faces (selected and deselected) connected to each edge. */
879 Array<int> edge_to_face_offsets;
880 Array<int> edge_to_face_indices;
881 const GroupedSpan<int> edge_to_face_map = bke::mesh::build_edge_to_face_map(
882 orig_faces, mesh.corner_edges(), mesh.edges_num, edge_to_face_offsets, edge_to_face_indices);
883
884 /* All vertices that are connected to the selected faces. */
885 IndexMaskMemory memory;
886 const IndexMask all_selected_verts = geometry::vert_selection_from_face(
887 orig_faces, face_selection, orig_corner_verts, orig_vert_size, memory);
888
889 /* Edges inside of an extruded region that are also attached to deselected edges. They must be
890 * duplicated in order to leave the old edge attached to the unchanged deselected faces. */
891 VectorSet<int> new_inner_edge_indices;
892 /* Edges inside of an extruded region. Their vertices should be translated
893 * with the offset, but the edges themselves should not be duplicated. */
894 Vector<int> inner_edge_indices;
895 /* The extruded face corresponding to each boundary edge (and each boundary face). */
896 Vector<int> edge_extruded_face_indices;
897 /* Edges on the outside of selected regions, either because there are no
898 * other connected faces, or because all of the other faces aren't selected. */
899 VectorSet<int> boundary_edge_indices;
900 for (const int i_edge : orig_edges.index_range()) {
901 const Span<int> faces = edge_to_face_map[i_edge];
902
903 int i_selected_face = -1;
904 int deselected_face_count = 0;
905 int selected_face_count = 0;
906 for (const int i_other_face : faces) {
907 if (face_selection_array[i_other_face]) {
908 selected_face_count++;
909 i_selected_face = i_other_face;
910 }
911 else {
912 deselected_face_count++;
913 }
914 }
915
916 if (selected_face_count == 1) {
917 /* If there is only one selected face connected to the edge,
918 * the edge should be extruded to form a "side face". */
919 boundary_edge_indices.add_new(i_edge);
920 edge_extruded_face_indices.append(i_selected_face);
921 }
922 else if (selected_face_count > 1) {
923 /* The edge is inside an extruded region of faces. */
924 if (deselected_face_count > 0) {
925 /* Add edges that are also connected to deselected edges to a separate list. */
926 new_inner_edge_indices.add_new(i_edge);
927 }
928 else {
929 /* Otherwise, just keep track of edges inside the region so that
930 * we can reattach them to duplicated vertices if necessary. */
931 inner_edge_indices.append(i_edge);
932 }
933 }
934 }
935
936 VectorSet<int> new_vert_indices = vert_indices_from_edges(mesh, boundary_edge_indices);
937 /* Before adding the rest of the new vertices from the new inner edges, store the number
938 * of new vertices from the boundary edges, since this is the number of connecting edges. */
939 const int extruded_vert_size = new_vert_indices.size();
940
941 /* The vertices attached to duplicate inner edges also have to be duplicated. */
942 for (const int i_edge : new_inner_edge_indices) {
943 const int2 &edge = orig_edges[i_edge];
944 new_vert_indices.add(edge[0]);
945 new_vert_indices.add(edge[1]);
946 }
947
948 /* New vertices forming the duplicated boundary edges and the ends of the new inner edges. */
949 const IndexRange new_vert_range{orig_vert_size, new_vert_indices.size()};
950 /* One edge connects each selected vertex to a new vertex on the extruded faces. */
951 const IndexRange connect_edge_range{orig_edges.size(), extruded_vert_size};
952 /* Each selected edge is duplicated to form a single edge on the extrusion. */
953 const IndexRange boundary_edge_range = connect_edge_range.after(boundary_edge_indices.size());
954 /* Duplicated edges inside regions that were connected to deselected faces. */
955 const IndexRange new_inner_edge_range = boundary_edge_range.after(new_inner_edge_indices.size());
956 /* Each edge selected for extrusion is extruded into a single face. */
957 const IndexRange side_face_range{orig_faces.size(), boundary_edge_indices.size()};
958 /* The loops that form the new side faces. */
959 const IndexRange side_loop_range{orig_corner_verts.size(), side_face_range.size() * 4};
960
961 MutableAttributeAccessor attributes = mesh.attributes_for_write();
962 remove_non_propagated_attributes(attributes, attribute_filter);
963
968 expand_mesh(mesh,
969 new_vert_range.size(),
970 connect_edge_range.size() + boundary_edge_range.size() + new_inner_edge_range.size(),
971 side_face_range.size(),
972 side_loop_range.size());
973
974 const IDsByDomain ids_by_domain = attribute_ids_by_domain(
975 attributes, {".corner_vert", ".corner_edge", ".edge_verts"});
976
977 MutableSpan<int2> edges = mesh.edges_for_write();
978 MutableSpan<int2> connect_edges = edges.slice(connect_edge_range);
979 MutableSpan<int2> boundary_edges = edges.slice(boundary_edge_range);
980 MutableSpan<int2> new_inner_edges = edges.slice(new_inner_edge_range);
981 MutableSpan<int> face_offsets = mesh.face_offsets_for_write();
982 MutableSpan<int> new_face_offsets = face_offsets.slice(side_face_range);
983 MutableSpan<int> corner_verts = mesh.corner_verts_for_write();
984 MutableSpan<int> new_corner_verts = corner_verts.slice(side_loop_range);
985 MutableSpan<int> corner_edges = mesh.corner_edges_for_write();
986 MutableSpan<int> new_corner_edges = corner_edges.slice(side_loop_range);
987
988 /* Initialize the new side faces. */
989 if (!new_face_offsets.is_empty()) {
990 offset_indices::fill_constant_group_size(4, orig_loop_size, new_face_offsets);
991 }
992 const OffsetIndices faces = mesh.faces();
993
994 /* Initialize the edges that form the sides of the extrusion. */
995 for (const int i : connect_edges.index_range()) {
996 connect_edges[i] = int2(new_vert_indices[i], new_vert_range[i]);
997 }
998
999 /* Initialize the edges that form the top of the extrusion. */
1000 for (const int i : boundary_edges.index_range()) {
1001 const int2 &orig_edge = edges[boundary_edge_indices[i]];
1002 const int i_new_vert_1 = new_vert_indices.index_of(orig_edge[0]);
1003 const int i_new_vert_2 = new_vert_indices.index_of(orig_edge[1]);
1004 boundary_edges[i] = int2(new_vert_range[i_new_vert_1], new_vert_range[i_new_vert_2]);
1005 }
1006
1007 /* Initialize the new edges inside of extrude regions. */
1008 for (const int i : new_inner_edge_indices.index_range()) {
1009 const int2 &orig_edge = edges[new_inner_edge_indices[i]];
1010 const int i_new_vert_1 = new_vert_indices.index_of(orig_edge[0]);
1011 const int i_new_vert_2 = new_vert_indices.index_of(orig_edge[1]);
1012 new_inner_edges[i] = int2(new_vert_range[i_new_vert_1], new_vert_range[i_new_vert_2]);
1013 }
1014
1015 /* Connect original edges inside face regions to any new vertices, if necessary. */
1016 for (const int i : inner_edge_indices) {
1017 int2 &edge = edges[i];
1018 const int i_new_vert_1 = new_vert_indices.index_of_try(edge[0]);
1019 const int i_new_vert_2 = new_vert_indices.index_of_try(edge[1]);
1020 if (i_new_vert_1 != -1) {
1021 edge[0] = new_vert_range[i_new_vert_1];
1022 }
1023 if (i_new_vert_2 != -1) {
1024 edge[1] = new_vert_range[i_new_vert_2];
1025 }
1026 }
1027
1028 /* Connect the selected faces to the extruded or duplicated edges and the new vertices. */
1029 face_selection.foreach_index([&](const int i_face) {
1030 for (const int corner : faces[i_face]) {
1031 const int i_new_vert = new_vert_indices.index_of_try(corner_verts[corner]);
1032 if (i_new_vert != -1) {
1033 corner_verts[corner] = new_vert_range[i_new_vert];
1034 }
1035 const int i_boundary_edge = boundary_edge_indices.index_of_try(corner_edges[corner]);
1036 if (i_boundary_edge != -1) {
1037 corner_edges[corner] = boundary_edge_range[i_boundary_edge];
1038 /* Skip the next check, an edge cannot be both a boundary edge and an inner edge. */
1039 continue;
1040 }
1041 const int i_new_inner_edge = new_inner_edge_indices.index_of_try(corner_edges[corner]);
1042 if (i_new_inner_edge != -1) {
1043 corner_edges[corner] = new_inner_edge_range[i_new_inner_edge];
1044 }
1045 }
1046 });
1047
1048 /* Create the faces on the sides of extruded regions. */
1049 for (const int i : boundary_edge_indices.index_range()) {
1050 const int2 &boundary_edge = boundary_edges[i];
1051 const int new_vert_1 = boundary_edge[0];
1052 const int new_vert_2 = boundary_edge[1];
1053 const int extrude_index_1 = new_vert_1 - orig_vert_size;
1054 const int extrude_index_2 = new_vert_2 - orig_vert_size;
1055
1056 const IndexRange extrude_face = faces[edge_extruded_face_indices[i]];
1057
1058 fill_quad_consistent_direction(corner_verts.slice(extrude_face),
1059 corner_edges.slice(extrude_face),
1060 new_corner_verts.slice(4 * i, 4),
1061 new_corner_edges.slice(4 * i, 4),
1062 new_vert_1,
1063 new_vert_2,
1064 new_vert_indices[extrude_index_1],
1065 new_vert_indices[extrude_index_2],
1066 boundary_edge_range[i],
1067 connect_edge_range[extrude_index_1],
1068 boundary_edge_indices[i],
1069 connect_edge_range[extrude_index_2]);
1070 }
1071
1072 /* New vertices copy the attributes from their original vertices. */
1074 mesh, ids_by_domain[int(AttrDomain::Point)], new_vert_indices, new_vert_range);
1075
1076 /* New faces on the side of extrusions get the values from the corresponding selected face. */
1077 gather_attributes(attributes,
1078 ids_by_domain[int(AttrDomain::Face)],
1079 edge_extruded_face_indices,
1080 side_face_range);
1081
1082 if (!ids_by_domain[int(AttrDomain::Edge)].is_empty()) {
1083 IndexMaskMemory memory;
1084 const IndexMask boundary_edge_mask = IndexMask::from_indices<int>(boundary_edge_indices,
1085 memory);
1086
1087 Array<int> vert_to_edge_offsets;
1088 Array<int> vert_to_edge_indices;
1089 const GroupedSpan<int> vert_to_boundary_edge_map = build_vert_to_edge_map(
1090 edges, boundary_edge_mask, mesh.verts_num, vert_to_edge_offsets, vert_to_edge_indices);
1091
1092 for (const StringRef id : ids_by_domain[int(AttrDomain::Edge)]) {
1093 GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
1094
1095 /* Edges parallel to original edges copy the edge attributes from the original edges. */
1096 GMutableSpan boundary_data = attribute.span.slice(boundary_edge_range);
1097 array_utils::gather(attribute.span, boundary_edge_mask, boundary_data);
1098
1099 /* Edges inside of face regions also just duplicate their source data. */
1100 GMutableSpan new_inner_data = attribute.span.slice(new_inner_edge_range);
1101 bke::attribute_math::gather(attribute.span, new_inner_edge_indices, new_inner_data);
1102
1103 /* Edges connected to original vertices mix values of selected connected edges. */
1104 copy_with_mixing(attribute.span,
1105 vert_to_boundary_edge_map,
1106 new_vert_indices,
1107 attribute.span.slice(connect_edge_range));
1108 attribute.finish();
1109 }
1110 }
1111
1112 /* New corners get the values from the corresponding corner on the extruded face. */
1113 if (!ids_by_domain[int(AttrDomain::Corner)].is_empty()) {
1114 Array<int> orig_corners(side_loop_range.size());
1115 threading::parallel_for(boundary_edge_indices.index_range(), 256, [&](const IndexRange range) {
1116 for (const int i_boundary_edge : range) {
1117 const int2 &boundary_edge = boundary_edges[i_boundary_edge];
1118 const int new_vert_1 = boundary_edge[0];
1119 const int new_vert_2 = boundary_edge[1];
1120 const int orig_vert_1 = new_vert_indices[new_vert_1 - orig_vert_size];
1121 const int orig_vert_2 = new_vert_indices[new_vert_2 - orig_vert_size];
1122
1123 /* Retrieve the data for the first two sides of the quad from the extruded
1124 * face, which we generally expect to have just a small amount of sides. This
1125 * loop could be eliminated by adding a cache of connected loops (which would
1126 * also simplify some of the other code to find the correct loops on the extruded
1127 * face). */
1128 int corner_1;
1129 int corner_2;
1130 for (const int corner : faces[edge_extruded_face_indices[i_boundary_edge]]) {
1131 if (corner_verts[corner] == new_vert_1) {
1132 corner_1 = corner;
1133 }
1134 if (corner_verts[corner] == new_vert_2) {
1135 corner_2 = corner;
1136 }
1137 }
1138
1139 /* Instead of replicating the order in #fill_quad_consistent_direction here, it's
1140 * simpler (though probably slower) to just match the corner data based on the
1141 * vertex indices. */
1142 for (const int i : IndexRange(4 * i_boundary_edge, 4)) {
1143 if (ELEM(new_corner_verts[i], new_vert_1, orig_vert_1)) {
1144 orig_corners[i] = corner_1;
1145 }
1146 else if (ELEM(new_corner_verts[i], new_vert_2, orig_vert_2)) {
1147 orig_corners[i] = corner_2;
1148 }
1149 }
1150 }
1151 });
1152 gather_attributes(
1153 attributes, ids_by_domain[int(AttrDomain::Corner)], orig_corners, side_loop_range);
1154 }
1155
1156 /* Translate vertices based on the offset. If the vertex is used by a selected edge, it will
1157 * have been duplicated and only the new vertex should use the offset. Otherwise the vertex might
1158 * still need an offset, but it was reused on the inside of a region of extruded faces. */
1159 MutableSpan<float3> positions = mesh.vert_positions_for_write();
1160 if (face_position_offsets.is_single()) {
1161 const float3 offset = face_position_offsets.get_internal_single();
1162 all_selected_verts.foreach_index(GrainSize(1024), [&](const int orig_vert) {
1163 const int i_new = new_vert_indices.index_of_try(orig_vert);
1164 if (i_new == -1) {
1165 positions[orig_vert] += offset;
1166 }
1167 else {
1168 positions[new_vert_range[i_new]] += offset;
1169 }
1170 });
1171 }
1172 else {
1173 all_selected_verts.foreach_index(GrainSize(1024), [&](const int orig_vert) {
1174 const int i_new = new_vert_indices.index_of_try(orig_vert);
1175 const float3 offset = vert_offsets[orig_vert];
1176 if (i_new == -1) {
1177 positions[orig_vert] += offset;
1178 }
1179 else {
1180 positions[new_vert_range[i_new]] += offset;
1181 }
1182 });
1183 }
1184
1185 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Point)) {
1186 array_utils::gather(
1187 indices->as_span(), new_vert_indices.as_span(), indices->slice(new_vert_range));
1188 }
1189 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Edge)) {
1190 indices->slice(connect_edge_range).fill(ORIGINDEX_NONE);
1191 array_utils::gather(indices->as_span(),
1192 new_inner_edge_indices.as_span(),
1193 indices->slice(new_inner_edge_range));
1194 array_utils::gather(
1195 indices->as_span(), boundary_edge_indices.as_span(), indices->slice(boundary_edge_range));
1196 }
1197 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Face)) {
1198 array_utils::gather(
1199 indices->as_span(), edge_extruded_face_indices.as_span(), indices->slice(side_face_range));
1200 }
1201
1202 if (attribute_outputs.top_id) {
1204 attributes, *attribute_outputs.top_id, AttrDomain::Face, face_selection);
1205 }
1206 if (attribute_outputs.side_id) {
1208 attributes, *attribute_outputs.side_id, AttrDomain::Face, side_face_range);
1209 }
1210
1212}
1213
1215 const Field<bool> &selection_field,
1216 const Field<float3> &offset_field,
1217 const AttributeOutputs &attribute_outputs,
1218 const AttributeFilter &attribute_filter)
1219{
1220 const int orig_vert_size = mesh.verts_num;
1221 const int orig_edge_size = mesh.edges_num;
1222 const OffsetIndices orig_faces = mesh.faces();
1223 const Span<int> orig_corner_verts = mesh.corner_verts();
1224 const int orig_loop_size = orig_corner_verts.size();
1225
1226 /* Use an array for the result of the evaluation because the mesh is reallocated before
1227 * the vertices are moved, and the evaluated result might reference an attribute. */
1228 Array<float3> face_offset(orig_faces.size());
1229 const bke::MeshFieldContext face_context{mesh, AttrDomain::Face};
1230 FieldEvaluator face_evaluator{face_context, mesh.faces_num};
1231 face_evaluator.set_selection(selection_field);
1232 face_evaluator.add_with_destination(offset_field, face_offset.as_mutable_span());
1233 face_evaluator.evaluate();
1234 const IndexMask face_selection = face_evaluator.get_evaluated_selection_as_mask();
1235 if (face_selection.is_empty()) {
1236 return;
1237 }
1238
1239 /* Build an array of offsets into the new data for each face. This is used to facilitate
1240 * parallelism later on by avoiding the need to keep track of an offset when iterating through
1241 * all faces. */
1242 Array<int> group_per_face_data(face_selection.size() + 1);
1243 const OffsetIndices<int> group_per_face = offset_indices::gather_selected_offsets(
1244 orig_faces, face_selection, group_per_face_data);
1245 const int extrude_corner_size = group_per_face.total_size();
1246
1247 const IndexRange new_vert_range{orig_vert_size, extrude_corner_size};
1248 /* One edge connects each selected vertex to a new vertex on the extruded faces. */
1249 const IndexRange connect_edge_range{orig_edge_size, extrude_corner_size};
1250 /* Each selected edge is duplicated to form a single edge on the extrusion. */
1251 const IndexRange duplicate_edge_range = connect_edge_range.after(extrude_corner_size);
1252 /* Each edge selected for extrusion is extruded into a single face. */
1253 const IndexRange side_face_range{orig_faces.size(), duplicate_edge_range.size()};
1254 const IndexRange side_loop_range{orig_loop_size, side_face_range.size() * 4};
1255
1256 MutableAttributeAccessor attributes = mesh.attributes_for_write();
1257 remove_non_propagated_attributes(attributes, attribute_filter);
1258
1263 expand_mesh(mesh,
1264 new_vert_range.size(),
1265 connect_edge_range.size() + duplicate_edge_range.size(),
1266 side_face_range.size(),
1267 side_loop_range.size());
1268
1269 const IDsByDomain ids_by_domain = attribute_ids_by_domain(
1270 attributes, {"position", ".edge_verts", ".corner_vert", ".corner_edge"});
1271
1272 MutableSpan<float3> positions = mesh.vert_positions_for_write();
1273 MutableSpan<float3> new_positions = positions.slice(new_vert_range);
1274 MutableSpan<int2> edges = mesh.edges_for_write();
1275 MutableSpan<int2> connect_edges = edges.slice(connect_edge_range);
1276 MutableSpan<int2> duplicate_edges = edges.slice(duplicate_edge_range);
1277 MutableSpan<int> face_offsets = mesh.face_offsets_for_write();
1278 MutableSpan<int> new_face_offsets = face_offsets.slice(side_face_range);
1279 MutableSpan<int> corner_verts = mesh.corner_verts_for_write();
1280 MutableSpan<int> corner_edges = mesh.corner_edges_for_write();
1281
1282 offset_indices::fill_constant_group_size(4, orig_loop_size, new_face_offsets);
1283 const OffsetIndices faces = mesh.faces();
1284
1285 /* For every selected face, change it to use the new extruded vertices and the duplicate
1286 * edges, and build the faces that form the sides of the extrusion. Build "original index"
1287 * arrays for the new vertices and edges so they can be accessed later.
1288 *
1289 * Filling some of this data like the new edges or faces could be easily split into
1290 * separate loops, which may or may not be faster, but would involve more duplication. */
1291 Array<int> new_vert_indices(extrude_corner_size);
1292 Array<int> duplicate_edge_indices(extrude_corner_size);
1293 face_selection.foreach_index(
1294 GrainSize(256), [&](const int64_t index, const int64_t i_selection) {
1295 const IndexRange extrude_range = group_per_face[i_selection];
1296
1297 const IndexRange face = faces[index];
1298 MutableSpan<int> face_verts = corner_verts.slice(face);
1299 MutableSpan<int> face_edges = corner_edges.slice(face);
1300
1301 for (const int i : face.index_range()) {
1302 const int i_extrude = extrude_range[i];
1303 new_vert_indices[i_extrude] = face_verts[i];
1304 duplicate_edge_indices[i_extrude] = face_edges[i];
1305
1306 face_verts[i] = new_vert_range[i_extrude];
1307 face_edges[i] = duplicate_edge_range[i_extrude];
1308 }
1309
1310 for (const int i : face.index_range()) {
1311 const int i_next = (i == face.size() - 1) ? 0 : i + 1;
1312 const int i_extrude = extrude_range[i];
1313 const int i_extrude_next = extrude_range[i_next];
1314
1315 const int i_duplicate_edge = duplicate_edge_range[i_extrude];
1316 const int new_vert = new_vert_range[i_extrude];
1317 const int new_vert_next = new_vert_range[i_extrude_next];
1318
1319 const int orig_edge = duplicate_edge_indices[i_extrude];
1320
1321 const int orig_vert = new_vert_indices[i_extrude];
1322 const int orig_vert_next = new_vert_indices[i_extrude_next];
1323
1324 duplicate_edges[i_extrude] = int2(new_vert, new_vert_next);
1325
1326 MutableSpan<int> side_face_verts = corner_verts.slice(side_loop_range[i_extrude * 4], 4);
1327 MutableSpan<int> side_face_edges = corner_edges.slice(side_loop_range[i_extrude * 4], 4);
1328 side_face_verts[0] = new_vert_next;
1329 side_face_edges[0] = i_duplicate_edge;
1330 side_face_verts[1] = new_vert;
1331 side_face_edges[1] = connect_edge_range[i_extrude];
1332 side_face_verts[2] = orig_vert;
1333 side_face_edges[2] = orig_edge;
1334 side_face_verts[3] = orig_vert_next;
1335 side_face_edges[3] = connect_edge_range[i_extrude_next];
1336
1337 connect_edges[i_extrude] = int2(orig_vert, new_vert);
1338 }
1339 });
1340
1341 /* New vertices copy the attributes from their original vertices. */
1343 mesh, ids_by_domain[int(AttrDomain::Point)], new_vert_indices, new_vert_range);
1344
1345 /* The data for the duplicate edge is simply a copy of the original edge's data. */
1346 gather_attributes(attributes,
1347 ids_by_domain[int(AttrDomain::Edge)],
1348 duplicate_edge_indices,
1349 duplicate_edge_range);
1350
1351 /* For extruded edges, mix the data from the two neighboring original edges of the face. */
1352 if (!ids_by_domain[int(AttrDomain::Edge)].is_empty()) {
1353 Array<int2> neighbor_edges(connect_edge_range.size());
1354 face_selection.foreach_index(
1355 GrainSize(1024), [&](const int64_t index, const int64_t i_selection) {
1356 const IndexRange face = faces[index];
1357 const IndexRange extrude_range = group_per_face[i_selection];
1358
1359 for (const int i : face.index_range()) {
1360 const int i_prev = (i == 0) ? face.size() - 1 : i - 1;
1361 const int i_extrude = extrude_range[i];
1362 const int i_extrude_prev = extrude_range[i_prev];
1363 neighbor_edges[i_extrude] = int2(duplicate_edge_indices[i_extrude],
1364 duplicate_edge_indices[i_extrude_prev]);
1365 }
1366 });
1367
1368 for (const StringRef id : ids_by_domain[int(AttrDomain::Edge)]) {
1369 GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
1370 bke::attribute_math::convert_to_static_type(attribute.span.type(), [&](auto dummy) {
1371 using T = decltype(dummy);
1372 MutableSpan<T> data = attribute.span.typed<T>();
1373 MutableSpan<T> dst = data.slice(connect_edge_range);
1374 threading::parallel_for(dst.index_range(), 1024, [&](const IndexRange range) {
1375 for (const int i : range) {
1376 const int2 neighbors = neighbor_edges[i];
1377 if constexpr (std::is_same_v<T, bool>) {
1378 /* Propagate selections with "or" instead of "at least half". */
1379 dst[i] = data[neighbors[0]] || data[neighbors[1]];
1380 }
1381 else {
1382 dst[i] = bke::attribute_math::mix2(0.5f, data[neighbors[0]], data[neighbors[1]]);
1383 }
1384 }
1385 });
1386 });
1387 attribute.finish();
1388 }
1389 }
1390
1391 /* Each side face gets the values from the corresponding new face. */
1392 for (const StringRef id : ids_by_domain[int(AttrDomain::Face)]) {
1393 GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
1394 bke::attribute_math::gather_to_groups(
1395 group_per_face, face_selection, attribute.span, attribute.span.slice(side_face_range));
1396 attribute.finish();
1397 }
1398
1399 /* Each corner on a side face gets its value from the matching corner on an extruded face. */
1400 if (!ids_by_domain[int(AttrDomain::Corner)].is_empty()) {
1401 Array<int> orig_corners(side_loop_range.size());
1402 face_selection.foreach_index(
1403 GrainSize(256), [&](const int64_t index, const int64_t i_selection) {
1404 const IndexRange face = faces[index];
1405 const IndexRange extrude_range = group_per_face[i_selection];
1406
1407 for (const int i : face.index_range()) {
1408 const IndexRange side_face(extrude_range[i] * 4, 4);
1409 /* The two corners on each side of the side face get the data from the
1410 * matching corners of the extruded face. This order depends on the loop
1411 * filling the loop indices. */
1412 const int corner = face[i];
1413 const int next_corner = bke::mesh::face_corner_next(face, corner);
1414 orig_corners[side_face[0]] = next_corner;
1415 orig_corners[side_face[1]] = corner;
1416 orig_corners[side_face[2]] = corner;
1417 orig_corners[side_face[3]] = next_corner;
1418 }
1419 });
1421 attributes, ids_by_domain[int(AttrDomain::Corner)], orig_corners, side_loop_range);
1422 }
1423
1424 /* Offset the new vertices. */
1425 face_selection.foreach_index(GrainSize(1025),
1426 [&](const int64_t index, const int64_t i_selection) {
1427 const IndexRange extrude_range = group_per_face[i_selection];
1428 for (const int i : extrude_range) {
1429 const int src_vert = new_vert_indices[i];
1430 new_positions[i] = positions[src_vert] + face_offset[index];
1431 }
1432 });
1433
1434 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Point)) {
1435 array_utils::gather(
1436 indices->as_span(), new_vert_indices.as_span(), indices->slice(new_vert_range));
1437 }
1438 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Edge)) {
1439 indices->slice(connect_edge_range).fill(ORIGINDEX_NONE);
1440 array_utils::gather(indices->as_span(),
1441 duplicate_edge_indices.as_span(),
1442 indices->slice(duplicate_edge_range));
1443 }
1444 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Face)) {
1445 array_utils::gather_to_groups(
1446 group_per_face, face_selection, indices->as_span(), indices->slice(side_face_range));
1447 }
1448
1449 if (attribute_outputs.top_id) {
1451 attributes, *attribute_outputs.top_id, AttrDomain::Face, face_selection);
1452 }
1453 if (attribute_outputs.side_id) {
1455 attributes, *attribute_outputs.side_id, AttrDomain::Face, side_face_range);
1456 }
1457
1459}
1460
1462{
1463 GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh");
1464 Field<bool> selection = params.extract_input<Field<bool>>("Selection");
1465 Field<float3> offset_field = params.extract_input<Field<float3>>("Offset");
1466 Field<float> scale_field = params.extract_input<Field<float>>("Offset Scale");
1467 const NodeGeometryExtrudeMesh &storage = node_storage(params.node());
1469
1470 /* Create a combined field from the offset and the scale so the field evaluator
1471 * can take care of the multiplication and to simplify each extrude function. */
1472 static auto multiply_fn = mf::build::SI2_SO<float3, float, float3>(
1473 "Scale",
1474 [](const float3 &offset, const float scale) { return offset * scale; },
1475 mf::build::exec_presets::AllSpanOrSingle());
1476 const Field<float3> final_offset{
1477 FieldOperation::Create(multiply_fn, {std::move(offset_field), std::move(scale_field)})};
1478
1479 AttributeOutputs attribute_outputs;
1480 attribute_outputs.top_id = params.get_output_anonymous_attribute_id_if_needed("Top");
1481 attribute_outputs.side_id = params.get_output_anonymous_attribute_id_if_needed("Side");
1482
1483 const bool extrude_individual = mode == GEO_NODE_EXTRUDE_MESH_FACES &&
1484 params.extract_input<bool>("Individual");
1485
1486 const NodeAttributeFilter &attribute_filter = params.get_attribute_filter("Mesh");
1487
1488 geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
1489 if (Mesh *mesh = geometry_set.get_mesh_for_write()) {
1490
1491 switch (mode) {
1494 *mesh, selection, final_offset, attribute_outputs, attribute_filter);
1495 break;
1497 extrude_mesh_edges(*mesh, selection, final_offset, attribute_outputs, attribute_filter);
1498 break;
1500 if (extrude_individual) {
1502 *mesh, selection, final_offset, attribute_outputs, attribute_filter);
1503 }
1504 else {
1506 *mesh, selection, final_offset, attribute_outputs, attribute_filter);
1507 }
1508 break;
1509 }
1510 }
1511
1512 geometry::debug_randomize_mesh_order(mesh);
1513 }
1514 });
1515
1516 params.set_output("Mesh", std::move(geometry_set));
1517}
1518
1519static void node_rna(StructRNA *srna)
1520{
1521 static const EnumPropertyItem mode_items[] = {
1522 {GEO_NODE_EXTRUDE_MESH_VERTICES, "VERTICES", 0, "Vertices", ""},
1523 {GEO_NODE_EXTRUDE_MESH_EDGES, "EDGES", 0, "Edges", ""},
1524 {GEO_NODE_EXTRUDE_MESH_FACES, "FACES", 0, "Faces", ""},
1525 {0, nullptr, 0, nullptr, nullptr},
1526 };
1527
1528 RNA_def_node_enum(srna,
1529 "mode",
1530 "Mode",
1531 "",
1532 mode_items,
1535}
1536
1537static void node_register()
1538{
1539 static blender::bke::bNodeType ntype;
1540 geo_node_type_base(&ntype, GEO_NODE_EXTRUDE_MESH, "Extrude Mesh", NODE_CLASS_GEOMETRY);
1541 ntype.declare = node_declare;
1542 ntype.initfunc = node_init;
1545 &ntype, "NodeGeometryExtrudeMesh", node_free_standard_storage, node_copy_standard_storage);
1546 ntype.draw_buttons = node_layout;
1548
1549 node_rna(ntype.rna_ext.srna);
1550}
1551NOD_REGISTER_NODE(node_register)
1552
1553} // namespace blender::nodes::node_geo_extrude_mesh_cc
#define ATTR_DOMAIN_NUM
CustomData interface, see also DNA_customdata_types.h.
void CustomData_realloc(CustomData *data, int old_size, int new_size, eCDAllocType alloctype=CD_CONSTRUCT)
void CustomData_free_layers(CustomData *data, eCustomDataType type, int totelem)
#define ORIGINDEX_NONE
void * CustomData_get_layer_for_write(CustomData *data, eCustomDataType type, int totelem)
support for deformation groups and hooks.
void BKE_mesh_runtime_clear_cache(Mesh *mesh)
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1799
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:418
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
#define LISTBASE_FOREACH(type, var, list)
#define BLT_I18NCONTEXT_ID_NODETREE
@ CD_MLOOPTANGENT
@ CD_MVERT_SKIN
@ CD_CUSTOMLOOPNORMAL
@ CD_PROP_INT32_2D
@ CD_PROP_INT32
@ CD_FREESTYLE_EDGE
@ CD_FREESTYLE_FACE
@ CD_GRID_PAINT_MASK
@ CD_CLOTH_ORCO
@ CD_PROP_STRING
GeometryNodeExtrudeMeshMode
@ GEO_NODE_EXTRUDE_MESH_FACES
@ GEO_NODE_EXTRUDE_MESH_VERTICES
@ GEO_NODE_EXTRUDE_MESH_EDGES
#define NOD_REGISTER_NODE(REGISTER_FUNC)
#define NOD_storage_enum_accessors(member)
@ PROP_TRANSLATION
Definition RNA_types.hh:164
void uiLayoutSetPropSep(uiLayout *layout, bool is_sep)
#define UI_ITEM_NONE
void uiLayoutSetPropDecorate(uiLayout *layout, bool is_sep)
void uiItemR(uiLayout *layout, PointerRNA *ptr, const char *propname, eUI_Item_Flag flag, const char *name, int icon)
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:237
IndexRange index_range() const
Definition BLI_array.hh:349
void reinitialize(const int64_t new_size)
Definition BLI_array.hh:388
GMutableSpan slice(const int64_t start, int64_t size) const
const CPPType & type() const
constexpr int64_t size() const
constexpr IndexRange after(int64_t n) const
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:574
constexpr bool is_empty() const
Definition BLI_span.hh:510
constexpr IndexRange index_range() const
Definition BLI_span.hh:671
bool contains(const Key &key) const
Definition BLI_set.hh:291
bool add(const Key &key)
Definition BLI_set.hh:248
bool is_empty() const
Definition BLI_set.hh:572
int64_t remove_if(Predicate &&predicate)
Definition BLI_set.hh:496
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:138
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr IndexRange index_range() const
Definition BLI_span.hh:402
int64_t index_of(const Key &key) const
void reserve(const int64_t n)
bool add(const Key &key)
void add_new(const Key &key)
int64_t index_of_try(const Key &key) const
IndexRange index_range() const
int64_t size() const
void append(const T &value)
void set_selection(Field< bool > selection)
Definition FN_field.hh:385
int add_with_destination(GField field, GVMutableArray dst)
Definition field.cc:743
void to_indices(MutableSpan< T > r_indices) const
void foreach_index_optimized(Fn &&fn) const
IndexMask slice(IndexRange range) const
void to_bools(MutableSpan< bool > r_bools) const
void foreach_index(Fn &&fn) const
void make_available(bNode &node) const
local_group_size(16, 16) .push_constant(Type b
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
static char faces[256]
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
typename DefaultPropagationMixerStruct< T >::type DefaultPropagationMixer
void gather_attributes(AttributeAccessor src_attributes, AttrDomain src_domain, AttrDomain dst_domain, const AttributeFilter &attribute_filter, const IndexMask &selection, MutableAttributeAccessor dst_attributes)
void node_type_storage(bNodeType *ntype, const char *storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:4632
void node_register_type(bNodeType *ntype)
Definition node.cc:1708
void resize_trivial_array(T **data, const ImplicitSharingInfo **sharing_info, int64_t old_size, int64_t new_size)
void normal(const bNode &, void *r_value)
static void node_declare(NodeDeclarationBuilder &b)
static void extrude_individual_mesh_faces(Mesh &mesh, const Field< bool > &selection_field, const Field< float3 > &offset_field, const AttributeOutputs &attribute_outputs, const AttributeFilter &attribute_filter)
static void extrude_mesh_face_regions(Mesh &mesh, const Field< bool > &selection_field, const Field< float3 > &offset_field, const AttributeOutputs &attribute_outputs, const AttributeFilter &attribute_filter)
static void extrude_mesh_edges(Mesh &mesh, const Field< bool > &selection_field, const Field< float3 > &offset_field, const AttributeOutputs &attribute_outputs, const AttributeFilter &attribute_filter)
std::array< Vector< StringRef >, ATTR_DOMAIN_NUM > IDsByDomain
static void gather_vert_attributes(Mesh &mesh, const Span< StringRef > ids, const Span< int > indices, const IndexRange new_range)
static IDsByDomain attribute_ids_by_domain(const AttributeAccessor attributes, const Set< StringRef > &skip)
static void node_layout(uiLayout *layout, bContext *, PointerRNA *ptr)
static VectorSet< int > vert_indices_from_edges(const Mesh &mesh, const Span< int > edge_indices)
static bool is_empty_domain(const AttributeAccessor attributes, const Set< StringRef > &skip, const AttrDomain domain)
static CustomData & mesh_custom_data_for_domain(Mesh &mesh, const AttrDomain domain)
static void extrude_mesh_vertices(Mesh &mesh, const Field< bool > &selection_field, const Field< float3 > &offset_field, const AttributeOutputs &attribute_outputs, const AttributeFilter &attribute_filter)
static void remove_non_propagated_attributes(MutableAttributeAccessor attributes, const AttributeFilter &attribute_filter)
static std::optional< MutableSpan< int > > get_orig_index_layer(Mesh &mesh, const AttrDomain domain)
static void save_selection_as_attribute(MutableAttributeAccessor attributes, const StringRef id, const AttrDomain domain, const IndexMask &selection)
static void fill_quad_consistent_direction(const Span< int > other_face_verts, const Span< int > other_face_edges, MutableSpan< int > new_corner_verts, MutableSpan< int > new_corner_edges, const int vert_connected_to_face_1, const int vert_connected_to_face_2, const int vert_across_from_face_1, const int vert_across_from_face_2, const int edge_connected_to_face, const int connecting_edge_1, const int edge_across_from_face, const int connecting_edge_2)
static void node_geo_exec(GeoNodeExecParams params)
void copy_with_mixing(const Span< T > src, const GroupedSpan< int > src_groups, const IndexMask &selection, MutableSpan< T > dst)
static void expand_mesh(Mesh &mesh, const int vert_expand, const int edge_expand, const int face_expand, const int loop_expand)
static void node_init(bNodeTree *, bNode *node)
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:95
void geo_node_type_base(blender::bke::bNodeType *ntype, int type, const char *name, short nclass)
void node_free_standard_storage(bNode *node)
Definition node_util.cc:46
void node_copy_standard_storage(bNodeTree *, bNode *dest_node, const bNode *src_node)
Definition node_util.cc:58
__int64 int64_t
Definition stdint.h:89
StructRNA * srna
Definition RNA_types.hh:780
bool allow_skip(const StringRef name) const
void modify_geometry_sets(ForeachSubGeometryCallback callback)
Defines a node type.
Definition BKE_node.hh:218
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:267
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:339
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:238
NodeDeclareFunction declare
Definition BKE_node.hh:347
PointerRNA * ptr
Definition wm_files.cc:4126