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