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