Blender V5.0
grease_pencil_edit.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
8
9#include "BLI_array.hh"
10#include "BLI_array_utils.hh"
11#include "BLI_assert.h"
13#include "BLI_index_mask.hh"
14#include "BLI_index_range.hh"
15#include "BLI_listbase.h"
16#include "BLI_math_base.hh"
17#include "BLI_math_matrix.hh"
18#include "BLI_math_vector.hh"
20#include "BLI_offset_indices.hh"
21#include "BLI_span.hh"
22#include "BLI_string.h"
23#include "BLI_string_utf8.h"
24#include "BLI_utildefines.h"
25#include "BLI_vector.hh"
26#include "BLT_translation.hh"
27
28#include "DNA_anim_types.h"
29#include "DNA_array_utils.hh"
31#include "DNA_material_types.h"
32#include "DNA_object_types.h"
33#include "DNA_scene_types.h"
34#include "DNA_space_types.h"
35#include "DNA_view3d_types.h"
37
38#include "BKE_anim_data.hh"
39#include "BKE_animsys.h"
40#include "BKE_attribute.hh"
42#include "BKE_context.hh"
43#include "BKE_curves_utils.hh"
44#include "BKE_customdata.hh"
45#include "BKE_deform.hh"
46#include "BKE_fcurve_driver.h"
47#include "BKE_grease_pencil.hh"
48#include "BKE_instances.hh"
49#include "BKE_lib_id.hh"
50#include "BKE_main.hh"
51#include "BKE_material.hh"
52#include "BKE_preview_image.hh"
53#include "BKE_report.hh"
54#include "BKE_scene.hh"
55
56#include "RNA_access.hh"
57#include "RNA_define.hh"
58#include "RNA_enum_types.hh"
59
60#include "DEG_depsgraph.hh"
62
63#include "ED_curves.hh"
64#include "ED_grease_pencil.hh"
65#include "ED_object.hh"
67#include "ED_view3d.hh"
68
70#include "GEO_fit_curves.hh"
73#include "GEO_reorder.hh"
75#include "GEO_set_curve_type.hh"
77#include "GEO_smooth_curves.hh"
79
80#include "UI_interface_c.hh"
82
83#include "UI_resources.hh"
84#include <limits>
85
87
88/* -------------------------------------------------------------------- */
91
93{
94 const Scene *scene = CTX_data_scene(C);
96 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
97
98 const int iterations = RNA_int_get(op->ptr, "iterations");
99 const float influence = RNA_float_get(op->ptr, "factor");
100 const bool keep_shape = RNA_boolean_get(op->ptr, "keep_shape");
101 const bool smooth_ends = RNA_boolean_get(op->ptr, "smooth_ends");
102
103 const bool smooth_position = RNA_boolean_get(op->ptr, "smooth_position");
104 const bool smooth_radius = RNA_boolean_get(op->ptr, "smooth_radius");
105 const bool smooth_opacity = RNA_boolean_get(op->ptr, "smooth_opacity");
106
107 if (!(smooth_position || smooth_radius || smooth_opacity)) {
108 /* There's nothing to be smoothed, return. */
109 return OPERATOR_FINISHED;
110 }
111
112 bool changed = false;
113 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
114 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
116 if (curves.is_empty()) {
117 return;
118 }
119
120 IndexMaskMemory memory;
122 *object, info.drawing, info.layer_index, memory);
123 if (strokes.is_empty()) {
124 return;
125 }
126
127 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
128 const OffsetIndices points_by_curve = curves.points_by_curve();
129 const VArray<bool> cyclic = curves.cyclic();
130 const VArray<bool> point_selection = *curves.attributes().lookup_or_default<bool>(
131 ".selection", bke::AttrDomain::Point, true);
132
133 if (smooth_position) {
134 bke::GSpanAttributeWriter positions = attributes.lookup_for_write_span("position");
136 points_by_curve,
137 point_selection,
138 cyclic,
139 iterations,
140 influence,
141 smooth_ends,
142 keep_shape,
143 positions.span);
144 positions.finish();
145 changed = true;
146 }
147 if (smooth_opacity && info.drawing.opacities().is_span()) {
148 bke::GSpanAttributeWriter opacities = attributes.lookup_for_write_span("opacity");
150 points_by_curve,
151 point_selection,
152 cyclic,
153 iterations,
154 influence,
155 smooth_ends,
156 false,
157 opacities.span);
158 opacities.finish();
159 changed = true;
160 }
161 if (smooth_radius && info.drawing.radii().is_span()) {
162 bke::GSpanAttributeWriter radii = attributes.lookup_for_write_span("radius");
164 points_by_curve,
165 point_selection,
166 cyclic,
167 iterations,
168 influence,
169 smooth_ends,
170 false,
171 radii.span);
172 radii.finish();
173 changed = true;
174 }
175 });
176
177 if (changed) {
178 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
179 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
180 }
181
182 return OPERATOR_FINISHED;
183}
184
186{
187 PropertyRNA *prop;
188
189 ot->name = "Smooth Stroke";
190 ot->idname = "GREASE_PENCIL_OT_stroke_smooth";
191 ot->description = "Smooth selected strokes";
192
195
197
198 prop = RNA_def_int(ot->srna, "iterations", 10, 1, 100, "Iterations", "", 1, 30);
200 RNA_def_float(ot->srna, "factor", 1.0f, 0.0f, 1.0f, "Factor", "", 0.0f, 1.0f);
201 RNA_def_boolean(ot->srna, "smooth_ends", false, "Smooth Endpoints", "");
202 RNA_def_boolean(ot->srna, "keep_shape", false, "Keep Shape", "");
203
204 RNA_def_boolean(ot->srna, "smooth_position", true, "Position", "");
205 RNA_def_boolean(ot->srna, "smooth_radius", true, "Radius", "");
206 RNA_def_boolean(ot->srna, "smooth_opacity", false, "Opacity", "");
207}
208
210
211/* -------------------------------------------------------------------- */
214
215enum class SimplifyMode {
216 FIXED = 0,
219 MERGE = 3,
220};
221
224 "FIXED",
225 0,
226 "Fixed",
227 "Delete alternating vertices in the stroke, except extremes"},
229 "ADAPTIVE",
230 0,
231 "Adaptive",
232 "Use a Ramer-Douglas-Peucker algorithm to simplify the stroke preserving main shape"},
234 "SAMPLE",
235 0,
236 "Sample",
237 "Re-sample the stroke with segments of the specified length"},
239 "MERGE",
240 0,
241 "Merge",
242 "Simplify the stroke by merging vertices closer than a given distance"},
243 {0, nullptr, 0, nullptr, nullptr},
244};
245
247 const int step,
248 const IndexMask &stroke_selection,
249 IndexMaskMemory &memory)
250{
251 const OffsetIndices points_by_curve = curves.points_by_curve();
252 const Array<int> point_to_curve_map = curves.point_to_curve_map();
253
254 const IndexMask selected_points = IndexMask::from_ranges(
255 points_by_curve, stroke_selection, memory);
256
257 /* Find points to keep among selected points. */
258 const IndexMask selected_to_keep = IndexMask::from_predicate(
259 selected_points, GrainSize(2048), memory, [&](const int64_t i) {
260 const int curve_i = point_to_curve_map[i];
261 const IndexRange points = points_by_curve[curve_i];
262 if (points.size() <= 2) {
263 return true;
264 }
265 const int local_i = i - points.start();
266 return (local_i % int(math::pow(2.0f, float(step))) == 0) || points.last() == i;
267 });
268
269 /* All the points that are not selected are also kept. */
271 {selected_to_keep, selected_points.complement(curves.points_range(), memory)}, memory);
272}
273
275{
276 const Scene *scene = CTX_data_scene(C);
278 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
279
280 const SimplifyMode mode = SimplifyMode(RNA_enum_get(op->ptr, "mode"));
281
282 bool changed = false;
283 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
284 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
286 if (curves.is_empty()) {
287 return;
288 }
289
290 IndexMaskMemory memory;
292 *object, info.drawing, info.layer_index, memory);
293 if (strokes.is_empty()) {
294 return;
295 }
296
297 switch (mode) {
298 case SimplifyMode::FIXED: {
299 const int steps = RNA_int_get(op->ptr, "steps");
300 const IndexMask points_to_keep = simplify_fixed(curves, steps, strokes, memory);
301 if (points_to_keep.is_empty()) {
302 info.drawing.strokes_for_write() = {};
303 break;
304 }
305 if (points_to_keep.size() == curves.points_num()) {
306 break;
307 }
309 curves, points_to_keep, {});
311 changed = true;
312 break;
313 }
315 const float simplify_factor = RNA_float_get(op->ptr, "factor");
316 const IndexMask points_to_delete = geometry::simplify_curve_attribute(
317 curves.positions(),
318 strokes,
319 curves.points_by_curve(),
320 curves.cyclic(),
321 simplify_factor,
322 curves.positions(),
323 memory);
324 info.drawing.strokes_for_write().remove_points(points_to_delete, {});
326 changed = true;
327 break;
328 }
330 const float resample_length = RNA_float_get(op->ptr, "length");
332 curves, strokes, VArray<float>::from_single(resample_length, curves.curves_num()), {});
334 changed = true;
335 break;
336 }
337 case SimplifyMode::MERGE: {
338 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
339 const Array<int> point_to_curve_map = curves.point_to_curve_map();
340 const float merge_distance = RNA_float_get(op->ptr, "distance");
341 const IndexMask selected_points = IndexMask::from_ranges(points_by_curve, strokes, memory);
342 const IndexMask filtered_points = IndexMask::from_predicate(
343 selected_points, GrainSize(2048), memory, [&](const int64_t i) {
344 const int curve_i = point_to_curve_map[i];
345 const IndexRange points = points_by_curve[curve_i];
346 if (points.drop_front(1).drop_back(1).contains(i)) {
347 return true;
348 }
349 return false;
350 });
352 curves, merge_distance, filtered_points, {});
354 changed = true;
355 break;
356 }
357 default:
358 break;
359 }
360 });
361
362 if (changed) {
363 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
364 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
365 }
366 return OPERATOR_FINISHED;
367}
368
370{
371 uiLayout *layout = op->layout;
373
375
376 layout->use_property_split_set(true);
377 layout->use_property_decorate_set(false);
378
379 layout->prop(&ptr, "mode", UI_ITEM_NONE, std::nullopt, ICON_NONE);
380
381 const SimplifyMode mode = SimplifyMode(RNA_enum_get(op->ptr, "mode"));
382
383 switch (mode) {
385 layout->prop(&ptr, "steps", UI_ITEM_NONE, std::nullopt, ICON_NONE);
386 break;
388 layout->prop(&ptr, "factor", UI_ITEM_NONE, std::nullopt, ICON_NONE);
389 break;
391 layout->prop(&ptr, "length", UI_ITEM_NONE, std::nullopt, ICON_NONE);
392 break;
394 layout->prop(&ptr, "distance", UI_ITEM_NONE, std::nullopt, ICON_NONE);
395 break;
396 default:
397 break;
398 }
399}
400
402{
403 PropertyRNA *prop;
404
405 ot->name = "Simplify Stroke";
406 ot->idname = "GREASE_PENCIL_OT_stroke_simplify";
407 ot->description = "Simplify selected strokes";
408
411
413
415
416 prop = RNA_def_float(ot->srna, "factor", 0.01f, 0.0f, 100.0f, "Factor", "", 0.0f, 100.0f);
418 prop = RNA_def_float(ot->srna, "length", 0.05f, 0.01f, 100.0f, "Length", "", 0.01f, 1.0f);
420 prop = RNA_def_float(ot->srna, "distance", 0.01f, 0.0f, 100.0f, "Distance", "", 0.0f, 1.0f);
422 prop = RNA_def_int(ot->srna, "steps", 1, 0, 50, "Steps", "", 0.0f, 10);
424 prop = RNA_def_enum(ot->srna,
425 "mode",
427 0,
428 "Mode",
429 "Method used for simplifying stroke points");
431}
432
434
435/* -------------------------------------------------------------------- */
438
440{
441 const Scene *scene = CTX_data_scene(C);
443 View3D *v3d = CTX_wm_view3d(C);
444 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
445
447 scene->toolsettings);
448
449 bool changed = false;
450 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
451 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
453
454 IndexMaskMemory memory;
455 if (selection_domain == bke::AttrDomain::Curve) {
457 *object, info.drawing, info.layer_index, memory);
458 if (strokes.is_empty()) {
459 return;
460 }
461 curves.remove_curves(strokes, {});
462 }
463 else if (selection_domain == bke::AttrDomain::Point) {
465 *object, info.drawing, info.layer_index, v3d->overlay.handle_display, memory);
466 if (points.is_empty()) {
467 return;
468 }
470 }
472 changed = true;
473 });
474
475 if (changed) {
476 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
477 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
478 }
479 return OPERATOR_FINISHED;
480}
481
483{
484 ot->name = "Delete";
485 ot->idname = "GREASE_PENCIL_OT_delete";
486 ot->description = "Delete selected strokes or points";
487
490
492}
493
495
496/* -------------------------------------------------------------------- */
499
500enum class DissolveMode : int8_t {
507};
508
510 {int(DissolveMode::POINTS), "POINTS", 0, "Dissolve", "Dissolve selected points"},
512 "BETWEEN",
513 0,
514 "Dissolve Between",
515 "Dissolve points between selected points"},
517 "UNSELECT",
518 0,
519 "Dissolve Unselect",
520 "Dissolve all unselected points"},
521 {0, nullptr, 0, nullptr, nullptr},
522};
523
525 const IndexMask &mask,
526 const DissolveMode mode)
527{
528 Array<bool> points_to_dissolve(curves.points_num());
529 mask.to_bools(points_to_dissolve);
530
531 if (mode == DissolveMode::POINTS) {
532 return points_to_dissolve;
533 }
534
535 /* Both `between` and `unselect` have the unselected point being the ones dissolved so we need
536 * to invert. */
538
539 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
540 /* Because we are going to invert, these become the points to keep. */
541 MutableSpan<bool> points_to_keep = points_to_dissolve.as_mutable_span();
542
543 threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) {
544 for (const int64_t curve_i : range) {
545 const IndexRange points = points_by_curve[curve_i];
546 const Span<bool> curve_selection = points_to_dissolve.as_span().slice(points);
547 /* The unselected curves should not be dissolved. */
548 if (!curve_selection.contains(true)) {
549 points_to_keep.slice(points).fill(true);
550 continue;
551 }
552
553 /* `between` is just `unselect` but with the first and last segments not getting
554 * dissolved. */
555 if (mode != DissolveMode::BETWEEN) {
556 continue;
557 }
558
559 const Vector<IndexRange> deselection_ranges = array_utils::find_all_ranges(curve_selection,
560 false);
561
562 if (deselection_ranges.size() != 0) {
563 const IndexRange first_range = deselection_ranges.first().shift(points.first());
564 const IndexRange last_range = deselection_ranges.last().shift(points.first());
565
566 /* Ranges should only be fill if the first/last point matches the start/end point
567 * of the segment. */
568 if (first_range.first() == points.first()) {
569 points_to_keep.slice(first_range).fill(true);
570 }
571 if (last_range.last() == points.last()) {
572 points_to_keep.slice(last_range).fill(true);
573 }
574 }
575 }
576 });
577
578 array_utils::invert_booleans(points_to_dissolve);
579
580 return points_to_dissolve;
581}
582
584{
585 const Scene *scene = CTX_data_scene(C);
587 View3D *v3d = CTX_wm_view3d(C);
588 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
589
590 const DissolveMode mode = DissolveMode(RNA_enum_get(op->ptr, "type"));
591
592 bool changed = false;
593 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
594 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
596 if (curves.is_empty()) {
597 return;
598 }
599
600 IndexMaskMemory memory;
602 *object, info.drawing, info.layer_index, v3d->overlay.handle_display, memory);
603 if (points.is_empty()) {
604 return;
605 }
606
607 const Array<bool> points_to_dissolve = get_points_to_dissolve(curves, points, mode);
608 if (points_to_dissolve.as_span().contains(true)) {
609 curves.remove_points(IndexMask::from_bools(points_to_dissolve, memory), {});
611 changed = true;
612 }
613 });
614
615 if (changed) {
616 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
617 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
618 }
619 return OPERATOR_FINISHED;
620}
621
623{
624 PropertyRNA *prop;
625
626 ot->name = "Dissolve";
627 ot->idname = "GREASE_PENCIL_OT_dissolve";
628 ot->description = "Delete selected points without splitting strokes";
629
630 ot->invoke = WM_menu_invoke;
633
635
636 ot->prop = prop = RNA_def_enum(ot->srna,
637 "type",
639 0,
640 "Type",
641 "Method used for dissolving stroke points");
644}
645
647
648/* -------------------------------------------------------------------- */
651
652enum class DeleteFrameMode : int8_t {
657};
658
661 "ACTIVE_FRAME",
662 0,
663 "Active Frame",
664 "Deletes current frame in the active layer"},
666 "ALL_FRAMES",
667 0,
668 "All Active Frames",
669 "Delete active frames for all layers"},
670 {0, nullptr, 0, nullptr, nullptr},
671};
672
674{
675 const Scene *scene = CTX_data_scene(C);
677 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
678 const int current_frame = scene->r.cfra;
679
680 const DeleteFrameMode mode = DeleteFrameMode(RNA_enum_get(op->ptr, "type"));
681
682 bool changed = false;
683 if (mode == DeleteFrameMode::ACTIVE_FRAME && grease_pencil.has_active_layer()) {
684 bke::greasepencil::Layer &layer = *grease_pencil.get_active_layer();
685 if (layer.is_editable() && layer.start_frame_at(current_frame)) {
686 changed |= grease_pencil.remove_frames(layer, {*layer.start_frame_at(current_frame)});
687 }
688 }
689 else if (mode == DeleteFrameMode::ALL_FRAMES) {
690 for (bke::greasepencil::Layer *layer : grease_pencil.layers_for_write()) {
691 if (layer->is_editable() && layer->start_frame_at(current_frame)) {
692 changed |= grease_pencil.remove_frames(*layer, {*layer->start_frame_at(current_frame)});
693 }
694 }
695 }
696
697 if (changed) {
698 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
699 WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_EDITED, &grease_pencil);
701 }
702
703 return OPERATOR_FINISHED;
704}
705
707{
708 PropertyRNA *prop;
709
710 ot->name = "Delete Frame";
711 ot->idname = "GREASE_PENCIL_OT_delete_frame";
712 ot->description = "Delete Grease Pencil Frame(s)";
713
714 ot->invoke = WM_menu_invoke;
717
719
720 ot->prop = prop = RNA_def_enum(ot->srna,
721 "type",
723 0,
724 "Type",
725 "Method used for deleting Grease Pencil frames");
727}
728
729
730/* -------------------------------------------------------------------- */
733
735{
736 using namespace blender;
737 Main *bmain = CTX_data_main(C);
738 const Scene *scene = CTX_data_scene(C);
740 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
741 Material *ma = nullptr;
742 char name[MAX_ID_NAME - 2];
743 RNA_string_get(op->ptr, "material", name);
744
745 int material_index = object->actcol - 1;
746
747 if (name[0] != '\0') {
748 ma = reinterpret_cast<Material *>(BKE_libblock_find_name(bmain, ID_MA, name));
749 if (ma == nullptr) {
750 BKE_reportf(op->reports, RPT_WARNING, TIP_("Material '%s' could not be found"), name);
751 return OPERATOR_CANCELLED;
752 }
753
754 /* Find slot index. */
755 material_index = BKE_object_material_index_get(object, ma);
756 }
757
758 if (material_index == -1) {
759 return OPERATOR_CANCELLED;
760 }
761
762 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
763 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
764 IndexMaskMemory memory;
766 *object, info.drawing, info.layer_index, memory);
767 if (strokes.is_empty()) {
768 return;
769 }
770
773 curves.attributes_for_write().lookup_or_add_for_write_span<int>("material_index",
775 index_mask::masked_fill(materials.span, material_index, strokes);
776 materials.finish();
777 });
778
779 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
780 WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_EDITED, &grease_pencil);
781
782 return OPERATOR_FINISHED;
783}
784
786{
787 ot->name = "Assign Material";
788 ot->idname = "GREASE_PENCIL_OT_stroke_material_set";
789 ot->description = "Assign the active material slot to the selected strokes";
790
793
795
796 ot->prop = RNA_def_string(
797 ot->srna, "material", nullptr, MAX_ID_NAME - 2, "Material", "Name of the material");
799}
800
801
802/* -------------------------------------------------------------------- */
805
806enum class CyclicalMode : int8_t {
808 CLOSE = 0,
810 OPEN = 1,
813};
814
816 {int(CyclicalMode::CLOSE), "CLOSE", 0, "Close All", ""},
817 {int(CyclicalMode::OPEN), "OPEN", 0, "Open All", ""},
818 {int(CyclicalMode::TOGGLE), "TOGGLE", 0, "Toggle", ""},
819 {0, nullptr, 0, nullptr, nullptr},
820};
821
823 const IndexMask &strokes)
824{
825 const VArray<bool> cyclic = curves.cyclic();
826 const Span<float3> positions = curves.positions();
827 curves.ensure_evaluated_lengths();
828
829 Array<int> use_cuts(curves.points_num(), 0);
830 const OffsetIndices points_by_curve = curves.points_by_curve();
831
832 strokes.foreach_index(GrainSize(4096), [&](const int curve_i) {
833 if (cyclic[curve_i]) {
834 const IndexRange points = points_by_curve[curve_i];
835 const float end_distance = math::distance(positions[points.first()],
836 positions[points.last()]);
837
838 /* Because the curve is already cyclical the last segment has to be subtracted. */
839 const float curve_length = curves.evaluated_length_total_for_curve(curve_i, true) -
840 end_distance;
841
842 /* Calculate cuts to match the average density. */
843 const float point_density = float(points.size()) / curve_length;
844 use_cuts[points.last()] = int(point_density * end_distance);
845 }
846 });
847
848 const VArray<int> cuts = VArray<int>::from_span(use_cuts.as_span());
849
850 return geometry::subdivide_curves(curves, strokes, cuts);
851}
852
854{
855 const Scene *scene = CTX_data_scene(C);
857 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
858
859 const CyclicalMode mode = CyclicalMode(RNA_enum_get(op->ptr, "type"));
860 const bool subdivide_cyclic_segment = RNA_boolean_get(op->ptr, "subdivide_cyclic_segment");
861
862 bool changed = false;
863 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
864 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
866 if (mode == CyclicalMode::OPEN && !curves.attributes().contains("cyclic")) {
867 /* Avoid creating unneeded attribute. */
868 return;
869 }
870
871 IndexMaskMemory memory;
873 *object, info.drawing, info.layer_index, memory);
874 if (strokes.is_empty()) {
875 return;
876 }
877
878 MutableSpan<bool> cyclic = curves.cyclic_for_write();
879 switch (mode) {
881 index_mask::masked_fill(cyclic, true, strokes);
882 break;
884 index_mask::masked_fill(cyclic, false, strokes);
885 break;
887 array_utils::invert_booleans(cyclic, strokes);
888 break;
889 }
890
891 /* Remove the attribute if it is empty. */
892 if (mode != CyclicalMode::CLOSE) {
894 curves.attributes_for_write().remove("cyclic");
895 }
896 }
897
898 if (subdivide_cyclic_segment) {
899 /* Update to properly calculate the lengths. */
900 curves.tag_topology_changed();
901
903 }
904
906 changed = true;
907 });
908
909 if (changed) {
910 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
911 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
912 }
913
914 return OPERATOR_FINISHED;
915}
916
918{
919 ot->name = "Set Cyclical State";
920 ot->idname = "GREASE_PENCIL_OT_cyclical_set";
921 ot->description = "Close or open the selected stroke adding a segment from last to first point";
922
923 ot->invoke = WM_menu_invoke;
926
928
929 ot->prop = RNA_def_enum(
930 ot->srna, "type", prop_cyclical_types, int(CyclicalMode::TOGGLE), "Type", "");
931
932 RNA_def_boolean(ot->srna,
933 "subdivide_cyclic_segment",
934 true,
935 "Match Point Density",
936 "Add point in the new segment to keep the same density");
937}
938
940
941/* -------------------------------------------------------------------- */
944
946{
947 const Scene *scene = CTX_data_scene(C);
949 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
950
951 if (object->totcol == 0) {
952 return OPERATOR_CANCELLED;
953 }
954
955 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
956 for (const MutableDrawingInfo &info : drawings) {
957 IndexMaskMemory memory;
959 *object, info.drawing, info.layer_index, memory);
960 if (strokes.is_empty()) {
961 continue;
962 }
963 bke::CurvesGeometry &curves = info.drawing.strokes_for_write();
964
965 const VArray<int> materials = *curves.attributes().lookup_or_default<int>(
966 "material_index", bke::AttrDomain::Curve, 0);
967 object->actcol = materials[strokes.first()] + 1;
968 break;
969 };
970
971 WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_EDITED, &grease_pencil);
972
973 return OPERATOR_FINISHED;
974}
975
977{
978 ot->name = "Set Active Material";
979 ot->idname = "GREASE_PENCIL_OT_set_active_material";
980 ot->description = "Set the selected stroke material as the active material";
981
984
986}
987
988
989/* -------------------------------------------------------------------- */
992
994{
995 const Scene *scene = CTX_data_scene(C);
997 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
998
999 /* Radius is half of the thickness. */
1000 const float radius = RNA_float_get(op->ptr, "thickness") * 0.5f;
1001
1002 bool changed = false;
1003 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1004 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1005 IndexMaskMemory memory;
1007 *object, info.drawing, info.layer_index, memory);
1008 if (strokes.is_empty()) {
1009 return;
1010 }
1012
1013 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
1015 bke::curves::fill_points<float>(points_by_curve, strokes, radius, radii);
1016 changed = true;
1017 });
1018
1019 if (changed) {
1020 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1021 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1022 }
1023
1024 return OPERATOR_FINISHED;
1025}
1026
1028{
1029 ot->name = "Set Uniform Thickness";
1030 ot->idname = "GREASE_PENCIL_OT_set_uniform_thickness";
1031 ot->description = "Set all stroke points to same thickness";
1032
1035
1036 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1037
1038 ot->prop = RNA_def_float(
1039 ot->srna, "thickness", 0.1f, 0.0f, 1000.0f, "Thickness", "Thickness", 0.0f, 1000.0f);
1040}
1041
1043/* -------------------------------------------------------------------- */
1046
1048{
1049 using namespace blender::bke;
1050
1051 const Scene *scene = CTX_data_scene(C);
1052 Object *object = CTX_data_active_object(C);
1053 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1054
1055 const float opacity_stroke = RNA_float_get(op->ptr, "opacity_stroke");
1056 const float opacity_fill = RNA_float_get(op->ptr, "opacity_fill");
1057
1058 bool changed = false;
1059 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1060 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1061 IndexMaskMemory memory;
1063 *object, info.drawing, info.layer_index, memory);
1064 if (strokes.is_empty()) {
1065 return;
1066 }
1068 MutableAttributeAccessor attributes = curves.attributes_for_write();
1069 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
1070
1072 bke::curves::fill_points<float>(points_by_curve, strokes, opacity_stroke, opacities);
1073
1074 if (SpanAttributeWriter<float> fill_opacities = attributes.lookup_or_add_for_write_span<float>(
1075 "fill_opacity",
1078 {
1079 strokes.foreach_index(GrainSize(2048), [&](const int64_t curve) {
1080 fill_opacities.span[curve] = opacity_fill;
1081 });
1082 fill_opacities.finish();
1083 }
1084
1085 changed = true;
1086 });
1087
1088 if (changed) {
1089 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1090 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1091 }
1092
1093 return OPERATOR_FINISHED;
1094}
1095
1097{
1098 ot->name = "Set Uniform Opacity";
1099 ot->idname = "GREASE_PENCIL_OT_set_uniform_opacity";
1100 ot->description = "Set all stroke points to same opacity";
1101
1104
1105 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1106
1107 /* Differentiate default opacities for stroke & fills so shapes with same stroke+fill colors will
1108 * be more readable. */
1109 RNA_def_float(ot->srna, "opacity_stroke", 1.0f, 0.0f, 1.0f, "Stroke Opacity", "", 0.0f, 1.0f);
1110 RNA_def_float(ot->srna, "opacity_fill", 0.5f, 0.0f, 1.0f, "Fill Opacity", "", 0.0f, 1.0f);
1111}
1112
1114
1115/* -------------------------------------------------------------------- */
1118
1120 wmOperator * /*op*/)
1121{
1122 const Scene *scene = CTX_data_scene(C);
1123 Object *object = CTX_data_active_object(C);
1124 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1125
1126 bool changed = false;
1127 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1128 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1129 IndexMaskMemory memory;
1131 *object, info.drawing, info.layer_index, memory);
1132 if (strokes.is_empty()) {
1133 return;
1134 }
1136
1137 /* Switch stroke direction. */
1138 curves.reverse_curves(strokes);
1139
1140 changed = true;
1141 });
1142
1143 if (changed) {
1144 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1145 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1146 }
1147
1148 return OPERATOR_FINISHED;
1149}
1150
1152{
1153 /* identifiers */
1154 ot->name = "Switch Direction";
1155 ot->idname = "GREASE_PENCIL_OT_stroke_switch_direction";
1156 ot->description = "Change direction of the points of the selected strokes";
1157
1158 /* Callbacks. */
1161
1162 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1163}
1164
1166/* -------------------------------------------------------------------- */
1170 const IndexMask &mask)
1171{
1172
1173 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
1174 const VArray<bool> src_cyclic = curves.cyclic();
1175
1176 /* Early-return if no cyclic curves. */
1178 return curves;
1179 }
1180
1181 Array<bool> start_set_points(curves.points_num());
1182 mask.to_bools(start_set_points.as_mutable_span());
1183
1184 Array<int> dst_to_src_point(curves.points_num());
1185
1186 threading::parallel_for(curves.curves_range(), 1024, [&](const IndexRange range) {
1187 for (const int curve_i : range) {
1188 const IndexRange points = points_by_curve[curve_i];
1189 const Span<bool> curve_i_selected_points = start_set_points.as_span().slice(points);
1190 const int first_selected = curve_i_selected_points.first_index_try(true);
1191
1192 MutableSpan<int> dst_to_src_slice = dst_to_src_point.as_mutable_span().slice(points);
1193
1194 array_utils::fill_index_range<int>(dst_to_src_slice, points.start());
1195
1196 if (first_selected == -1 || src_cyclic[curve_i] == false) {
1197 continue;
1198 }
1199
1200 std::rotate(dst_to_src_slice.begin(),
1201 dst_to_src_slice.begin() + first_selected,
1202 dst_to_src_slice.end());
1203 }
1204 });
1205
1206 /* New CurvesGeometry to copy to. */
1207 bke::CurvesGeometry dst_curves(curves.points_num(), curves.curves_num());
1208 BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &curves.vertex_group_names);
1209
1210 /* Copy offsets. */
1211 array_utils::copy(curves.offsets(), dst_curves.offsets_for_write());
1212
1213 /* Attribute accessors for copying. */
1214 bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
1215 const bke::AttributeAccessor src_attributes = curves.attributes();
1216
1217 /* Copy curve attrs. */
1219 src_attributes, bke::AttrDomain::Curve, bke::AttrDomain::Curve, {}, dst_attributes);
1220 array_utils::copy(src_cyclic, dst_curves.cyclic_for_write());
1221
1222 /* Copy point attrs */
1223 gather_attributes(src_attributes,
1226 {},
1227 dst_to_src_point,
1228 dst_attributes);
1229
1230 dst_curves.update_curve_types();
1231 /* TODO: change to copying knots by point. */
1232 if (curves.nurbs_has_custom_knots()) {
1234 dst_curves.curves_range(), NURBS_KNOT_MODE_NORMAL, NURBS_KNOT_MODE_NORMAL, dst_curves);
1235 }
1236 return dst_curves;
1237}
1238
1240{
1241 using namespace bke::greasepencil;
1242 const Scene *scene = CTX_data_scene(C);
1243 Object *object = CTX_data_active_object(C);
1244 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1245
1246 std::atomic<bool> changed = false;
1247 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1248 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1249 IndexMaskMemory memory;
1251 *object, info.drawing, info.layer_index, memory);
1252 if (selection.is_empty()) {
1253 return;
1254 }
1255
1256 info.drawing.strokes_for_write() = set_start_point(info.drawing.strokes(), selection);
1257
1259 changed = true;
1260 });
1261
1262 if (changed) {
1263 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1264 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1265 }
1266 return OPERATOR_FINISHED;
1267}
1269{
1270 /* Identifiers */
1271 ot->name = "Set Start Point";
1272 ot->idname = "GREASE_PENCIL_OT_set_start_point";
1273 ot->description = "Select which point is the beginning of the curve";
1274
1275 /* Callbacks */
1278
1279 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1280}
1281
1282
1283/* -------------------------------------------------------------------- */
1286
1287enum class CapsMode : int8_t {
1289 FLAT = 0,
1293 END = 2,
1296};
1297
1298static void toggle_caps(MutableSpan<int8_t> caps, const IndexMask &strokes)
1299{
1300 strokes.foreach_index([&](const int stroke_i) {
1301 if (caps[stroke_i] == GP_STROKE_CAP_FLAT) {
1302 caps[stroke_i] = GP_STROKE_CAP_ROUND;
1303 }
1304 else {
1305 caps[stroke_i] = GP_STROKE_CAP_FLAT;
1306 }
1307 });
1308}
1309
1311{
1312 const Scene *scene = CTX_data_scene(C);
1313 Object *object = CTX_data_active_object(C);
1314 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1315
1316 const CapsMode mode = CapsMode(RNA_enum_get(op->ptr, "type"));
1317
1318 bool changed = false;
1319 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1320 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1322 IndexMaskMemory memory;
1324 *object, info.drawing, info.layer_index, memory);
1325 if (strokes.is_empty()) {
1326 return;
1327 }
1328
1329 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
1330
1331 if (ELEM(mode, CapsMode::ROUND, CapsMode::FLAT)) {
1332 const int8_t flag_set = (mode == CapsMode::ROUND) ? int8_t(GP_STROKE_CAP_TYPE_ROUND) :
1334 if (bke::SpanAttributeWriter<int8_t> start_caps =
1335 attributes.lookup_or_add_for_write_span<int8_t>("start_cap", bke::AttrDomain::Curve))
1336 {
1337 index_mask::masked_fill(start_caps.span, flag_set, strokes);
1338 start_caps.finish();
1339 }
1341 attributes.lookup_or_add_for_write_span<int8_t>("end_cap", bke::AttrDomain::Curve))
1342 {
1343 index_mask::masked_fill(end_caps.span, flag_set, strokes);
1344 end_caps.finish();
1345 }
1346 }
1347 else {
1348 switch (mode) {
1349 case CapsMode::START: {
1351 attributes.lookup_or_add_for_write_span<int8_t>("start_cap",
1353 {
1354 toggle_caps(caps.span, strokes);
1355 caps.finish();
1356 }
1357 break;
1358 }
1359 case CapsMode::END: {
1361 attributes.lookup_or_add_for_write_span<int8_t>("end_cap",
1363 {
1364 toggle_caps(caps.span, strokes);
1365 caps.finish();
1366 }
1367 break;
1368 }
1369 case CapsMode::ROUND:
1370 case CapsMode::FLAT:
1371 break;
1372 }
1373 }
1374
1375 changed = true;
1376 });
1377
1378 if (changed) {
1379 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1380 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1381 }
1382
1383 return OPERATOR_FINISHED;
1384}
1385
1387{
1388 static const EnumPropertyItem prop_caps_types[] = {
1389 {int(CapsMode::ROUND), "ROUND", 0, "Rounded", "Set as default rounded"},
1390 {int(CapsMode::FLAT), "FLAT", 0, "Flat", ""},
1392 {int(CapsMode::START), "START", 0, "Toggle Start", ""},
1393 {int(CapsMode::END), "END", 0, "Toggle End", ""},
1394 {0, nullptr, 0, nullptr, nullptr},
1395 };
1396
1397 ot->name = "Set Curve Caps";
1398 ot->idname = "GREASE_PENCIL_OT_caps_set";
1399 ot->description = "Change curve caps mode (rounded or flat)";
1400
1401 ot->invoke = WM_menu_invoke;
1404
1405 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1406
1407 ot->prop = RNA_def_enum(ot->srna, "type", prop_caps_types, int(CapsMode::ROUND), "Type", "");
1408}
1409
1411
1412/* -------------------------------------------------------------------- */
1415
1416/* Retry enum items with object materials. */
1418 PointerRNA * /*ptr*/,
1419 PropertyRNA * /*prop*/,
1420 bool *r_free)
1421{
1423 EnumPropertyItem *item = nullptr, item_tmp = {0};
1424 int totitem = 0;
1425
1426 if (ob == nullptr) {
1428 }
1429
1430 /* Existing materials */
1431 for (const int i : IndexRange(ob->totcol)) {
1432 if (Material *ma = BKE_object_material_get(ob, i + 1)) {
1433 item_tmp.identifier = ma->id.name + 2;
1434 item_tmp.name = ma->id.name + 2;
1435 item_tmp.value = i + 1;
1436 item_tmp.icon = ma->preview ? ma->preview->runtime->icon_id : ICON_NONE;
1437
1438 RNA_enum_item_add(&item, &totitem, &item_tmp);
1439 }
1440 }
1441 RNA_enum_item_end(&item, &totitem);
1442 *r_free = true;
1443
1444 return item;
1445}
1446
1448{
1449 Object *object = CTX_data_active_object(C);
1450 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1451 const int slot = RNA_enum_get(op->ptr, "slot");
1452
1453 /* Try to get material slot. */
1454 if ((slot < 1) || (slot > object->totcol)) {
1455 return OPERATOR_CANCELLED;
1456 }
1457
1458 /* Set active material. */
1459 object->actcol = slot;
1460
1461 WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_EDITED, &grease_pencil);
1462
1463 return OPERATOR_FINISHED;
1464}
1465
1467{
1468 ot->name = "Set Active Material";
1469 ot->idname = "GREASE_PENCIL_OT_set_material";
1470 ot->description = "Set active material";
1471
1474
1475 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1476
1477 /* Material to use (dynamic enum) */
1478 ot->prop = RNA_def_enum(ot->srna, "slot", rna_enum_dummy_DEFAULT_items, 0, "Material Slot", "");
1480}
1481
1482
1483/* -------------------------------------------------------------------- */
1486
1488{
1489 const Scene *scene = CTX_data_scene(C);
1490 Object *object = CTX_data_active_object(C);
1491 View3D *v3d = CTX_wm_view3d(C);
1492 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1493
1495 scene->toolsettings);
1496
1497 std::atomic<bool> changed = false;
1498 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1499 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1500 IndexMaskMemory memory;
1501
1503 if (selection_domain == bke::AttrDomain::Curve) {
1505 *object, info.drawing, info.layer_index, memory);
1506 if (strokes.is_empty()) {
1507 return;
1508 }
1510 }
1511 else if (selection_domain == bke::AttrDomain::Point) {
1513 *object, info.drawing, info.layer_index, v3d->overlay.handle_display, memory);
1514 if (points.is_empty()) {
1515 return;
1516 }
1518 }
1520 changed.store(true, std::memory_order_relaxed);
1521 });
1522
1523 if (changed) {
1524 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1525 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1526 }
1527 return OPERATOR_FINISHED;
1528}
1529
1531{
1532 ot->name = "Duplicate";
1533 ot->idname = "GREASE_PENCIL_OT_duplicate";
1534 ot->description = "Duplicate the selected points";
1535
1538
1539 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1540}
1541
1543{
1544 Object *object = CTX_data_active_object(C);
1545 Scene &scene = *CTX_data_scene(C);
1546 const int limit = RNA_int_get(op->ptr, "limit");
1547
1548 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1549 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
1550
1551 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1553 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
1554
1555 IndexMaskMemory memory;
1557 *object, info.drawing, info.layer_index, memory);
1558
1559 const IndexMask curves_to_delete = IndexMask::from_predicate(
1560 editable_strokes, GrainSize(4096), memory, [&](const int i) {
1561 return points_by_curve[i].size() <= limit;
1562 });
1563
1564 curves.remove_curves(curves_to_delete, {});
1565 });
1566
1567 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1568 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1569
1570 return OPERATOR_FINISHED;
1571}
1572
1574 wmOperator *op,
1575 const wmEvent *event)
1576{
1578 C, op, event, IFACE_("Remove Loose Points"), IFACE_("Delete"));
1579}
1580
1582{
1583 ot->name = "Clean Loose Points";
1584 ot->idname = "GREASE_PENCIL_OT_clean_loose";
1585 ot->description = "Remove loose points";
1586
1590
1591 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1592
1593 RNA_def_int(ot->srna,
1594 "limit",
1595 1,
1596 1,
1597 INT_MAX,
1598 "Limit",
1599 "Number of points to consider stroke as loose",
1600 1,
1601 INT_MAX);
1602}
1603
1605
1606/* -------------------------------------------------------------------- */
1609
1611{
1612 const int cuts = RNA_int_get(op->ptr, "number_cuts");
1613 const bool only_selected = RNA_boolean_get(op->ptr, "only_selected");
1614
1615 std::atomic<bool> changed = false;
1616
1617 const Scene *scene = CTX_data_scene(C);
1618 Object *object = CTX_data_active_object(C);
1619 View3D *v3d = CTX_wm_view3d(C);
1620 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1622 scene->toolsettings);
1623
1624 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1625
1626 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1627 IndexMaskMemory memory;
1629 *object, info.drawing, info.layer_index, memory);
1630 if (strokes.is_empty()) {
1631 return;
1632 }
1634
1635 VArray<int> vcuts = {};
1636
1637 if (selection_domain == bke::AttrDomain::Curve || !only_selected) {
1638 /* Subdivide entire selected curve, every stroke subdivides to the same cut. */
1639 vcuts = VArray<int>::from_single(cuts, curves.points_num());
1640 }
1641 else if (selection_domain == bke::AttrDomain::Point) {
1642 /* Subdivide between selected points. Only cut between selected points.
1643 * Make the cut array the same length as point count for specifying
1644 * cut/uncut for each segment. */
1645 const VArray<bool> selection = *curves.attributes().lookup_or_default<bool>(
1646 ".selection", bke::AttrDomain::Point, true);
1647 const VArray<bool> selection_left = *curves.attributes().lookup_or_default<bool>(
1648 ".selection_handle_left", bke::AttrDomain::Point, true);
1649 const VArray<bool> selection_right = *curves.attributes().lookup_or_default<bool>(
1650 ".selection_handle_right", bke::AttrDomain::Point, true);
1651 const VArray<int8_t> curve_types = curves.curve_types();
1652
1653 auto is_selected = [&](const int point_i, const int curve_i) {
1654 if (selection[point_i]) {
1655 return true;
1656 }
1658 return false;
1659 }
1660 if (curve_types[curve_i] == CURVE_TYPE_BEZIER) {
1661 return selection_left[point_i] || selection_right[point_i];
1662 }
1663 return false;
1664 };
1665
1666 const OffsetIndices points_by_curve = curves.points_by_curve();
1667 const VArray<bool> cyclic = curves.cyclic();
1668
1669 Array<int> use_cuts(curves.points_num(), 0);
1670
1671 /* The cut is after each point, so the last point selected wouldn't need to be registered. */
1672 for (const int curve : curves.curves_range()) {
1673 /* No need to loop to the last point since the cut is registered on the point before the
1674 * segment. */
1675 for (const int point : points_by_curve[curve].drop_back(1)) {
1676 /* The point itself should be selected. */
1677 if (!is_selected(point, curve)) {
1678 continue;
1679 }
1680 /* If the next point in the curve is selected, then cut this segment. */
1681 if (is_selected(point + 1, curve)) {
1682 use_cuts[point] = cuts;
1683 }
1684 }
1685 /* Check for cyclic and selection. */
1686 if (cyclic[curve]) {
1687 const int first_point = points_by_curve[curve].first();
1688 const int last_point = points_by_curve[curve].last();
1689 if (is_selected(first_point, curve) && is_selected(last_point, curve)) {
1690 use_cuts[last_point] = cuts;
1691 }
1692 }
1693 }
1694 vcuts = VArray<int>::from_container(std::move(use_cuts));
1695 }
1696
1697 curves = geometry::subdivide_curves(curves, strokes, vcuts);
1699 changed.store(true, std::memory_order_relaxed);
1700 });
1701
1702 if (changed) {
1703 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1705 }
1706
1707 return OPERATOR_FINISHED;
1708}
1709
1711{
1712 PropertyRNA *prop;
1713
1714 ot->name = "Subdivide Stroke";
1715 ot->idname = "GREASE_PENCIL_OT_stroke_subdivide";
1716 ot->description =
1717 "Subdivide between continuous selected points of the stroke adding a point half way "
1718 "between "
1719 "them";
1720
1723
1724 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1725
1726 prop = RNA_def_int(ot->srna, "number_cuts", 1, 1, 32, "Number of Cuts", "", 1, 5);
1727 /* Avoid re-using last var because it can cause _very_ high value and annoy users. */
1729
1730 RNA_def_boolean(ot->srna,
1731 "only_selected",
1732 true,
1733 "Selected Points",
1734 "Smooth only selected points in the stroke");
1735}
1736
1738
1739/* -------------------------------------------------------------------- */
1742
1743enum class ReorderDirection : int8_t {
1745 TOP = 0,
1747 UP = 1,
1749 DOWN = 2,
1752};
1753
1755 const IndexMask &selected,
1756 const ReorderDirection direction)
1757{
1758 Array<int> indices(universe.size());
1759
1761 /* Initialize the indices. */
1763 }
1764
1766 /*
1767 * Take the selected indices and move them to the start for `Bottom` or the end for `Top`
1768 * And fill the reset with the unselected indices.
1769 *
1770 * Here's a diagram:
1771 *
1772 * Input
1773 * 0 1 2 3 4 5 6 7 8 9
1774 * ^ ^ ^
1775 *
1776 * Top
1777 * |-----A-----| |-B-|
1778 * 0 1 3 6 7 8 9 2 4 5
1779 * ^ ^ ^
1780 *
1781 * Bottom
1782 * |-A-| |-----B-----|
1783 * 2 4 5 0 1 3 6 7 8 9
1784 * ^ ^ ^
1785 */
1786
1787 IndexMaskMemory memory;
1788 const IndexMask unselected = selected.complement(universe, memory);
1789
1790 const IndexMask &A = (direction == ReorderDirection::BOTTOM) ? selected : unselected;
1791 const IndexMask &B = (direction == ReorderDirection::BOTTOM) ? unselected : selected;
1792
1793 A.to_indices(indices.as_mutable_span().take_front(A.size()));
1794 B.to_indices(indices.as_mutable_span().take_back(B.size()));
1795 }
1796 else if (direction == ReorderDirection::DOWN) {
1797 selected.foreach_index_optimized<int>([&](const int curve_i, const int pos) {
1798 /* Check if the curve index is touching the beginning without any gaps. */
1799 if (curve_i != pos) {
1800 /* Move a index down by flipping it with the one below it. */
1801 std::swap(indices[curve_i], indices[curve_i - 1]);
1802 }
1803 });
1804 }
1805 else if (direction == ReorderDirection::UP) {
1806 Array<int> selected_indices(selected.size());
1807 selected.to_indices(selected_indices.as_mutable_span());
1808
1809 /* Because each index is moving up we need to loop through the indices backwards,
1810 * starting at the largest. */
1811 for (const int i : selected_indices.index_range()) {
1812 const int pos = selected_indices.index_range().last(i);
1813 const int curve_i = selected_indices[pos];
1814
1815 /* Check if the curve index is touching the end without any gaps. */
1816 if (curve_i != universe.last(i)) {
1817 /* Move a index up by flipping it with the one above it. */
1818 std::swap(indices[curve_i], indices[curve_i + 1]);
1819 }
1820 }
1821 }
1822
1823 return indices;
1824}
1825
1827{
1828 const Scene *scene = CTX_data_scene(C);
1829 Object *object = CTX_data_active_object(C);
1830 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1831
1832 const ReorderDirection direction = ReorderDirection(RNA_enum_get(op->ptr, "direction"));
1833
1834 std::atomic<bool> changed = false;
1835 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1836 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1837 IndexMaskMemory memory;
1839 *object, info.drawing, info.layer_index, memory);
1840 if (strokes.is_empty()) {
1841 return;
1842 }
1844
1845 /* Return if everything is selected. */
1846 if (strokes.size() == curves.curves_num()) {
1847 return;
1848 }
1849
1850 const Array<int> indices = get_reordered_indices(curves.curves_range(), strokes, direction);
1851
1854 changed.store(true, std::memory_order_relaxed);
1855 });
1856
1857 if (changed) {
1858 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1859 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1860 }
1861
1862 return OPERATOR_FINISHED;
1863}
1864
1866{
1867 static const EnumPropertyItem prop_reorder_direction[] = {
1868 {int(ReorderDirection::TOP), "TOP", 0, "Bring to Front", ""},
1869 {int(ReorderDirection::UP), "UP", 0, "Bring Forward", ""},
1871 {int(ReorderDirection::DOWN), "DOWN", 0, "Send Backward", ""},
1872 {int(ReorderDirection::BOTTOM), "BOTTOM", 0, "Send to Back", ""},
1873 {0, nullptr, 0, nullptr, nullptr},
1874 };
1875
1876 ot->name = "Reorder";
1877 ot->idname = "GREASE_PENCIL_OT_reorder";
1878 ot->description = "Change the display order of the selected strokes";
1879
1882
1883 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1884
1885 ot->prop = RNA_def_enum(
1886 ot->srna, "direction", prop_reorder_direction, int(ReorderDirection::TOP), "Direction", "");
1887}
1888
1890
1891/* -------------------------------------------------------------------- */
1894
1896{
1897 using namespace bke::greasepencil;
1898 const Scene *scene = CTX_data_scene(C);
1899 bool changed = false;
1900
1901 Object *object = CTX_data_active_object(C);
1902 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1903
1904 std::string target_layer_name = RNA_string_get(op->ptr, "target_layer_name");
1905 const bool add_new_layer = RNA_boolean_get(op->ptr, "add_new_layer");
1906 TreeNode *target_node = nullptr;
1907
1908 if (add_new_layer) {
1909 target_node = &grease_pencil.add_layer(target_layer_name).as_node();
1910 }
1911 else {
1912 target_node = grease_pencil.find_node_by_name(target_layer_name);
1913 }
1914
1915 if (target_node == nullptr || !target_node->is_layer()) {
1916 BKE_reportf(op->reports, RPT_ERROR, "There is no layer '%s'", target_layer_name.c_str());
1917 return OPERATOR_CANCELLED;
1918 }
1919
1920 Layer &layer_dst = target_node->as_layer();
1921 if (layer_dst.is_locked()) {
1922 BKE_reportf(op->reports, RPT_ERROR, "'%s' Layer is locked", target_layer_name.c_str());
1923 return OPERATOR_CANCELLED;
1924 }
1925
1926 /* Iterate through all the drawings at current scene frame. */
1927 const Vector<MutableDrawingInfo> drawings_src = retrieve_editable_drawings(*scene,
1928 grease_pencil);
1929 for (const MutableDrawingInfo &info : drawings_src) {
1930 bke::CurvesGeometry &curves_src = info.drawing.strokes_for_write();
1931 IndexMaskMemory memory;
1932 const IndexMask selected_strokes = ed::curves::retrieve_selected_curves(curves_src, memory);
1933 if (selected_strokes.is_empty()) {
1934 continue;
1935 }
1936
1937 bool is_key_inserted = false;
1938 const bool has_active_key = ensure_active_keyframe(
1939 *scene, grease_pencil, layer_dst, false, is_key_inserted);
1940 if (has_active_key && is_key_inserted) {
1941 /* Move geometry to a new drawing in target layer. */
1942 Drawing &drawing_dst = *grease_pencil.get_drawing_at(layer_dst, info.frame_number);
1944 curves_src, selected_strokes, {});
1945
1946 curves_src.remove_curves(selected_strokes, {});
1947 drawing_dst.tag_topology_changed();
1948 }
1949 else if (Drawing *drawing_dst = grease_pencil.get_drawing_at(layer_dst, info.frame_number)) {
1950 /* Append geometry to drawing in target layer. */
1952 curves_src, selected_strokes, {});
1953 Curves *selected_curves = bke::curves_new_nomain(std::move(selected_elems));
1954 Curves *layer_curves = bke::curves_new_nomain(std::move(drawing_dst->strokes_for_write()));
1955 std::array<bke::GeometrySet, 2> geometry_sets{
1956 bke::GeometrySet::from_curves(layer_curves),
1957 bke::GeometrySet::from_curves(selected_curves)};
1958 bke::GeometrySet joined = geometry::join_geometries(geometry_sets, {});
1959 drawing_dst->strokes_for_write() = std::move(joined.get_curves_for_write()->geometry.wrap());
1960
1961 curves_src.remove_curves(selected_strokes, {});
1962
1963 drawing_dst->tag_topology_changed();
1964 }
1965
1966 info.drawing.tag_topology_changed();
1967 changed = true;
1968 }
1969
1970 if (changed) {
1971 /* updates */
1974 }
1975
1976 return OPERATOR_FINISHED;
1977}
1978
1980 wmOperator *op,
1981 const wmEvent *event)
1982{
1983 const bool add_new_layer = RNA_boolean_get(op->ptr, "add_new_layer");
1984 if (add_new_layer) {
1985 Object *object = CTX_data_active_object(C);
1986 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1987
1988 const std::string unique_name = grease_pencil.unique_layer_name("Layer");
1989 RNA_string_set(op->ptr, "target_layer_name", unique_name.c_str());
1990
1992 C, op, event, IFACE_("Move to New Layer"), IFACE_("Create"));
1993 }
1994
1995 /* Show the move menu if this operator is invoked from operator search without any property
1996 * pre-set. */
1997 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "target_layer_name");
1998 if (!RNA_property_is_set(op->ptr, prop)) {
1999 WM_menu_name_call(C, "GREASE_PENCIL_MT_move_to_layer", wm::OpCallContext::InvokeDefault);
2000 return OPERATOR_FINISHED;
2001 }
2002
2004}
2005
2007{
2008 PropertyRNA *prop;
2009
2010 ot->name = "Move to Layer";
2011 ot->idname = "GREASE_PENCIL_OT_move_to_layer";
2012 ot->description = "Move selected strokes to another layer";
2013
2017
2018 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2019
2020 prop = RNA_def_string(
2021 ot->srna, "target_layer_name", nullptr, INT16_MAX, "Name", "Target Grease Pencil Layer");
2023 prop = RNA_def_boolean(
2024 ot->srna, "add_new_layer", false, "New Layer", "Move selection to a new layer");
2026}
2027
2029
2030/* -------------------------------------------------------------------- */
2033
2034enum class SeparateMode : int8_t {
2035 /* Selected Points/Strokes. */
2037 /* By Material. */
2038 MATERIAL = 1,
2039 /* By each Layer. */
2041};
2042
2044 {int(SeparateMode::SELECTED), "SELECTED", 0, "Selection", "Separate selected geometry"},
2045 {int(SeparateMode::MATERIAL), "MATERIAL", 0, "By Material", "Separate by material"},
2046 {int(SeparateMode::LAYER), "LAYER", 0, "By Layer", "Separate by layer"},
2047 {0, nullptr, 0, nullptr, nullptr},
2048};
2049
2050static void remove_unused_materials(Main *bmain, Object *object)
2051{
2052 int actcol = object->actcol;
2053 for (int slot = 1; slot <= object->totcol; slot++) {
2054 while (slot <= object->totcol && !BKE_object_material_slot_used(object, slot)) {
2055 object->actcol = slot;
2056 if (!BKE_object_material_slot_remove(bmain, object)) {
2057 break;
2058 }
2059
2060 if (actcol >= slot) {
2061 actcol--;
2062 }
2063 }
2064 }
2065 object->actcol = actcol;
2066}
2067
2069 Scene *scene,
2070 ViewLayer *view_layer,
2071 Base *base_prev,
2072 const GreasePencil &grease_pencil_src)
2073{
2074 const eDupli_ID_Flags dupflag = eDupli_ID_Flags(U.dupflag & USER_DUP_GPENCIL);
2075 Base *base_new = object::add_duplicate(bmain, scene, view_layer, base_prev, dupflag);
2076 Object *object_dst = base_new->object;
2077 object_dst->mode = OB_MODE_OBJECT;
2078 GreasePencil *grease_pencil_dst = BKE_grease_pencil_add(bmain, grease_pencil_src.id.name + 2);
2079 BKE_grease_pencil_copy_parameters(grease_pencil_src, *grease_pencil_dst);
2080 object_dst->data = grease_pencil_dst;
2081
2082 return object_dst;
2083}
2084
2086 const int layer_index,
2087 const GreasePencil &grease_pencil_src,
2088 GreasePencil &grease_pencil_dst,
2089 Vector<int> &src_to_dst_layer_indices)
2090{
2091 using namespace bke::greasepencil;
2092
2093 /* This assumes that the index is valid. Will cause an assert if it is not. */
2094 const Layer &layer_src = grease_pencil_src.layer(layer_index);
2095 if (TreeNode *node = grease_pencil_dst.find_node_by_name(layer_src.name())) {
2096 return node->as_layer();
2097 }
2098
2099 /* If the layer can't be found in `grease_pencil_dst` by name add a new layer. */
2100 Layer &new_layer = grease_pencil_dst.add_layer(layer_src.name());
2101 BKE_grease_pencil_copy_layer_parameters(layer_src, new_layer);
2102 src_to_dst_layer_indices.append(layer_index);
2103
2104 return new_layer;
2105}
2106
2108 Main &bmain,
2109 Scene &scene,
2110 ViewLayer &view_layer,
2111 Base &base_prev,
2112 Object &object_src)
2113{
2114 using namespace bke::greasepencil;
2115 bool changed = false;
2116
2117 GreasePencil &grease_pencil_src = *static_cast<GreasePencil *>(object_src.data);
2119 &bmain, &scene, &view_layer, &base_prev, grease_pencil_src);
2120 GreasePencil &grease_pencil_dst = *static_cast<GreasePencil *>(object_dst->data);
2121
2122 /* Iterate through all the drawings at current scene frame. */
2123 const Vector<MutableDrawingInfo> drawings_src = retrieve_editable_drawings(scene,
2124 grease_pencil_src);
2125 Vector<int> src_to_dst_layer_indices;
2126 for (const MutableDrawingInfo &info : drawings_src) {
2127 bke::CurvesGeometry &curves_src = info.drawing.strokes_for_write();
2128 IndexMaskMemory memory;
2129 const IndexMask selected_points = ed::curves::retrieve_selected_points(curves_src, memory);
2130 if (selected_points.is_empty()) {
2131 continue;
2132 }
2133
2134 /* Insert Keyframe at current frame/layer. */
2136 info.layer_index, grease_pencil_src, grease_pencil_dst, src_to_dst_layer_indices);
2137
2138 Drawing *drawing_dst = grease_pencil_dst.insert_frame(layer_dst, info.frame_number);
2139 BLI_assert(drawing_dst != nullptr);
2140
2141 /* Copy strokes to new CurvesGeometry. */
2143 curves_src, selected_points, {});
2144 curves_src = geometry::remove_points_and_split(curves_src, selected_points);
2145
2146 info.drawing.tag_topology_changed();
2147 drawing_dst->tag_topology_changed();
2148
2149 changed = true;
2150 }
2151
2152 if (changed) {
2153 /* Transfer layer attributes. */
2154 bke::gather_attributes(grease_pencil_src.attributes(),
2157 {},
2158 src_to_dst_layer_indices.as_span(),
2159 grease_pencil_dst.attributes_for_write());
2160
2161 /* Set the active layer in the target object. */
2162 if (grease_pencil_src.has_active_layer()) {
2163 const Layer &active_layer_src = *grease_pencil_src.get_active_layer();
2164 TreeNode *active_layer_dst = grease_pencil_dst.find_node_by_name(active_layer_src.name());
2165 if (active_layer_dst && active_layer_dst->is_layer()) {
2166 grease_pencil_dst.set_active_layer(&active_layer_dst->as_layer());
2167 }
2168 }
2169
2170 /* Add object materials to target object. */
2172 object_dst,
2173 BKE_object_material_array_p(&object_src),
2174 *BKE_object_material_len_p(&object_src),
2175 false);
2176
2177 remove_unused_materials(&bmain, object_dst);
2178 DEG_id_tag_update(&grease_pencil_dst.id, ID_RECALC_GEOMETRY);
2179 WM_event_add_notifier(&C, NC_OBJECT | ND_DRAW, &grease_pencil_dst);
2180 }
2181 return changed;
2182}
2183
2185 Main &bmain,
2186 Scene &scene,
2187 ViewLayer &view_layer,
2188 Base &base_prev,
2189 Object &object_src)
2190{
2191 using namespace bke::greasepencil;
2192 bool changed = false;
2193
2194 GreasePencil &grease_pencil_src = *static_cast<GreasePencil *>(object_src.data);
2195
2196 /* Create a new object for each layer. */
2197 for (const int layer_i : grease_pencil_src.layers().index_range()) {
2198 Layer &layer_src = grease_pencil_src.layer(layer_i);
2199 if (layer_src.is_locked()) {
2200 continue;
2201 }
2202
2204 &bmain, &scene, &view_layer, &base_prev, grease_pencil_src);
2205 GreasePencil &grease_pencil_dst = *static_cast<GreasePencil *>(object_dst->data);
2206 Vector<int> src_to_dst_layer_indices;
2208 layer_i, grease_pencil_src, grease_pencil_dst, src_to_dst_layer_indices);
2209
2210 /* Iterate through all the drawings at current frame. */
2212 scene, grease_pencil_src, layer_src);
2213 for (const MutableDrawingInfo &info : drawings_src) {
2214 bke::CurvesGeometry &curves_src = info.drawing.strokes_for_write();
2215 IndexMaskMemory memory;
2216 const IndexMask strokes = retrieve_editable_strokes(
2217 object_src, info.drawing, info.layer_index, memory);
2218 if (strokes.is_empty()) {
2219 continue;
2220 }
2221
2222 /* Add object materials. */
2224 object_dst,
2225 BKE_object_material_array_p(&object_src),
2226 *BKE_object_material_len_p(&object_src),
2227 false);
2228
2229 /* Insert Keyframe at current frame/layer. */
2230 Drawing *drawing_dst = grease_pencil_dst.insert_frame(layer_dst, info.frame_number);
2231 /* TODO: Can we assume the insert never fails? */
2232 BLI_assert(drawing_dst != nullptr);
2233
2234 /* Copy strokes to new CurvesGeometry. */
2236 info.drawing.strokes(), strokes, {});
2237 curves_src.remove_curves(strokes, {});
2238
2239 info.drawing.tag_topology_changed();
2240 drawing_dst->tag_topology_changed();
2241
2242 changed = true;
2243 }
2244
2245 /* Transfer layer attributes. */
2246 bke::gather_attributes(grease_pencil_src.attributes(),
2249 {},
2250 src_to_dst_layer_indices.as_span(),
2251 grease_pencil_dst.attributes_for_write());
2252
2253 remove_unused_materials(&bmain, object_dst);
2254
2255 DEG_id_tag_update(&grease_pencil_dst.id, ID_RECALC_GEOMETRY);
2256 WM_event_add_notifier(&C, NC_OBJECT | ND_DRAW, &grease_pencil_dst);
2257 }
2258
2259 return changed;
2260}
2261
2263 Main &bmain,
2264 Scene &scene,
2265 ViewLayer &view_layer,
2266 Base &base_prev,
2267 Object &object_src)
2268{
2269 using namespace blender::bke;
2270 using namespace bke::greasepencil;
2271 bool changed = false;
2272
2273 GreasePencil &grease_pencil_src = *static_cast<GreasePencil *>(object_src.data);
2274
2275 /* Create a new object for each material. */
2276 for (const int mat_i : IndexRange(object_src.totcol).drop_front(1)) {
2277 if (!BKE_object_material_slot_used(&object_src, mat_i + 1)) {
2278 continue;
2279 }
2280
2282 &bmain, &scene, &view_layer, &base_prev, grease_pencil_src);
2283 GreasePencil &grease_pencil_dst = *static_cast<GreasePencil *>(object_dst->data);
2284
2285 /* Add object materials. */
2287 object_dst,
2288 BKE_object_material_array_p(&object_src),
2289 *BKE_object_material_len_p(&object_src),
2290 false);
2291
2292 /* Iterate through all the drawings at current scene frame. */
2293 const Vector<MutableDrawingInfo> drawings_src = retrieve_editable_drawings(scene,
2294 grease_pencil_src);
2295 Vector<int> src_to_dst_layer_indices;
2296 for (const MutableDrawingInfo &info : drawings_src) {
2297 bke::CurvesGeometry &curves_src = info.drawing.strokes_for_write();
2298 IndexMaskMemory memory;
2300 object_src, info.drawing, mat_i, memory);
2301 if (strokes.is_empty()) {
2302 continue;
2303 }
2304
2305 /* Insert Keyframe at current frame/layer. */
2307 info.layer_index, grease_pencil_src, grease_pencil_dst, src_to_dst_layer_indices);
2308
2309 Drawing *drawing_dst = grease_pencil_dst.insert_frame(layer_dst, info.frame_number);
2310 /* TODO: Can we assume the insert never fails? */
2311 BLI_assert(drawing_dst != nullptr);
2312
2313 /* Copy strokes to new CurvesGeometry. */
2314 drawing_dst->strokes_for_write() = bke::curves_copy_curve_selection(curves_src, strokes, {});
2315 curves_src.remove_curves(strokes, {});
2316
2317 info.drawing.tag_topology_changed();
2318 drawing_dst->tag_topology_changed();
2319
2320 changed = true;
2321 }
2322
2323 /* Transfer layer attributes. */
2324 bke::gather_attributes(grease_pencil_src.attributes(),
2327 {},
2328 src_to_dst_layer_indices.as_span(),
2329 grease_pencil_dst.attributes_for_write());
2330
2331 remove_unused_materials(&bmain, object_dst);
2332
2333 DEG_id_tag_update(&grease_pencil_dst.id, ID_RECALC_GEOMETRY);
2334 WM_event_add_notifier(&C, NC_OBJECT | ND_DRAW, &grease_pencil_dst);
2335 }
2336
2337 if (changed) {
2338 remove_unused_materials(&bmain, &object_src);
2339 }
2340
2341 return changed;
2342}
2343
2345{
2346 using namespace bke::greasepencil;
2347 Main *bmain = CTX_data_main(C);
2348 Scene *scene = CTX_data_scene(C);
2349 ViewLayer *view_layer = CTX_data_view_layer(C);
2350 Base *base_prev = CTX_data_active_base(C);
2351 Object *object_src = CTX_data_active_object(C);
2352 GreasePencil &grease_pencil_src = *static_cast<GreasePencil *>(object_src->data);
2353
2354 const SeparateMode mode = SeparateMode(RNA_enum_get(op->ptr, "mode"));
2355 bool changed = false;
2356
2357 WM_cursor_wait(true);
2358
2359 switch (mode) {
2361 /* Cancel if nothing selected. */
2363 grease_pencil_src);
2364 const bool has_selection = std::any_of(
2365 drawings.begin(), drawings.end(), [&](const MutableDrawingInfo &info) {
2366 return ed::curves::has_anything_selected(info.drawing.strokes());
2367 });
2368 if (!has_selection) {
2369 BKE_report(op->reports, RPT_ERROR, "Nothing selected");
2370 WM_cursor_wait(false);
2371 return OPERATOR_CANCELLED;
2372 }
2373
2375 *C, *bmain, *scene, *view_layer, *base_prev, *object_src);
2376 break;
2377 }
2379 /* Cancel if the object only has one material. */
2380 if (object_src->totcol == 1) {
2381 BKE_report(op->reports, RPT_ERROR, "The object has only one material");
2382 WM_cursor_wait(false);
2383 return OPERATOR_CANCELLED;
2384 }
2385
2387 *C, *bmain, *scene, *view_layer, *base_prev, *object_src);
2388 break;
2389 }
2390 case SeparateMode::LAYER: {
2391 /* Cancel if the object only has one layer. */
2392 if (grease_pencil_src.layers().size() == 1) {
2393 BKE_report(op->reports, RPT_ERROR, "The object has only one layer");
2394 WM_cursor_wait(false);
2395 return OPERATOR_CANCELLED;
2396 }
2398 *C, *bmain, *scene, *view_layer, *base_prev, *object_src);
2399 break;
2400 }
2401 }
2402
2403 WM_cursor_wait(false);
2404
2405 if (changed) {
2406 DEG_id_tag_update(&grease_pencil_src.id, ID_RECALC_GEOMETRY);
2407 WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_EDITED, &grease_pencil_src);
2408 }
2409
2410 return OPERATOR_FINISHED;
2411}
2412
2414{
2415 ot->name = "Separate";
2416 ot->idname = "GREASE_PENCIL_OT_separate";
2417 ot->description = "Separate the selected geometry into a new Grease Pencil object";
2418
2419 ot->invoke = WM_menu_invoke;
2422
2423 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2424
2425 ot->prop = RNA_def_enum(
2426 ot->srna, "mode", prop_separate_modes, int(SeparateMode::SELECTED), "Mode", "");
2427}
2428
2430
2431/* -------------------------------------------------------------------- */
2434
2435/* Global clipboard for Grease Pencil curves. */
2436static struct Clipboard {
2438 /* Name of the layer. */
2439 std::string name;
2440 /* Curves for this layer. */
2442 };
2444 /* Object transform of stored curves. */
2446 /* We store the material uid's of the copied curves, so we can match those when pasting the
2447 * clipboard into another object. */
2450} *grease_pencil_clipboard = nullptr;
2451
2454
2456{
2457 std::scoped_lock lock(grease_pencil_clipboard_lock);
2458
2459 if (grease_pencil_clipboard == nullptr) {
2460 grease_pencil_clipboard = MEM_new<Clipboard>(__func__);
2461 }
2463}
2464
2466{
2467 std::scoped_lock lock(grease_pencil_clipboard_lock);
2468
2470 MEM_delete(grease_pencil_clipboard);
2471 grease_pencil_clipboard = nullptr;
2472 }
2473}
2474
2476{
2477 using namespace blender::ed::greasepencil;
2478
2479 /* Get a list of all materials in the scene. */
2480 Map<uint, Material *> scene_materials;
2481 LISTBASE_FOREACH (Material *, material, &bmain.materials) {
2482 scene_materials.add(material->id.session_uid, material);
2483 }
2484
2485 const Clipboard &clipboard = ensure_grease_pencil_clipboard();
2486 Array<int> clipboard_material_remap(clipboard.materials_in_source_num, 0);
2487 for (const int i : clipboard.materials.index_range()) {
2488 /* Check if the material name exists in the scene. */
2489 int target_index;
2490 uint material_id = clipboard.materials[i].first;
2491 Material *material = scene_materials.lookup_default(material_id, nullptr);
2492 if (!material) {
2493 /* Material is removed, so create a new material. */
2494 BKE_grease_pencil_object_material_new(&bmain, &object, nullptr, &target_index);
2495 clipboard_material_remap[clipboard.materials[i].second] = target_index;
2496 continue;
2497 }
2498
2499 /* Find or add the material to the target object. */
2500 target_index = BKE_object_material_ensure(&bmain, &object, material);
2501 clipboard_material_remap[clipboard.materials[i].second] = target_index;
2502 }
2503
2504 return clipboard_material_remap;
2505}
2506
2508 const VArray<float4x4> &transforms)
2509{
2510 BLI_assert(geometries.size() == transforms.size());
2511
2512 std::unique_ptr<bke::Instances> instances = std::make_unique<bke::Instances>();
2513 instances->resize(geometries.size());
2514 transforms.materialize(instances->transforms_for_write());
2515 MutableSpan<int> handles = instances->reference_handles_for_write();
2516 for (const int i : geometries.index_range()) {
2517 handles[i] = instances->add_new_reference(bke::InstanceReference{geometries[i]});
2518 }
2519
2521 options.keep_original_ids = true;
2522 options.realize_instance_attributes = false;
2523 return realize_instances(bke::GeometrySet::from_instances(instances.release()), options)
2524 .geometry;
2525}
2532
2534{
2536
2537 const Scene *scene = CTX_data_scene(C);
2538 Object *object = CTX_data_active_object(C);
2539 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
2541 scene->toolsettings);
2542
2544
2545 int num_elements_copied = 0;
2546 Map<const Layer *, Vector<bke::GeometrySet>> copied_curves_per_layer;
2547
2548 /* Collect all selected strokes/points on all editable layers. */
2549 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
2550 for (const MutableDrawingInfo &drawing_info : drawings) {
2551 const bke::CurvesGeometry &curves = drawing_info.drawing.strokes();
2552 const Layer &layer = grease_pencil.layer(drawing_info.layer_index);
2553
2554 if (curves.is_empty()) {
2555 continue;
2556 }
2558 continue;
2559 }
2560
2561 /* Get a copy of the selected geometry on this layer. */
2562 IndexMaskMemory memory;
2563 bke::CurvesGeometry copied_curves;
2564
2565 if (selection_domain == bke::AttrDomain::Curve) {
2566 const IndexMask selected_curves = ed::curves::retrieve_selected_curves(curves, memory);
2567 copied_curves = curves_copy_curve_selection(curves, selected_curves, {});
2568 num_elements_copied += copied_curves.curves_num();
2569 }
2570 else if (selection_domain == bke::AttrDomain::Point) {
2571 const IndexMask selected_points = ed::curves::retrieve_selected_points(curves, memory);
2572 copied_curves = geometry::remove_points_and_split(
2573 curves, selected_points.complement(curves.points_range(), memory));
2574 num_elements_copied += copied_curves.points_num();
2575 }
2576
2577 /* Add the layer selection to the set of copied curves. */
2578 copied_curves_per_layer.lookup_or_add_default(&layer).append(
2579 bke::GeometrySet::from_curves(curves_new_nomain(std::move(copied_curves))));
2580 }
2581
2582 if (copied_curves_per_layer.is_empty()) {
2583 clipboard.layers.reinitialize(0);
2584 return OPERATOR_CANCELLED;
2585 }
2586
2587 clipboard.layers.reinitialize(copied_curves_per_layer.size());
2588
2589 int i = 0;
2590 for (auto const &[layer, geometries] : copied_curves_per_layer.items()) {
2591 const float4x4 layer_to_object = layer->to_object_space(*object);
2592 Clipboard::ClipboardLayer &cliplayer = clipboard.layers[i];
2593
2594 bke::GeometrySet joined_copied_curves = join_geometries_with_transform(geometries.as_span(),
2595 layer_to_object);
2596 cliplayer.curves = joined_copied_curves.get_curves()->geometry.wrap();
2597 cliplayer.name = layer->name();
2598 i++;
2599 }
2600 clipboard.object_to_world = object->object_to_world();
2601
2602 /* Store the session uid of the materials used by the curves in the clipboard. We use the uid to
2603 * remap the material indices when pasting. */
2604 clipboard.materials.clear();
2605 clipboard.materials_in_source_num = grease_pencil.material_array_num;
2606
2607 const auto is_material_index_used = [&](const int material_index) -> bool {
2608 for (const Clipboard::ClipboardLayer &layer : clipboard.layers) {
2609 const bke::AttributeAccessor attributes = layer.curves.attributes();
2610 const VArraySpan<int> material_indices = *attributes.lookup_or_default<int>(
2611 "material_index", bke::AttrDomain::Curve, 0);
2612 if (material_indices.contains(material_index)) {
2613 return true;
2614 }
2615 }
2616 return false;
2617 };
2618
2619 for (const int material_index : IndexRange(grease_pencil.material_array_num)) {
2620 if (!is_material_index_used(material_index)) {
2621 continue;
2622 }
2623 const Material *material = BKE_object_material_get(object, material_index + 1);
2624 clipboard.materials.append({material ? material->id.session_uid : 0, material_index});
2625 }
2626
2627 /* Report the numbers. */
2628 if (selection_domain == bke::AttrDomain::Curve) {
2629 BKE_reportf(op->reports, RPT_INFO, "Copied %d selected curve(s)", num_elements_copied);
2630 }
2631 else if (selection_domain == bke::AttrDomain::Point) {
2632 BKE_reportf(op->reports, RPT_INFO, "Copied %d selected point(s)", num_elements_copied);
2633 }
2634
2635 return OPERATOR_FINISHED;
2636}
2637
2639{
2640 ot->name = "Copy Strokes";
2641 ot->idname = "GREASE_PENCIL_OT_copy";
2642 ot->description = "Copy the selected Grease Pencil points or strokes to the internal clipboard";
2643
2646
2647 ot->flag = OPTYPE_REGISTER;
2648}
2649
2651 Object &object,
2652 const bke::CurvesGeometry &curves_to_paste,
2653 const float4x4 &object_to_paste_layer,
2654 const float4x4 &clipboard_to_world,
2655 const bool keep_world_transform,
2656 const bool paste_back,
2658{
2659 /* Get a list of all materials in the scene. */
2660 const Array<int> clipboard_material_remap = ed::greasepencil::clipboard_materials_remap(bmain,
2661 object);
2662
2663 /* Get the index range of the pasted curves in the target layer. */
2664 const IndexRange pasted_curves_range = paste_back ? IndexRange(0, curves_to_paste.curves_num()) :
2665 IndexRange(drawing.strokes().curves_num(),
2666 curves_to_paste.curves_num());
2667
2668 /* Append the geometry from the clipboard to the target layer. */
2669 Curves *clipboard_id = bke::curves_new_nomain(curves_to_paste);
2670 Curves *target_id = curves_new_nomain(std::move(drawing.strokes_for_write()));
2671
2672 const Array<bke::GeometrySet> geometry_sets = {
2673 bke::GeometrySet::from_curves(paste_back ? clipboard_id : target_id),
2674 bke::GeometrySet::from_curves(paste_back ? target_id : clipboard_id)};
2675
2676 const float4x4 transform = object_to_paste_layer *
2677 (keep_world_transform ?
2678 object.world_to_object() * clipboard_to_world :
2680 const Array<float4x4> transforms = paste_back ? Span<float4x4>{transform, float4x4::identity()} :
2683 geometry_sets, VArray<float4x4>::from_container(transforms));
2684
2685 drawing.strokes_for_write() = std::move(joined_curves.get_curves_for_write()->geometry.wrap());
2686
2687 if (!clipboard_material_remap.is_empty()) {
2688 /* Remap the material indices of the pasted curves to the target object material indices. */
2690 bke::SpanAttributeWriter<int> material_indices = attributes.lookup_or_add_for_write_span<int>(
2691 "material_index", bke::AttrDomain::Curve);
2692 if (material_indices) {
2693 for (const int i : pasted_curves_range) {
2694 material_indices.span[i] = clipboard_material_remap[material_indices.span[i]];
2695 }
2696 material_indices.finish();
2697 }
2698 }
2699
2700 drawing.tag_topology_changed();
2701
2702 return pasted_curves_range;
2703}
2704
2705enum class PasteType {
2708};
2709
2711{
2712 using namespace bke::greasepencil;
2713 Main *bmain = CTX_data_main(C);
2714 const Scene &scene = *CTX_data_scene(C);
2715 Object *object = CTX_data_active_object(C);
2717 scene.toolsettings);
2718 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
2719
2720 const PasteType type = PasteType(RNA_enum_get(op->ptr, "type"));
2721
2722 const bool keep_world_transform = RNA_boolean_get(op->ptr, "keep_world_transform");
2723 const bool paste_on_back = RNA_boolean_get(op->ptr, "paste_back");
2724
2726 if (clipboard.layers.is_empty()) {
2727 return OPERATOR_CANCELLED;
2728 }
2729
2730 /* Make sure everything on the clipboard is selected, in the correct selection domain. */
2732 bke::GSpanAttributeWriter selection = ed::curves::ensure_selection_attribute(
2733 layer.curves, selection_domain, bke::AttrType::Bool);
2734 selection.finish();
2735 });
2736
2737 if (type == PasteType::Active) {
2738 Layer *active_layer = grease_pencil.get_active_layer();
2739 if (!active_layer) {
2740 BKE_report(op->reports, RPT_ERROR, "No active Grease Pencil layer to paste into");
2741 return OPERATOR_CANCELLED;
2742 }
2743 if (!active_layer->is_editable()) {
2744 BKE_report(op->reports, RPT_ERROR, "Active layer is not editable");
2745 return OPERATOR_CANCELLED;
2746 }
2747
2748 /* Deselect everything from editable drawings. The pasted strokes are the only ones then after
2749 * the paste. That's convenient for the user. */
2750 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
2751 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
2753 info.drawing.strokes_for_write(), selection_domain, bke::AttrType::Bool);
2754 ed::curves::fill_selection_false(selection_in_target.span);
2755 selection_in_target.finish();
2756 });
2757
2758 const float4x4 object_to_layer = math::invert(active_layer->to_object_space(*object));
2759
2760 /* Ensure active keyframe. */
2761 bool inserted_keyframe = false;
2762 if (!ensure_active_keyframe(scene, grease_pencil, *active_layer, false, inserted_keyframe)) {
2763 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil frame to draw on");
2764 return OPERATOR_CANCELLED;
2765 }
2766
2767 Vector<MutableDrawingInfo> drawing_infos =
2769 scene, grease_pencil, *active_layer);
2770 for (const MutableDrawingInfo info : drawing_infos) {
2772 *bmain, *object, object_to_layer, keep_world_transform, paste_on_back, info.drawing);
2773 }
2774
2775 if (inserted_keyframe) {
2777 }
2778 }
2779 else if (type == PasteType::ByLayer) {
2780 Layer *active_layer = grease_pencil.get_active_layer();
2781 /* Find layers to paste strokes into. */
2782 Array<Layer *> layers_to_paste_into(clipboard.layers.size());
2783 for (const int clip_layer_i : clipboard.layers.index_range()) {
2784 const Clipboard::ClipboardLayer &layer = clipboard.layers[clip_layer_i];
2785 bke::greasepencil::TreeNode *node = grease_pencil.find_node_by_name(layer.name);
2786 const bool found_layer = node && node->is_layer() && node->as_layer().is_editable();
2787 if (found_layer) {
2788 layers_to_paste_into[clip_layer_i] = &node->as_layer();
2789 continue;
2790 }
2791 if (active_layer && active_layer->is_editable()) {
2792 /* Fall back to active layer. */
2793 BKE_report(
2794 op->reports, RPT_WARNING, "Couldn't find matching layer, pasting into active layer");
2795 layers_to_paste_into[clip_layer_i] = active_layer;
2796 continue;
2797 }
2798
2799 if (!active_layer) {
2800 BKE_report(op->reports, RPT_ERROR, "No active Grease Pencil layer to paste into");
2801 }
2802 if (!active_layer->is_editable()) {
2803 BKE_report(op->reports, RPT_ERROR, "Active layer is not editable");
2804 }
2805 return OPERATOR_CANCELLED;
2806 }
2807
2808 /* Deselect everything from editable drawings. The pasted strokes are the only ones then after
2809 * the paste. That's convenient for the user. */
2810 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
2811 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
2813 info.drawing.strokes_for_write(), selection_domain, bke::AttrType::Bool);
2814 ed::curves::fill_selection_false(selection_in_target.span);
2815 selection_in_target.finish();
2816 });
2817
2818 for (const int clip_layer_i : clipboard.layers.index_range()) {
2819 const Clipboard::ClipboardLayer &clip_layer = clipboard.layers[clip_layer_i];
2820 const bke::CurvesGeometry &curves_to_paste = clip_layer.curves;
2821
2822 BLI_assert(layers_to_paste_into[clip_layer_i] != nullptr);
2823 Layer &paste_layer = *layers_to_paste_into[clip_layer_i];
2824 const float4x4 object_to_paste_layer = math::invert(paste_layer.to_object_space(*object));
2825
2826 /* Ensure active keyframe. */
2827 bool inserted_keyframe = false;
2828 if (!ensure_active_keyframe(scene, grease_pencil, paste_layer, false, inserted_keyframe)) {
2829 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil frame to draw on");
2830 return OPERATOR_CANCELLED;
2831 }
2832
2833 Vector<MutableDrawingInfo> drawing_infos =
2835 scene, grease_pencil, paste_layer);
2836 for (const MutableDrawingInfo info : drawing_infos) {
2838 *object,
2839 curves_to_paste,
2840 object_to_paste_layer,
2841 clipboard.object_to_world,
2842 keep_world_transform,
2843 paste_on_back,
2844 info.drawing);
2845 }
2846
2847 if (inserted_keyframe) {
2849 }
2850 }
2851 }
2852 else {
2854 }
2855
2856 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
2857 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
2858
2859 return OPERATOR_FINISHED;
2860}
2861
2863{
2865 return false;
2866 }
2867
2868 std::scoped_lock lock(grease_pencil_clipboard_lock);
2869 /* Check for curves in the Grease Pencil clipboard. */
2870 return (grease_pencil_clipboard && grease_pencil_clipboard->layers.size() > 0);
2871}
2872
2874{
2875 PropertyRNA *prop;
2876
2877 static const EnumPropertyItem rna_paste_items[] = {
2878 {int(PasteType::Active), "ACTIVE", 0, "Paste to Active", ""},
2879 {int(PasteType::ByLayer), "LAYER", 0, "Paste by Layer", ""},
2880 {0, nullptr, 0, nullptr, nullptr},
2881 };
2882
2883 ot->name = "Paste Strokes";
2884 ot->idname = "GREASE_PENCIL_OT_paste";
2885 ot->description =
2886 "Paste Grease Pencil points or strokes from the internal clipboard to the active layer";
2887
2890
2891 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2892
2893 ot->prop = RNA_def_enum(ot->srna, "type", rna_paste_items, int(PasteType::Active), "Type", "");
2894
2895 prop = RNA_def_boolean(
2896 ot->srna, "paste_back", false, "Paste on Back", "Add pasted strokes behind all strokes");
2898
2899 prop = RNA_def_boolean(ot->srna,
2900 "keep_world_transform",
2901 false,
2902 "Keep World Transform",
2903 "Keep the world transform of strokes from the clipboard unchanged");
2905}
2906
2908
2910 Object &object,
2911 const float4x4 &object_to_paste_layer,
2912 const bool keep_world_transform,
2913 const bool paste_back,
2915{
2917 if (clipboard.layers.is_empty()) {
2918 return {};
2919 }
2920
2921 Vector<bke::GeometrySet> geometries_to_join;
2922 for (Clipboard::ClipboardLayer &layer : clipboard.layers) {
2924 }
2925 bke::GeometrySet joined_clipboard_set = geometry::join_geometries(geometries_to_join.as_span(),
2926 {});
2927 BLI_assert(joined_clipboard_set.has_curves());
2928 const bke::CurvesGeometry &joined_clipboard_curves =
2929 joined_clipboard_set.get_curves()->geometry.wrap();
2930
2931 return clipboard_paste_strokes_ex(bmain,
2932 object,
2933 joined_clipboard_curves,
2934 object_to_paste_layer,
2935 clipboard.object_to_world,
2936 keep_world_transform,
2937 paste_back,
2938 drawing);
2939}
2940
2941/* -------------------------------------------------------------------- */
2945{
2946 const Scene *scene = CTX_data_scene(C);
2947 Object *object = CTX_data_active_object(C);
2948 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
2949
2950 const float threshold = RNA_float_get(op->ptr, "threshold");
2951 const bool use_unselected = RNA_boolean_get(op->ptr, "use_unselected");
2952
2953 std::atomic<bool> changed = false;
2954
2955 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
2956 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
2957 bke::greasepencil::Drawing &drawing = info.drawing;
2958 IndexMaskMemory memory;
2959 const IndexMask points = use_unselected ?
2961 *object, drawing, info.layer_index, memory) :
2963 *object, info.drawing, info.layer_index, memory);
2964 if (points.is_empty()) {
2965 return;
2966 }
2968 drawing.strokes(), threshold, points, {});
2969 drawing.tag_topology_changed();
2970 changed.store(true, std::memory_order_relaxed);
2971 });
2972 if (changed) {
2973 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
2974 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
2975 }
2976 return OPERATOR_FINISHED;
2977}
2978
2980{
2981 PropertyRNA *prop;
2982
2983 ot->name = "Merge by Distance";
2984 ot->idname = "GREASE_PENCIL_OT_stroke_merge_by_distance";
2985 ot->description = "Merge points by distance";
2986
2989
2990 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2991
2992 prop = RNA_def_float(ot->srna, "threshold", 0.001f, 0.0f, 100.0f, "Threshold", "", 0.0f, 100.0f);
2993 /* Avoid re-using last var. */
2995
2996 prop = RNA_def_boolean(ot->srna,
2997 "use_unselected",
2998 false,
2999 "Unselected",
3000 "Use whole stroke, not only selected points");
3002}
3003
3005
3006/* -------------------------------------------------------------------- */
3009
3011 const IndexMask &points_to_extrude)
3012{
3013 const OffsetIndices<int> points_by_curve = src.points_by_curve();
3014
3015 const int old_curves_num = src.curves_num();
3016 const int old_points_num = src.points_num();
3017
3018 Vector<int> dst_to_src_points(old_points_num);
3020
3021 Vector<int> dst_to_src_curves(old_curves_num);
3023
3024 Vector<bool> dst_selected(old_points_num, false);
3025
3026 Vector<int> dst_curve_counts(old_curves_num);
3028 points_by_curve, src.curves_range(), dst_curve_counts.as_mutable_span());
3029
3030 const VArray<bool> &src_cyclic = src.cyclic();
3031
3032 /* Point offset keeps track of the points inserted. */
3033 int point_offset = 0;
3034 for (const int curve_index : src.curves_range()) {
3035 const IndexRange curve_points = points_by_curve[curve_index];
3036 const IndexMask curve_points_to_extrude = points_to_extrude.slice_content(curve_points);
3037 const bool curve_cyclic = src_cyclic[curve_index];
3038
3039 curve_points_to_extrude.foreach_index([&](const int src_point_index) {
3040 if (!curve_cyclic && (src_point_index == curve_points.first())) {
3041 /* Start-point extruded, we insert a new point at the beginning of the curve.
3042 * NOTE: all points of a cyclic curve behave like an inner-point. */
3043 dst_to_src_points.insert(src_point_index + point_offset, src_point_index);
3044 dst_selected.insert(src_point_index + point_offset, true);
3045 ++dst_curve_counts[curve_index];
3046 ++point_offset;
3047 return;
3048 }
3049 if (!curve_cyclic && (src_point_index == curve_points.last())) {
3050 /* End-point extruded, we insert a new point at the end of the curve.
3051 * NOTE: all points of a cyclic curve behave like an inner-point. */
3052 dst_to_src_points.insert(src_point_index + point_offset + 1, src_point_index);
3053 dst_selected.insert(src_point_index + point_offset + 1, true);
3054 ++dst_curve_counts[curve_index];
3055 ++point_offset;
3056 return;
3057 }
3058
3059 /* Inner-point extruded: we create a new curve made of two points located at the same
3060 * position. Only one of them is selected so that the other one remains stuck to the curve.
3061 */
3062 dst_to_src_points.append(src_point_index);
3063 dst_selected.append(false);
3064 dst_to_src_points.append(src_point_index);
3065 dst_selected.append(true);
3066 dst_to_src_curves.append(curve_index);
3067 dst_curve_counts.append(2);
3068 });
3069 }
3070
3071 const int new_points_num = dst_to_src_points.size();
3072 const int new_curves_num = dst_to_src_curves.size();
3073
3074 bke::CurvesGeometry dst(new_points_num, new_curves_num);
3076
3077 /* Setup curve offsets, based on the number of points in each curve. */
3078 MutableSpan<int> new_curve_offsets = dst.offsets_for_write();
3079 array_utils::copy(dst_curve_counts.as_span(), new_curve_offsets.drop_back(1));
3081
3082 /* Attributes. */
3083 const bke::AttributeAccessor src_attributes = src.attributes();
3085
3086 /* Selection attribute. */
3087 /* Copy the value of control point selections to all selection attributes.
3088 *
3089 * This will lead to the extruded control point always having both handles selected, if it's a
3090 * bezier type stroke. This is to circumvent the issue of source curves handles not being
3091 * deselected when the user extrudes a bezier control point with both handles selected. */
3092 for (const StringRef selection_attribute_name :
3094 {
3096 dst, bke::AttrDomain::Point, bke::AttrType::Bool, selection_attribute_name);
3097 selection.span.copy_from(dst_selected.as_span());
3098 selection.finish();
3099 }
3100
3101 bke::gather_attributes(src_attributes,
3104 {},
3105 dst_to_src_curves,
3106 dst_attributes);
3107
3108 /* Cyclic attribute : newly created curves cannot be cyclic. */
3109 dst.cyclic_for_write().drop_front(old_curves_num).fill(false);
3110
3111 bke::gather_attributes(src_attributes,
3115 {".selection", ".selection_handle_left", ".selection_handle_right"}),
3116 dst_to_src_points,
3117 dst_attributes);
3118
3119 dst.update_curve_types();
3120 if (src.nurbs_has_custom_knots()) {
3121 IndexMaskMemory memory;
3122 const VArray<int8_t> curve_types = src.curve_types();
3123 const VArray<int8_t> knot_modes = dst.nurbs_knots_modes();
3124 const OffsetIndices<int> dst_points_by_curve = dst.points_by_curve();
3125 const IndexMask include_curves = IndexMask::from_predicate(
3126 src.curves_range(), GrainSize(512), memory, [&](const int64_t curve_index) {
3127 return curve_types[curve_index] == CURVE_TYPE_NURBS &&
3128 knot_modes[curve_index] == NURBS_KNOT_MODE_CUSTOM &&
3129 points_by_curve[curve_index].size() == dst_points_by_curve[curve_index].size();
3130 });
3132 include_curves.complement(dst.curves_range(), memory),
3135 dst);
3136 bke::curves::nurbs::gather_custom_knots(src, include_curves, 0, dst);
3137 }
3138 return dst;
3139}
3140
3142{
3143 const Scene *scene = CTX_data_scene(C);
3144 Object *object = CTX_data_active_object(C);
3145 View3D *v3d = CTX_wm_view3d(C);
3146 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
3147
3148 std::atomic<bool> changed = false;
3149 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
3150 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3151 IndexMaskMemory memory;
3152 const IndexMask points_to_extrude =
3154 *object, info.drawing, info.layer_index, v3d->overlay.handle_display, memory);
3155 if (points_to_extrude.is_empty()) {
3156 return;
3157 }
3158
3159 const bke::CurvesGeometry &curves = info.drawing.strokes();
3160 info.drawing.strokes_for_write() = extrude_grease_pencil_curves(curves, points_to_extrude);
3161
3163 changed.store(true, std::memory_order_relaxed);
3164 });
3165
3166 if (changed) {
3167 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
3168 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
3169 }
3170
3171 return OPERATOR_FINISHED;
3172}
3173
3175{
3176 ot->name = "Extrude Stroke Points";
3177 ot->idname = "GREASE_PENCIL_OT_extrude";
3178 ot->description = "Extrude the selected points";
3179
3182
3183 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3184}
3185
3187
3188/* -------------------------------------------------------------------- */
3191
3192/* Determine how much the radius needs to be scaled to look the same from the view. */
3194 const float3 &old_pos,
3195 const float3 &new_pos)
3196{
3197 /* Don't scale the radius when the view is orthographic. */
3198 if (!rv3d->is_persp) {
3199 return 1.0f;
3200 }
3201
3202 const float3 view_center = float3(rv3d->viewinv[3]);
3203 return math::length(new_pos - view_center) / math::length(old_pos - view_center);
3204}
3205
3207{
3208 Scene &scene = *CTX_data_scene(C);
3210
3211 View3D *v3d = CTX_wm_view3d(C);
3212 ARegion *region = CTX_wm_region(C);
3213
3214 RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
3215
3216 const ReprojectMode mode = ReprojectMode(RNA_enum_get(op->ptr, "type"));
3217 const bool keep_original = RNA_boolean_get(op->ptr, "keep_original");
3218
3219 Object *object = CTX_data_active_object(C);
3220 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
3221 const float offset = RNA_float_get(op->ptr, "offset");
3222
3223 /* Init snap context for geometry projection. */
3225 [&]() -> transform::SnapObjectContext * {
3226 if (mode == ReprojectMode::Surface) {
3227 return transform::snap_object_context_create(&scene, 0);
3228 }
3229 return nullptr;
3230 });
3231
3233 scene.toolsettings);
3234
3235 const int oldframe = int(DEG_get_ctime(depsgraph));
3236 if (keep_original) {
3237 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
3238 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3239 IndexMaskMemory memory;
3241 *object, info.drawing, info.layer_index, selection_domain, memory);
3242 if (elements.is_empty()) {
3243 return;
3244 }
3245
3247 if (selection_domain == bke::AttrDomain::Curve) {
3249 }
3250 else if (selection_domain == bke::AttrDomain::Point) {
3252 }
3254 });
3255 }
3256
3257 /* TODO: This can probably be optimized further for the non-Surface projection use case by
3258 * considering all drawings for the parallel loop instead of having to partition by frame number.
3259 */
3260 std::atomic<bool> changed = false;
3261 Array<Vector<MutableDrawingInfo>> drawings_per_frame =
3263 for (const Span<MutableDrawingInfo> drawings : drawings_per_frame) {
3264 if (drawings.is_empty()) {
3265 continue;
3266 }
3267 const int current_frame_number = drawings.first().frame_number;
3268
3269 if (mode == ReprojectMode::Surface) {
3270 scene.r.cfra = current_frame_number;
3272 }
3273
3274 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3276 MutableSpan<float> radii = curves.radius_for_write();
3277
3278 IndexMaskMemory memory;
3279 const IndexMask editable_points = retrieve_editable_points(
3280 *object, info.drawing, info.layer_index, memory);
3281
3283 curves, CURVE_TYPE_BEZIER, memory);
3284
3285 for (const StringRef selection_name :
3287 {
3288 const IndexMask selected_points = ed::curves::retrieve_selected_points(
3289 curves, selection_name, bezier_points, memory);
3290 const IndexMask points_to_reproject = IndexMask::from_intersection(
3291 editable_points, selected_points, memory);
3292
3293 if (points_to_reproject.is_empty()) {
3294 return;
3295 }
3296
3297 MutableSpan<float3> positions = curves.positions_for_write();
3298 if (selection_name == ".selection_handle_left") {
3299 positions = curves.handle_positions_left_for_write();
3300 }
3301 else if (selection_name == ".selection_handle_right") {
3302 positions = curves.handle_positions_right_for_write();
3303 }
3304
3305 const bke::greasepencil::Layer &layer = grease_pencil.layer(info.layer_index);
3306 if (mode == ReprojectMode::Surface) {
3307 const float4x4 layer_space_to_world_space = layer.to_world_space(*object);
3308 const float4x4 world_space_to_layer_space = math::invert(layer_space_to_world_space);
3309 points_to_reproject.foreach_index(GrainSize(4096), [&](const int point_i) {
3310 float3 &position = positions[point_i];
3311 const float3 world_pos = math::transform_point(layer_space_to_world_space, position);
3312 float2 screen_co;
3313 if (ED_view3d_project_float_global(region, world_pos, screen_co, V3D_PROJ_TEST_NOP) !=
3315 {
3316 return;
3317 }
3318
3319 float3 ray_start, ray_direction;
3321 depsgraph, region, v3d, screen_co, ray_start, ray_direction, true))
3322 {
3323 return;
3324 }
3325
3326 float hit_depth = std::numeric_limits<float>::max();
3327 float3 hit_position(0.0f);
3328 float3 hit_normal(0.0f);
3329
3331 params.snap_target_select = SCE_SNAP_TARGET_ALL;
3332 transform::SnapObjectContext *snap_context = thread_snap_contexts.local();
3333 if (transform::snap_object_project_ray(snap_context,
3334 depsgraph,
3335 v3d,
3336 &params,
3337 ray_start,
3338 ray_direction,
3339 &hit_depth,
3340 hit_position,
3341 hit_normal))
3342 {
3343 /* Apply offset over surface. */
3344 const float3 new_pos = math::transform_point(
3345 world_space_to_layer_space,
3346 hit_position + math::normalize(ray_start - hit_position) * offset);
3347
3348 if (selection_name == ".selection") {
3349 radii[point_i] *= calculate_radius_projection_factor(rv3d, position, new_pos);
3350 }
3351 position = new_pos;
3352 }
3353 });
3354 }
3355 else {
3356 const DrawingPlacement drawing_placement(
3357 scene, *region, *v3d, *object, &layer, mode, offset, nullptr);
3358 points_to_reproject.foreach_index(GrainSize(4096), [&](const int point_i) {
3359 const float3 new_pos = drawing_placement.reproject(positions[point_i]);
3360 if (selection_name == ".selection") {
3361 radii[point_i] *= calculate_radius_projection_factor(
3362 rv3d, positions[point_i], new_pos);
3363 }
3364 positions[point_i] = new_pos;
3365 });
3366 }
3367
3369 changed.store(true, std::memory_order_relaxed);
3370 }
3371 });
3372 }
3373
3374 for (transform::SnapObjectContext *snap_context : thread_snap_contexts) {
3375 if (snap_context != nullptr) {
3377 }
3378 }
3379
3380 if (mode == ReprojectMode::Surface) {
3381 scene.r.cfra = oldframe;
3383 }
3384
3385 if (changed) {
3386 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
3387 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
3388 }
3389
3390 return OPERATOR_FINISHED;
3391}
3392
3394{
3395 uiLayout *layout = op->layout;
3396 uiLayout *row;
3397
3398 const ReprojectMode type = ReprojectMode(RNA_enum_get(op->ptr, "type"));
3399
3400 layout->use_property_split_set(true);
3401 layout->use_property_decorate_set(false);
3402 row = &layout->row(true);
3403 row->prop(op->ptr, "type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
3404
3405 if (type == ReprojectMode::Surface) {
3406 row = &layout->row(true);
3407 row->prop(op->ptr, "offset", UI_ITEM_NONE, std::nullopt, ICON_NONE);
3408 }
3409 row = &layout->row(true);
3410 row->prop(op->ptr, "keep_original", UI_ITEM_NONE, std::nullopt, ICON_NONE);
3411}
3412
3414{
3415 static const EnumPropertyItem reproject_type[] = {
3417 "FRONT",
3418 0,
3419 "Front",
3420 "Reproject the strokes using the X-Z plane"},
3421 {int(ReprojectMode::Side), "SIDE", 0, "Side", "Reproject the strokes using the Y-Z plane"},
3422 {int(ReprojectMode::Top), "TOP", 0, "Top", "Reproject the strokes using the X-Y plane"},
3423 {int(ReprojectMode::View),
3424 "VIEW",
3425 0,
3426 "View",
3427 "Reproject the strokes to end up on the same plane, as if drawn from the current "
3428 "viewpoint "
3429 "using 'Cursor' Stroke Placement"},
3431 "SURFACE",
3432 0,
3433 "Surface",
3434 "Reproject the strokes on to the scene geometry, as if drawn using 'Surface' placement"},
3436 "CURSOR",
3437 0,
3438 "Cursor",
3439 "Reproject the strokes using the orientation of 3D cursor"},
3440 {0, nullptr, 0, nullptr, nullptr},
3441 };
3442
3443 /* identifiers */
3444 ot->name = "Reproject Strokes";
3445 ot->idname = "GREASE_PENCIL_OT_reproject";
3446 ot->description =
3447 "Reproject the selected strokes from the current viewpoint as if they had been newly "
3448 "drawn "
3449 "(e.g. to fix problems from accidental 3D cursor movement or accidental viewport changes, "
3450 "or for matching deforming geometry)";
3451
3452 /* callbacks */
3453 ot->invoke = WM_menu_invoke;
3457
3458 /* flags */
3459 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3460
3461 /* properties */
3462 ot->prop = RNA_def_enum(
3463 ot->srna, "type", reproject_type, int(ReprojectMode::View), "Projection Type", "");
3464
3466 ot->srna,
3467 "keep_original",
3468 false,
3469 "Keep Original",
3470 "Keep original strokes and create a copy before reprojecting");
3472
3473 RNA_def_float(ot->srna, "offset", 0.0f, 0.0f, 10.0f, "Surface Offset", "", 0.0f, 10.0f);
3474}
3475
3477/* -------------------------------------------------------------------- */
3480
3481/* Poll callback for snap operators */
3482/* NOTE: For now, we only allow these in the 3D view, as other editors do not
3483 * define a cursor or grid-step which can be used.
3484 */
3486{
3488 return false;
3489 }
3490
3491 const ScrArea *area = CTX_wm_area(C);
3492 if (!(area && area->spacetype == SPACE_VIEW3D)) {
3493 return false;
3494 }
3495 const ARegion *region = CTX_wm_region(C);
3496 if (!(region && region->regiontype == RGN_TYPE_WINDOW)) {
3497 return false;
3498 }
3499
3500 return true;
3501}
3502
3504{
3506
3507 const Scene &scene = *CTX_data_scene(C);
3508 Object &object = *CTX_data_active_object(C);
3509 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
3510 const View3D &v3d = *CTX_wm_view3d(C);
3511 const ARegion &region = *CTX_wm_region(C);
3512 const float grid_size = ED_view3d_grid_view_scale(&scene, &v3d, &region, nullptr);
3513
3514 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
3515 for (const MutableDrawingInfo &drawing_info : drawings) {
3516 bke::CurvesGeometry &curves = drawing_info.drawing.strokes_for_write();
3517 if (curves.is_empty()) {
3518 continue;
3519 }
3521 continue;
3522 }
3523
3524 IndexMaskMemory memory;
3526 curves, CURVE_TYPE_BEZIER, memory);
3527
3529 {
3530 const IndexMask selected_points = ed::curves::retrieve_selected_points(
3531 curves, selection_name, bezier_points, memory);
3532
3533 const Layer &layer = grease_pencil.layer(drawing_info.layer_index);
3534 const float4x4 layer_to_world = layer.to_world_space(object);
3535 const float4x4 world_to_layer = math::invert(layer_to_world);
3536
3537 MutableSpan<float3> positions = curves.positions_for_write();
3538 if (selection_name == ".selection_handle_left") {
3539 positions = curves.handle_positions_left_for_write();
3540 }
3541 else if (selection_name == ".selection_handle_right") {
3542 positions = curves.handle_positions_right_for_write();
3543 }
3544 selected_points.foreach_index(GrainSize(4096), [&](const int point_i) {
3545 const float3 pos_world = math::transform_point(layer_to_world, positions[point_i]);
3546 const float3 pos_snapped = grid_size * math::floor(pos_world / grid_size + 0.5f);
3547 positions[point_i] = math::transform_point(world_to_layer, pos_snapped);
3548 });
3549 }
3550
3551 drawing_info.drawing.tag_positions_changed();
3555 WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, &grease_pencil);
3556 }
3557
3558 return OPERATOR_FINISHED;
3559}
3560
3562{
3563 ot->name = "Snap Selection to Grid";
3564 ot->idname = "GREASE_PENCIL_OT_snap_to_grid";
3565 ot->description = "Snap selected points to the nearest grid points";
3566
3569
3570 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3571}
3572
3574
3575/* -------------------------------------------------------------------- */
3578
3580{
3582
3583 const Scene &scene = *CTX_data_scene(C);
3584 Object &object = *CTX_data_active_object(C);
3585 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
3586 const bool use_offset = RNA_boolean_get(op->ptr, "use_offset");
3587 const float3 cursor_world = scene.cursor.location;
3588
3589 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
3590 for (const MutableDrawingInfo &drawing_info : drawings) {
3591 bke::CurvesGeometry &curves = drawing_info.drawing.strokes_for_write();
3592 if (curves.is_empty()) {
3593 continue;
3594 }
3596 continue;
3597 }
3598
3599 IndexMaskMemory selected_points_memory;
3601 selected_points_memory);
3602
3603 const Layer &layer = grease_pencil.layer(drawing_info.layer_index);
3604 const float4x4 layer_to_world = layer.to_world_space(object);
3605 const float4x4 world_to_layer = math::invert(layer_to_world);
3606 const float3 cursor_layer = math::transform_point(world_to_layer, cursor_world);
3607
3608 MutableSpan<float3> positions = curves.positions_for_write();
3609 if (use_offset) {
3610 const OffsetIndices points_by_curve = curves.points_by_curve();
3611 IndexMaskMemory selected_curves_memory;
3612 const IndexMask selected_curves = ed::curves::retrieve_selected_curves(
3613 curves, selected_curves_memory);
3614
3615 selected_curves.foreach_index(GrainSize(512), [&](const int curve_i) {
3616 const IndexRange points = points_by_curve[curve_i];
3617
3618 /* Offset from first point of the curve. */
3619 const float3 offset = cursor_layer - positions[points.first()];
3620 selected_points.slice_content(points).foreach_index(
3621 GrainSize(4096), [&](const int point_i) { positions[point_i] += offset; });
3622 });
3623 }
3624 else {
3625 /* Set all selected positions to the cursor location. */
3626 index_mask::masked_fill(positions, cursor_layer, selected_points);
3627 }
3628
3629 curves.calculate_bezier_auto_handles();
3630 drawing_info.drawing.tag_positions_changed();
3634 WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, &grease_pencil);
3635 }
3636
3637 return OPERATOR_FINISHED;
3638}
3639
3641{
3642 /* identifiers */
3643 ot->name = "Snap Selection to Cursor";
3644 ot->idname = "GREASE_PENCIL_OT_snap_to_cursor";
3645 ot->description = "Snap selected points/strokes to the cursor";
3646
3647 /* callbacks */
3650
3651 /* flags */
3652 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3653
3654 /* props */
3655 ot->prop = RNA_def_boolean(ot->srna,
3656 "use_offset",
3657 true,
3658 "With Offset",
3659 "Offset the entire stroke instead of selected points only");
3660}
3661
3663
3664/* -------------------------------------------------------------------- */
3667
3669 const Object &object,
3670 const GreasePencil &grease_pencil,
3671 float3 &r_centroid,
3672 float3 &r_min,
3673 float3 &r_max)
3674{
3676
3677 int num_selected = 0;
3678 r_centroid = float3(0.0f);
3679 r_min = float3(std::numeric_limits<float>::max());
3680 r_max = float3(std::numeric_limits<float>::lowest());
3681
3682 const Vector<DrawingInfo> drawings = retrieve_visible_drawings(scene, grease_pencil, false);
3683 for (const DrawingInfo &drawing_info : drawings) {
3684 const Layer &layer = grease_pencil.layer(drawing_info.layer_index);
3685 if (layer.is_locked()) {
3686 continue;
3687 }
3688 const bke::CurvesGeometry &curves = drawing_info.drawing.strokes();
3689 if (curves.is_empty()) {
3690 continue;
3691 }
3693 continue;
3694 }
3695
3696 IndexMaskMemory selected_points_memory;
3698 selected_points_memory);
3699 const float4x4 layer_to_world = layer.to_world_space(object);
3700
3701 Span<float3> positions = curves.positions();
3702 selected_points.foreach_index(GrainSize(4096), [&](const int point_i) {
3703 const float3 pos_world = math::transform_point(layer_to_world, positions[point_i]);
3704 r_centroid += pos_world;
3705 math::min_max(pos_world, r_min, r_max);
3706 });
3707 num_selected += selected_points.size();
3708 }
3709 if (num_selected == 0) {
3710 r_min = r_max = float3(0.0f);
3711 return false;
3712 }
3713
3714 r_centroid /= num_selected;
3715 return true;
3716}
3717
3719{
3720 Scene &scene = *CTX_data_scene(C);
3721 const Object &object = *CTX_data_active_object(C);
3722 const GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
3723 float3 &cursor = reinterpret_cast<float3 &>(scene.cursor.location);
3724
3725 float3 centroid, points_min, points_max;
3727 scene, object, grease_pencil, centroid, points_min, points_max))
3728 {
3729 return OPERATOR_FINISHED;
3730 }
3731
3732 switch (scene.toolsettings->transform_pivot_point) {
3734 cursor = math::midpoint(points_min, points_max);
3735 break;
3737 case V3D_AROUND_CURSOR:
3739 case V3D_AROUND_ACTIVE:
3740 cursor = centroid;
3741 break;
3742 default:
3744 }
3745
3748
3749 return OPERATOR_FINISHED;
3750}
3751
3753{
3754 /* identifiers */
3755 ot->name = "Snap Cursor to Selected Points";
3756 ot->idname = "GREASE_PENCIL_OT_snap_cursor_to_selected";
3757 ot->description = "Snap cursor to center of selected points";
3758
3759 /* callbacks */
3762
3763 /* flags */
3764 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3765}
3766
3768{
3769 float4x3 strokemat4x3 = float4x3(strokemat);
3770
3771 /*
3772 * We need the diagonal of ones to start from the bottom right instead top left to properly
3773 * apply the two matrices.
3774 *
3775 * i.e.
3776 * # # # # # # # #
3777 * We need # # # # Instead of # # # #
3778 * 0 0 0 1 0 0 1 0
3779 *
3780 */
3781 strokemat4x3[2][2] = 0.0f;
3782 strokemat4x3[3][2] = 1.0f;
3783
3784 return strokemat4x3;
3785}
3786
3788{
3789 const Scene *scene = CTX_data_scene(C);
3790 Object *object = CTX_data_active_object(C);
3791 ARegion *region = CTX_wm_region(C);
3792 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
3793
3794 std::atomic<bool> changed = false;
3795 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
3796 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3797 IndexMaskMemory memory;
3799 *object, info.drawing, info.layer_index, memory);
3800 if (strokes.is_empty()) {
3801 return;
3802 }
3803
3804 const bke::greasepencil::Layer &layer = grease_pencil.layer(info.layer_index);
3805 const float4x4 layer_space_to_world_space = layer.to_world_space(*object);
3806
3807 /* Calculate screen space points. */
3808 const float2 screen_start(RNA_int_get(op->ptr, "xstart"), RNA_int_get(op->ptr, "ystart"));
3809 const float2 screen_end(RNA_int_get(op->ptr, "xend"), RNA_int_get(op->ptr, "yend"));
3810 const float2 screen_direction = screen_end - screen_start;
3811 const float2 screen_tangent = screen_start + float2(-screen_direction[1], screen_direction[0]);
3812
3813 const bke::CurvesGeometry &curves = info.drawing.strokes();
3814 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
3815 const Span<float3> positions = curves.positions();
3817 const VArray<int> materials = *curves.attributes().lookup_or_default<int>(
3818 "material_index", bke::AttrDomain::Curve, 0);
3819
3820 Array<float4x2> texture_matrices(strokes.size());
3821
3822 strokes.foreach_index([&](const int curve_i, const int pos) {
3823 const int material_index = materials[curve_i];
3824
3825 const MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(object,
3826 material_index + 1);
3827 const bool is_radial = gp_style->gradient_type == GP_MATERIAL_GRADIENT_RADIAL;
3828
3829 const float texture_angle = gp_style->texture_angle;
3830 const float2 texture_scale = float2(gp_style->texture_scale);
3831 const float2 texture_offset = float2(gp_style->texture_offset);
3832
3833 const float2x2 texture_rotation = math::from_rotation<float2x2>(
3834 math::AngleRadian(texture_angle));
3835
3836 const float3 point = math::transform_point(layer_space_to_world_space,
3837 positions[points_by_curve[curve_i].first()]);
3838 const float3 normal = math::transform_direction(layer_space_to_world_space,
3839 normals[curve_i]);
3840
3841 const float4 plane = float4(normal, -math::dot(normal, point));
3842
3843 float3 start;
3844 float3 tangent;
3845 float3 end;
3846 ED_view3d_win_to_3d_on_plane(region, plane, screen_start, false, start);
3847 ED_view3d_win_to_3d_on_plane(region, plane, screen_tangent, false, tangent);
3848 ED_view3d_win_to_3d_on_plane(region, plane, screen_end, false, end);
3849
3850 const float3 origin = start;
3851 /* Invert the length by dividing by the length squared. */
3852 const float3 u_dir = (end - origin) / math::length_squared(end - origin);
3853 float3 v_dir = math::cross(u_dir, normal);
3854
3855 /* Flip the texture if need so that it is not mirrored. */
3856 if (math::dot(tangent - start, v_dir) < 0.0f) {
3857 v_dir = -v_dir;
3858 }
3859
3860 /* Calculate the texture space before the texture offset transformation. */
3861 const float4x2 base_texture_space = math::transpose(float2x4(
3862 float4(u_dir, -math::dot(u_dir, origin)), float4(v_dir, -math::dot(v_dir, origin))));
3863
3864 float3x2 offset_matrix = float3x2::identity();
3865
3866 if (is_radial) {
3867 /* Radial gradients are scaled down by a factor of 2 and have the center at 0.5 */
3868 offset_matrix *= 0.5f;
3869 offset_matrix[2] += float2(0.5f, 0.5f);
3870 }
3871
3872 /* For some reason 0.5 is added to the offset before being rendered, so remove it here. */
3873 offset_matrix[2] -= float2(0.5f, 0.5f);
3874
3875 offset_matrix = math::from_scale<float2x2>(texture_scale) * offset_matrix;
3876 offset_matrix = texture_rotation * offset_matrix;
3877 offset_matrix[2] -= texture_offset;
3878
3879 texture_matrices[pos] = (offset_matrix * expand_4x2_mat(base_texture_space)) *
3880 layer_space_to_world_space;
3881 });
3882
3883 info.drawing.set_texture_matrices(texture_matrices, strokes);
3884
3885 changed.store(true, std::memory_order_relaxed);
3886 });
3887
3888 if (changed) {
3889 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
3890 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
3891 }
3892
3894}
3895
3897 wmOperator *op,
3898 const wmEvent *event)
3899{
3901
3902 /* Check for mouse release. */
3903 if ((ret & OPERATOR_RUNNING_MODAL) != 0 && event->type == LEFTMOUSE && event->val == KM_RELEASE)
3904 {
3908 }
3909
3910 return ret;
3911}
3912
3914 wmOperator *op,
3915 const wmEvent *event)
3916{
3917 /* Invoke interactive line drawing (representing the gradient) in viewport. */
3919
3920 if ((ret & OPERATOR_RUNNING_MODAL) != 0) {
3921 ARegion *region = CTX_wm_region(C);
3922 if (region->regiontype == RGN_TYPE_WINDOW && event->type == LEFTMOUSE &&
3923 event->val == KM_PRESS)
3924 {
3925 wmGesture *gesture = static_cast<wmGesture *>(op->customdata);
3926 gesture->is_active = true;
3927 }
3928 }
3929
3930 return ret;
3931}
3932
3934{
3935 /* Identifiers. */
3936 ot->name = "Texture Gradient";
3937 ot->idname = "GREASE_PENCIL_OT_texture_gradient";
3938 ot->description = "Draw a line to set the fill material gradient for the selected strokes";
3939
3940 /* API callbacks. */
3946
3947 /* Flags. */
3948 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3949
3951}
3952
3954
3955/* -------------------------------------------------------------------- */
3958
3960{
3961 const Scene *scene = CTX_data_scene(C);
3962 Object *object = CTX_data_active_object(C);
3963 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
3964
3965 const CurveType dst_type = CurveType(RNA_enum_get(op->ptr, "type"));
3966 const bool use_handles = RNA_boolean_get(op->ptr, "use_handles");
3967
3968 bool changed = false;
3969 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
3970 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3972 IndexMaskMemory memory;
3974 *object, info.drawing, info.layer_index, memory);
3975 if (strokes.is_empty()) {
3976 return;
3977 }
3978
3980 options.convert_bezier_handles_to_poly_points = use_handles;
3981 options.convert_bezier_handles_to_catmull_rom_points = use_handles;
3982 options.keep_bezier_shape_as_nurbs = use_handles;
3983 options.keep_catmull_rom_shape_as_nurbs = use_handles;
3984
3985 curves = geometry::convert_curves(curves, strokes, dst_type, {}, options);
3987
3988 changed = true;
3989 });
3990
3991 if (changed) {
3992 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
3993 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
3994 }
3995
3996 return OPERATOR_FINISHED;
3997}
3998
4000{
4001 ot->name = "Set Curve Type";
4002 ot->idname = "GREASE_PENCIL_OT_set_curve_type";
4003 ot->description = "Set type of selected curves";
4004
4005 ot->invoke = WM_menu_invoke;
4008
4009 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4010
4011 ot->prop = RNA_def_enum(
4012 ot->srna, "type", rna_enum_curves_type_items, CURVE_TYPE_POLY, "Type", "Curve type");
4013
4014 RNA_def_boolean(ot->srna,
4015 "use_handles",
4016 false,
4017 "Handles",
4018 "Take handle information into account in the conversion");
4019}
4020
4022
4023/* -------------------------------------------------------------------- */
4026
4028{
4029 const Scene *scene = CTX_data_scene(C);
4030 Object *object = CTX_data_active_object(C);
4031 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
4032
4033 using namespace ed::curves;
4034 const SetHandleType dst_type = SetHandleType(RNA_enum_get(op->ptr, "type"));
4035
4036 auto new_handle_type = [&](const int8_t handle_type) {
4037 switch (dst_type) {
4038 case SetHandleType::Free:
4039 return int8_t(BEZIER_HANDLE_FREE);
4040 case SetHandleType::Auto:
4041 return int8_t(BEZIER_HANDLE_AUTO);
4042 case SetHandleType::Vector:
4043 return int8_t(BEZIER_HANDLE_VECTOR);
4044 case SetHandleType::Align:
4045 return int8_t(BEZIER_HANDLE_ALIGN);
4046 case SetHandleType::Toggle:
4047 return int8_t(handle_type == BEZIER_HANDLE_FREE ? BEZIER_HANDLE_ALIGN :
4049 }
4051 return int8_t(0);
4052 };
4053
4054 bool changed = false;
4055 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
4056 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4058 if (!curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
4059 return;
4060 }
4061 IndexMaskMemory memory;
4063 *object, info.drawing, info.layer_index, memory);
4064 const IndexMask bezier_curves = curves.indices_for_curve_type(
4065 CURVE_TYPE_BEZIER, editable_strokes, memory);
4066
4067 const bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
4068 const VArraySpan<bool> selection = *attributes.lookup_or_default<bool>(
4069 ".selection", bke::AttrDomain::Point, true);
4070 const VArraySpan<bool> selection_left = *attributes.lookup_or_default<bool>(
4071 ".selection_handle_left", bke::AttrDomain::Point, true);
4072 const VArraySpan<bool> selection_right = *attributes.lookup_or_default<bool>(
4073 ".selection_handle_right", bke::AttrDomain::Point, true);
4074
4075 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
4076 MutableSpan<int8_t> handle_types_left = curves.handle_types_left_for_write();
4077 MutableSpan<int8_t> handle_types_right = curves.handle_types_right_for_write();
4078 bezier_curves.foreach_index(GrainSize(256), [&](const int curve_i) {
4079 const IndexRange points = points_by_curve[curve_i];
4080 for (const int point_i : points) {
4081 if (selection_left[point_i] || selection[point_i]) {
4082 handle_types_left[point_i] = new_handle_type(handle_types_left[point_i]);
4083 }
4084 if (selection_right[point_i] || selection[point_i]) {
4085 handle_types_right[point_i] = new_handle_type(handle_types_right[point_i]);
4086 }
4087 }
4088 });
4089
4090 curves.calculate_bezier_auto_handles();
4091 curves.tag_topology_changed();
4093
4094 changed = true;
4095 });
4096
4097 if (changed) {
4098 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4099 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4100 }
4101
4102 return OPERATOR_FINISHED;
4103}
4104
4106{
4107 ot->name = "Set Handle Type";
4108 ot->idname = "GREASE_PENCIL_OT_set_handle_type";
4109 ot->description = "Set the handle type for Bézier curves";
4110
4111 ot->invoke = WM_menu_invoke;
4114
4115 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4116
4117 ot->prop = RNA_def_enum(ot->srna,
4118 "type",
4121 "Type",
4122 nullptr);
4123}
4124
4126
4127/* -------------------------------------------------------------------- */
4130
4132{
4133 const Scene *scene = CTX_data_scene(C);
4134 Object *object = CTX_data_active_object(C);
4135 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
4136
4137 const int resolution = RNA_int_get(op->ptr, "resolution");
4138
4139 bool changed = false;
4140 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
4141 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4143 IndexMaskMemory memory;
4145 *object, info.drawing, info.layer_index, memory);
4146 if (editable_strokes.is_empty()) {
4147 return;
4148 }
4149
4150 if (curves.is_single_type(CURVE_TYPE_POLY)) {
4151 return;
4152 }
4153
4154 index_mask::masked_fill(curves.resolution_for_write(), resolution, editable_strokes);
4156 changed = true;
4157 });
4158
4159 if (changed) {
4160 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4161 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4162 }
4163
4164 return OPERATOR_FINISHED;
4165}
4166
4168{
4169 ot->name = "Set Curve Resolution";
4170 ot->idname = "GREASE_PENCIL_OT_set_curve_resolution";
4171 ot->description = "Set resolution of selected curves";
4172
4175
4176 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4177
4178 RNA_def_int(ot->srna,
4179 "resolution",
4180 12,
4181 0,
4182 10000,
4183 "Resolution",
4184 "The resolution to use for each curve segment",
4185 1,
4186 64);
4187}
4188
4190
4191/* -------------------------------------------------------------------- */
4194
4196{
4197 const Scene *scene = CTX_data_scene(C);
4198 Object *object = CTX_data_active_object(C);
4199 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
4200
4201 bool changed = false;
4202 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
4203 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4205 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
4206 IndexMaskMemory memory;
4208 *object, info.drawing, info.layer_index, memory);
4209 if (editable_strokes.is_empty()) {
4210 return;
4211 }
4212
4213 if (attributes.contains("uv_rotation")) {
4214 if (editable_strokes.size() == curves.curves_num()) {
4215 attributes.remove("uv_rotation");
4216 }
4217 else {
4218 bke::SpanAttributeWriter<float> uv_rotations = attributes.lookup_for_write_span<float>(
4219 "uv_rotation");
4220 index_mask::masked_fill(uv_rotations.span, 0.0f, editable_strokes);
4221 uv_rotations.finish();
4222 }
4223 }
4224
4225 if (attributes.contains("uv_translation")) {
4226 if (editable_strokes.size() == curves.curves_num()) {
4227 attributes.remove("uv_translation");
4228 }
4229 else {
4230 bke::SpanAttributeWriter<float2> uv_translations =
4231 attributes.lookup_for_write_span<float2>("uv_translation");
4232 index_mask::masked_fill(uv_translations.span, float2(0.0f, 0.0f), editable_strokes);
4233 uv_translations.finish();
4234 }
4235 }
4236
4237 if (attributes.contains("uv_scale")) {
4238 if (editable_strokes.size() == curves.curves_num()) {
4239 attributes.remove("uv_scale");
4240 }
4241 else {
4243 "uv_scale");
4244 index_mask::masked_fill(uv_scales.span, float2(1.0f, 1.0f), editable_strokes);
4245 uv_scales.finish();
4246 }
4247 }
4248
4249 if (attributes.contains("uv_shear")) {
4250 if (editable_strokes.size() == curves.curves_num()) {
4251 attributes.remove("uv_shear");
4252 }
4253 else {
4254 bke::SpanAttributeWriter<float> uv_shears = attributes.lookup_for_write_span<float>(
4255 "uv_shear");
4256 index_mask::masked_fill(uv_shears.span, 0.0f, editable_strokes);
4257 uv_shears.finish();
4258 }
4259 }
4260
4262 changed = true;
4263 });
4264
4265 if (changed) {
4266 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4267 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4268 }
4269
4270 return OPERATOR_FINISHED;
4271}
4272
4274{
4275 /* Identifiers. */
4276 ot->name = "Reset UVs";
4277 ot->idname = "GREASE_PENCIL_OT_reset_uvs";
4278 ot->description = "Reset UV transformation to default values";
4279
4280 /* Callbacks. */
4283
4284 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4285}
4286
4288{
4289 const Scene &scene = *CTX_data_scene(C);
4290 Object &object = *CTX_data_active_object(C);
4291 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
4292 std::atomic<bool> changed = false;
4293
4294 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
4295 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4296 IndexMaskMemory memory;
4297 const IndexMask selected_points =
4299 object, info.drawing, info.layer_index, memory);
4300
4301 if (selected_points.is_empty()) {
4302 return;
4303 }
4304
4306 selected_points);
4308 changed.store(true, std::memory_order_relaxed);
4309 });
4310
4311 if (changed) {
4312 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4313 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4314 return OPERATOR_FINISHED;
4315 }
4316
4317 return OPERATOR_CANCELLED;
4318}
4319
4321{
4322 /* Identifiers. */
4323 ot->name = "Split stroke";
4324 ot->idname = "GREASE_PENCIL_OT_stroke_split";
4325 ot->description = "Split selected points to a new stroke";
4326
4327 /* Callbacks. */
4330
4331 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4332}
4333
4335
4336/* -------------------------------------------------------------------- */
4339
4340enum class RemoveFillGuidesMode : int8_t { ActiveFrame = 0, AllFrames = 1 };
4341
4343{
4344 using namespace blender::bke::greasepencil;
4345 const Scene &scene = *CTX_data_scene(C);
4346 Object &object = *CTX_data_active_object(C);
4347 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
4348
4349 const RemoveFillGuidesMode mode = RemoveFillGuidesMode(RNA_enum_get(op->ptr, "mode"));
4350
4351 std::atomic<bool> changed = false;
4354 for (const int layer_i : grease_pencil.layers().index_range()) {
4355 const Layer &layer = grease_pencil.layer(layer_i);
4356 if (Drawing *drawing = grease_pencil.get_drawing_at(layer, scene.r.cfra)) {
4357 drawings.append({*drawing, layer_i, scene.r.cfra, 1.0f});
4358 }
4359 }
4360 }
4361 else if (mode == RemoveFillGuidesMode::AllFrames) {
4362 for (const int layer_i : grease_pencil.layers().index_range()) {
4363 const Layer &layer = grease_pencil.layer(layer_i);
4364 for (const auto [frame_number, frame] : layer.frames().items()) {
4365 if (Drawing *drawing = grease_pencil.get_drawing_at(layer, frame_number)) {
4366 drawings.append({*drawing, layer_i, frame_number, 1.0f});
4367 }
4368 }
4369 }
4370 }
4371 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4374 changed.store(true, std::memory_order_relaxed);
4375 }
4376 });
4377
4378 if (changed) {
4379 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4380 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4381 return OPERATOR_FINISHED;
4382 }
4383
4384 return OPERATOR_CANCELLED;
4385}
4386
4388{
4389 static const EnumPropertyItem rna_mode_items[] = {
4390 {int(RemoveFillGuidesMode::ActiveFrame), "ACTIVE_FRAME", 0, "Active Frame", ""},
4391 {int(RemoveFillGuidesMode::AllFrames), "ALL_FRAMES", 0, "All Frames", ""},
4392 {0, nullptr, 0, nullptr, nullptr},
4393 };
4394
4395 /* Identifiers. */
4396 ot->name = "Remove Fill Guides";
4397 ot->idname = "GREASE_PENCIL_OT_remove_fill_guides";
4398 ot->description = "Remove all the strokes that were created from the fill tool as guides";
4399
4400 /* Callbacks. */
4403
4404 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4405
4406 ot->prop = RNA_def_enum(
4407 ot->srna, "mode", rna_mode_items, int(RemoveFillGuidesMode::AllFrames), "Mode", "");
4408}
4409
4410/* -------------------------------------------------------------------- */
4413
4414enum class OutlineMode : int8_t {
4415 View = 0,
4417 Side = 2,
4418 Top = 3,
4421};
4422
4424 {int(OutlineMode::View), "VIEW", 0, "View", ""},
4425 {int(OutlineMode::Front), "FRONT", 0, "Front", ""},
4426 {int(OutlineMode::Side), "SIDE", 0, "Side", ""},
4427 {int(OutlineMode::Top), "TOP", 0, "Top", ""},
4428 {int(OutlineMode::Cursor), "CURSOR", 0, "Cursor", ""},
4429 {int(OutlineMode::Camera), "CAMERA", 0, "Camera", ""},
4430 {0, nullptr, 0, nullptr, nullptr},
4431};
4432
4434{
4436
4437 const Scene *scene = CTX_data_scene(C);
4438 Object *object = CTX_data_active_object(C);
4439 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
4440
4441 const float radius = RNA_float_get(op->ptr, "radius");
4442 const float offset_factor = RNA_float_get(op->ptr, "offset_factor");
4443 const int corner_subdivisions = RNA_int_get(op->ptr, "corner_subdivisions");
4444 const float outline_offset = radius * offset_factor;
4445 const int mat_nr = -1;
4446
4447 const OutlineMode mode = OutlineMode(RNA_enum_get(op->ptr, "type"));
4448
4449 float4x4 viewinv = float4x4::identity();
4450 switch (mode) {
4451 case OutlineMode::View: {
4453 viewinv = float4x4(rv3d->viewmat);
4454 break;
4455 }
4456 case OutlineMode::Front:
4457 viewinv = float4x4({1.0f, 0.0f, 0.0f, 0.0f},
4458 {0.0f, 0.0f, 1.0f, 0.0f},
4459 {0.0f, 1.0f, 0.0f, 0.0f},
4460 {0.0f, 0.0f, 0.0f, 1.0f});
4461 break;
4462 case OutlineMode::Side:
4463 viewinv = float4x4({0.0f, 0.0f, 1.0f, 0.0f},
4464 {0.0f, 1.0f, 0.0f, 0.0f},
4465 {1.0f, 0.0f, 0.0f, 0.0f},
4466 {0.0f, 0.0f, 0.0f, 1.0f});
4467 break;
4468 case OutlineMode::Top:
4469 viewinv = float4x4::identity();
4470 break;
4471 case OutlineMode::Cursor: {
4472 viewinv = scene->cursor.matrix<float4x4>();
4473 break;
4474 }
4476 viewinv = scene->camera->world_to_object();
4477 break;
4478 default:
4480 break;
4481 }
4482
4483 bool changed = false;
4484 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
4485 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4486 IndexMaskMemory memory;
4488 *object, info.drawing, info.layer_index, memory);
4489 if (editable_strokes.is_empty()) {
4490 return;
4491 }
4492
4493 const Layer &layer = grease_pencil.layer(info.layer_index);
4494 const float4x4 viewmat = viewinv * layer.to_world_space(*object);
4495
4497 editable_strokes,
4498 viewmat,
4499 corner_subdivisions,
4500 radius,
4501 outline_offset,
4502 mat_nr);
4503
4504 info.drawing.strokes_for_write().remove_curves(editable_strokes, {});
4505
4506 /* Join the outline stroke into the drawing. */
4507 Curves *strokes = bke::curves_new_nomain(std::move(outline));
4508
4509 Curves *other_curves = bke::curves_new_nomain(std::move(info.drawing.strokes_for_write()));
4510 const std::array<bke::GeometrySet, 2> geometry_sets = {
4512
4513 info.drawing.strokes_for_write() = std::move(
4514 geometry::join_geometries(geometry_sets, {}).get_curves_for_write()->geometry.wrap());
4515
4517 changed = true;
4518 });
4519
4520 if (changed) {
4521 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4522 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4523 }
4524
4525 return OPERATOR_FINISHED;
4526}
4527
4529{
4530 /* Identifiers. */
4531 ot->name = "Outline";
4532 ot->idname = "GREASE_PENCIL_OT_outline";
4533 ot->description = "Convert selected strokes to perimeter";
4534
4535 /* Callbacks. */
4538
4539 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4540
4541 /* Properties */
4542 ot->prop = RNA_def_enum(
4543 ot->srna, "type", prop_outline_modes, int(OutlineMode::View), "Projection Mode", "");
4544 RNA_def_float_distance(ot->srna, "radius", 0.01f, 0.0f, 10.0f, "Radius", "", 0.0f, 10.0f);
4546 ot->srna, "offset_factor", -1.0f, -1.0f, 1.0f, "Offset Factor", "", -1.0f, 1.0f);
4547 RNA_def_int(ot->srna, "corner_subdivisions", 2, 0, 10, "Corner Subdivisions", "", 0, 5);
4548}
4549
4551
4552/* -------------------------------------------------------------------- */
4555
4557 const IndexMask &selection,
4558 const float threshold)
4559{
4560 const VArray<float> thresholds = VArray<float>::from_single(threshold, curves.curves_num());
4561 /* TODO: Detect or manually provide corners. */
4562 const VArray<bool> corners = VArray<bool>::from_single(false, curves.points_num());
4564 curves, selection, thresholds, corners, geometry::FitMethod::Refit, {});
4565}
4566
4568 const IndexMask &selection,
4569 const float threshold)
4570{
4571 if (curves.is_single_type(CURVE_TYPE_CATMULL_ROM)) {
4572 return;
4573 }
4574 IndexMaskMemory memory;
4575 const IndexMask non_catmull_rom_curves_selection =
4576 curves.indices_for_curve_type(CURVE_TYPE_CATMULL_ROM, selection, memory)
4577 .complement(selection, memory);
4578 if (non_catmull_rom_curves_selection.is_empty()) {
4579 return;
4580 }
4581
4582 curves = geometry::resample_to_evaluated(curves, non_catmull_rom_curves_selection);
4583
4584 /* To avoid having too many control points, simplify the position attribute based on the
4585 * threshold. This doesn't replace an actual curve fitting (which would be better), but
4586 * is a decent approximation for the meantime. */
4587 const IndexMask points_to_remove = geometry::simplify_curve_attribute(
4588 curves.positions(),
4589 non_catmull_rom_curves_selection,
4590 curves.points_by_curve(),
4591 curves.cyclic(),
4592 threshold,
4593 curves.positions(),
4594 memory);
4595 curves.remove_points(points_to_remove, {});
4596
4598 options.convert_bezier_handles_to_poly_points = false;
4599 options.convert_bezier_handles_to_catmull_rom_points = false;
4600 options.keep_bezier_shape_as_nurbs = true;
4601 options.keep_catmull_rom_shape_as_nurbs = true;
4603 curves, non_catmull_rom_curves_selection, CURVE_TYPE_CATMULL_ROM, {}, options);
4604}
4605
4607{
4608 if (curves.is_single_type(CURVE_TYPE_POLY)) {
4609 return;
4610 }
4611 IndexMaskMemory memory;
4612 const IndexMask non_poly_curves_selection = curves
4613 .indices_for_curve_type(
4614 CURVE_TYPE_POLY, selection, memory)
4615 .complement(selection, memory);
4616 if (non_poly_curves_selection.is_empty()) {
4617 return;
4618 }
4619
4620 curves = geometry::resample_to_evaluated(curves, non_poly_curves_selection);
4621}
4622
4624 const IndexMask &selection,
4625 const float threshold)
4626{
4627 if (curves.is_single_type(CURVE_TYPE_BEZIER)) {
4628 return;
4629 }
4630 IndexMaskMemory memory;
4631 const IndexMask poly_curves_selection = curves.indices_for_curve_type(
4632 CURVE_TYPE_POLY, selection, memory);
4633 if (!poly_curves_selection.is_empty()) {
4634 curves = fit_poly_curves(curves, poly_curves_selection, threshold);
4635 }
4636
4638 options.convert_bezier_handles_to_poly_points = false;
4639 options.convert_bezier_handles_to_catmull_rom_points = false;
4640 options.keep_bezier_shape_as_nurbs = true;
4641 options.keep_catmull_rom_shape_as_nurbs = true;
4643}
4644
4646 const IndexMask &selection,
4647 const float threshold)
4648{
4649 if (curves.is_single_type(CURVE_TYPE_NURBS)) {
4650 return;
4651 }
4652
4653 IndexMaskMemory memory;
4654 const IndexMask poly_curves_selection = curves.indices_for_curve_type(
4655 CURVE_TYPE_POLY, selection, memory);
4656 if (!poly_curves_selection.is_empty()) {
4657 curves = fit_poly_curves(curves, poly_curves_selection, threshold);
4658 }
4659
4661 options.convert_bezier_handles_to_poly_points = false;
4662 options.convert_bezier_handles_to_catmull_rom_points = false;
4663 options.keep_bezier_shape_as_nurbs = true;
4664 options.keep_catmull_rom_shape_as_nurbs = true;
4666}
4667
4669{
4670 const Scene *scene = CTX_data_scene(C);
4671 Object *object = CTX_data_active_object(C);
4672 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
4673
4674 const CurveType dst_type = CurveType(RNA_enum_get(op->ptr, "type"));
4675 const float threshold = RNA_float_get(op->ptr, "threshold");
4676
4677 std::atomic<bool> changed = false;
4678 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
4679 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4681 IndexMaskMemory memory;
4683 *object, info.drawing, info.layer_index, memory);
4684 if (strokes.is_empty()) {
4685 return;
4686 }
4687
4688 switch (dst_type) {
4690 convert_to_catmull_rom(curves, strokes, threshold);
4691 break;
4692 case CURVE_TYPE_POLY:
4693 convert_to_poly(curves, strokes);
4694 break;
4695 case CURVE_TYPE_BEZIER:
4696 convert_to_bezier(curves, strokes, threshold);
4697 break;
4698 case CURVE_TYPE_NURBS:
4699 convert_to_nurbs(curves, strokes, threshold);
4700 break;
4701 }
4702
4704 changed.store(true, std::memory_order_relaxed);
4705 });
4706
4707 if (changed) {
4708 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4709 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4710 }
4711
4712 return OPERATOR_FINISHED;
4713}
4714
4716{
4717 uiLayout *layout = op->layout;
4719
4721
4722 layout->use_property_split_set(true);
4723 layout->use_property_decorate_set(false);
4724
4725 layout->prop(&ptr, "type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
4726
4727 const CurveType dst_type = CurveType(RNA_enum_get(op->ptr, "type"));
4728
4729 if (dst_type == CURVE_TYPE_POLY) {
4730 return;
4731 }
4732
4733 layout->prop(&ptr, "threshold", UI_ITEM_NONE, std::nullopt, ICON_NONE);
4734}
4735
4737{
4738 ot->name = "Convert Curve Type";
4739 ot->idname = "GREASE_PENCIL_OT_convert_curve_type";
4740 ot->description = "Convert type of selected curves";
4741
4742 ot->invoke = WM_menu_invoke;
4746
4747 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4748
4749 ot->prop = RNA_def_enum(
4750 ot->srna, "type", rna_enum_curves_type_items, CURVE_TYPE_POLY, "Type", "");
4752
4753 PropertyRNA *prop = RNA_def_float(
4754 ot->srna,
4755 "threshold",
4756 0.01f,
4757 0.0f,
4758 100.0f,
4759 "Threshold",
4760 "The distance that the resulting points are allowed to be within",
4761 0.0f,
4762 100.0f);
4764}
4765
4767
4768/* -------------------------------------------------------------------- */
4771
4772enum class CornerType : uint8_t {
4776};
4777
4779 {int(CornerType::Round), "ROUND", 0, "Round", ""},
4780 {int(CornerType::Bevel), "FLAT", 0, "Flat", ""},
4781 {int(CornerType::Miter), "SHARP", 0, "Sharp", ""},
4782 {0, nullptr, 0, nullptr, nullptr},
4783};
4784
4786{
4788
4789 const Scene *scene = CTX_data_scene(C);
4790 Object *object = CTX_data_active_object(C);
4791 View3D *v3d = CTX_wm_view3d(C);
4792 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
4793
4794 const CornerType corner_type = CornerType(RNA_enum_get(op->ptr, "corner_type"));
4795 float miter_angle = RNA_float_get(op->ptr, "miter_angle");
4796
4797 if (corner_type == CornerType::Round) {
4798 miter_angle = GP_STROKE_MITER_ANGLE_ROUND;
4799 }
4800 else if (corner_type == CornerType::Bevel) {
4801 miter_angle = GP_STROKE_MITER_ANGLE_BEVEL;
4802 }
4803 else if (corner_type == CornerType::Miter) {
4804 /* Prevent the angle from being set to zero, and becoming the `Round` type.*/
4805 if (miter_angle == 0.0f) {
4806 miter_angle = DEG2RADF(1.0f);
4807 }
4808 }
4809
4810 std::atomic<bool> changed = false;
4811 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
4812 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4813 IndexMaskMemory memory;
4815 *object, info.drawing, info.layer_index, v3d->overlay.handle_display, memory);
4816 if (selection.is_empty()) {
4817 return;
4818 }
4819
4821 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
4822
4823 /* Only create the attribute if we are not storing the default. */
4824 if (miter_angle == GP_STROKE_MITER_ANGLE_ROUND && !attributes.contains("miter_angle")) {
4825 return;
4826 }
4827
4828 /* Remove the attribute if we are storing all default. */
4829 if (miter_angle == GP_STROKE_MITER_ANGLE_ROUND && selection == curves.points_range()) {
4830 attributes.remove("miter_angle");
4831 changed.store(true, std::memory_order_relaxed);
4832 return;
4833 }
4834
4835 if (bke::SpanAttributeWriter<float> miter_angles =
4836 attributes.lookup_or_add_for_write_span<float>(
4837 "miter_angle",
4841 {
4842 index_mask::masked_fill(miter_angles.span, miter_angle, selection);
4843 miter_angles.finish();
4844 changed.store(true, std::memory_order_relaxed);
4845 }
4846 });
4847
4848 if (changed) {
4849 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4850 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4851 }
4852
4853 return OPERATOR_FINISHED;
4854}
4855
4857{
4858 uiLayout *layout = op->layout;
4860
4862
4863 layout->use_property_split_set(true);
4864 layout->use_property_decorate_set(false);
4865
4866 layout->prop(&ptr, "corner_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
4867
4868 const CornerType corner_type = CornerType(RNA_enum_get(op->ptr, "corner_type"));
4869
4870 if (corner_type != CornerType::Miter) {
4871 return;
4872 }
4873
4874 layout->prop(&ptr, "miter_angle", UI_ITEM_NONE, std::nullopt, ICON_NONE);
4875}
4876
4878{
4879 /* Identifiers. */
4880 ot->name = "Set Corner Type";
4881 ot->idname = "GREASE_PENCIL_OT_set_corner_type";
4882 ot->description = "Set the corner type of the selected points";
4883
4884 /* Callbacks. */
4888
4889 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4890
4891 /* Properties */
4892 ot->prop = RNA_def_enum(
4893 ot->srna, "corner_type", prop_corner_types, int(CornerType::Miter), "Corner Type", "");
4894 ot->prop = RNA_def_float_distance(ot->srna,
4895 "miter_angle",
4896 DEG2RADF(45.0f),
4897 0.0f,
4898 M_PI,
4899 "Miter Cut Angle",
4900 "All corners sharper than the Miter angle will be cut flat",
4901 0.0f,
4902 M_PI);
4904}
4905
4907
4908} // namespace blender::ed::greasepencil
4909
4911{
4912 using namespace blender::ed::greasepencil;
4952}
4953
4954/* -------------------------------------------------------------------- */
4957
4958namespace blender::ed::greasepencil {
4959
4960/* Note: the `duplicate_layer` API would be nicer, but only supports duplicating groups from the
4961 * same datablock. */
4964 const bke::greasepencil::Layer &layer_src)
4965{
4966 using namespace blender::bke::greasepencil;
4967
4968 Layer &layer_dst = grease_pencil_dst.add_layer(group_dst, layer_src.name());
4969 BKE_grease_pencil_copy_layer_parameters(layer_src, layer_dst);
4970
4971 layer_dst.frames_for_write() = layer_src.frames();
4972 layer_dst.tag_frames_map_changed();
4973
4974 return layer_dst;
4975}
4976
4978 GreasePencil &grease_pencil_dst,
4980 const bke::greasepencil::LayerGroup &group_src,
4981 Map<StringRefNull, StringRefNull> &layer_name_map);
4982
4983static void copy_layer_group_content(GreasePencil &grease_pencil_dst,
4985 const bke::greasepencil::LayerGroup &group_src,
4986 Map<StringRefNull, StringRefNull> &layer_name_map)
4987{
4988 using namespace blender::bke::greasepencil;
4989
4990 LISTBASE_FOREACH (GreasePencilLayerTreeNode *, child, &group_src.children) {
4991 switch (child->type) {
4992 case GP_LAYER_TREE_LEAF: {
4993 Layer &layer_src = reinterpret_cast<GreasePencilLayer *>(child)->wrap();
4994 Layer &layer_dst = copy_layer(grease_pencil_dst, group_dst, layer_src);
4995 layer_name_map.add_new(layer_src.name(), layer_dst.name());
4996 break;
4997 }
4998 case GP_LAYER_TREE_GROUP: {
4999 LayerGroup &group_src = reinterpret_cast<GreasePencilLayerTreeGroup *>(child)->wrap();
5000 copy_layer_group_recursive(grease_pencil_dst, group_dst, group_src, layer_name_map);
5001 break;
5002 }
5003 }
5004 }
5005}
5006
5008 GreasePencil &grease_pencil_dst,
5010 const bke::greasepencil::LayerGroup &group_src,
5011 Map<StringRefNull, StringRefNull> &layer_name_map)
5012{
5013 bke::greasepencil::LayerGroup &group_dst = grease_pencil_dst.add_layer_group(
5014 parent_dst, group_src.base.name);
5016
5017 copy_layer_group_content(grease_pencil_dst, group_dst, group_src, layer_name_map);
5018 return group_dst;
5019}
5020
5022{
5023 BLI_assert(object.type == OB_GREASE_PENCIL);
5024 Array<int> material_index_map(*BKE_object_material_len_p(&object));
5025 for (const int i : material_index_map.index_range()) {
5026 Material *material = BKE_object_material_get(&object, i + 1);
5027 if (material != nullptr) {
5028 material_index_map[i] = materials.index_of_or_add(material);
5029 }
5030 else {
5031 material_index_map[i] = 0;
5032 }
5033 }
5034 return material_index_map;
5035}
5036
5038 const Span<int> material_index_map)
5039{
5041 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
5042 /* Validate material indices and add missing materials. */
5043 bke::SpanAttributeWriter<int> material_writer = attributes.lookup_or_add_for_write_span<int>(
5044 "material_index", bke::AttrDomain::Curve);
5045 threading::parallel_for(curves.curves_range(), 1024, [&](const IndexRange range) {
5046 for (const int curve_i : range) {
5047 material_writer.span[curve_i] = material_index_map[material_writer.span[curve_i]];
5048 }
5049 });
5050 material_writer.finish();
5051}
5052
5054 GreasePencil &grease_pencil,
5055 const ListBase &vertex_group_names)
5056{
5057 Map<StringRefNull, StringRefNull> vertex_group_map;
5058 LISTBASE_FOREACH (bDeformGroup *, dg, &vertex_group_names) {
5059 bDeformGroup *vgroup = static_cast<bDeformGroup *>(MEM_dupallocN(dg));
5060 BKE_object_defgroup_unique_name(vgroup, &object);
5061 BLI_addtail(&grease_pencil.vertex_group_names, vgroup);
5062 vertex_group_map.add_new(dg->name, vgroup->name);
5063 }
5064 return vertex_group_map;
5065}
5066
5068 const Map<StringRefNull, StringRefNull> &vertex_group_map)
5069{
5071 STRNCPY_UTF8(dg->name, vertex_group_map.lookup(dg->name).c_str());
5072 }
5073
5074 /* Indices in vertex weights remain valid, they are local to the drawing's vertex groups.
5075 * Only the names of the groups change. */
5076}
5077
5080 const int dst_size)
5081{
5083 const auto add_or_upgrade_types = [&](const bke::AttributeAccessor &attributes) {
5084 attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
5085 new_types.add_or_modify(
5086 iter.name,
5087 [&](bke::AttrType *value) { *value = iter.data_type; },
5088 [&](bke::AttrType *value) {
5089 *value = bke::attribute_data_type_highest_complexity({*value, iter.data_type});
5090 });
5091 });
5092 };
5093 add_or_upgrade_types(a);
5094 add_or_upgrade_types(b);
5095 const int64_t domain_size_a = a.domain_size(bke::AttrDomain::Layer);
5096
5097 bke::AttributeStorage new_storage;
5098 for (const auto &[name, type] : new_types.items()) {
5099 const CPPType &cpp_type = bke::attribute_type_to_cpp_type(type);
5100 auto new_data = bke::Attribute::ArrayData::from_uninitialized(cpp_type, dst_size);
5101
5102 const GVArray data_a = *a.lookup_or_default(name, bke::AttrDomain::Layer, type);
5103 data_a.materialize_to_uninitialized(new_data.data);
5104
5105 const GVArray data_b = *b.lookup_or_default(name, bke::AttrDomain::Layer, type);
5107 POINTER_OFFSET(new_data.data, cpp_type.size * domain_size_a));
5108
5109 new_storage.add(name, bke::AttrDomain::Layer, type, std::move(new_data));
5110 }
5111
5112 return new_storage;
5113}
5114
5116 Object &ob_src,
5117 Object &ob_dst,
5118 VectorSet<Material *> &materials)
5119{
5120 using namespace blender::bke::greasepencil;
5121
5122 /* Skip if the datablock is already used by the active object. */
5123 if (ob_src.data == ob_dst.data) {
5124 return;
5125 }
5126
5127 BLI_assert(ob_src.type == OB_GREASE_PENCIL);
5128 BLI_assert(ob_dst.type == OB_GREASE_PENCIL);
5129 GreasePencil &grease_pencil_src = *static_cast<GreasePencil *>(ob_src.data);
5130 GreasePencil &grease_pencil_dst = *static_cast<GreasePencil *>(ob_dst.data);
5131 /* Number of existing layers that don't need to be updated. */
5132 const int orig_layers_num = grease_pencil_dst.layers().size();
5133
5135 ob_dst, grease_pencil_dst, grease_pencil_src.vertex_group_names);
5136 const Array<int> material_index_map = add_materials_to_map(ob_src, materials);
5137
5138 /* Concatenate drawing arrays. Existing drawings in dst keep their position, new drawings are
5139 * mapped to the new index range. */
5140 const int new_drawing_array_num = grease_pencil_dst.drawing_array_num +
5141 grease_pencil_src.drawing_array_num;
5142 GreasePencilDrawingBase **new_drawing_array = static_cast<GreasePencilDrawingBase **>(
5143 MEM_malloc_arrayN(new_drawing_array_num, sizeof(GreasePencilDrawingBase *), __func__));
5144 MutableSpan<GreasePencilDrawingBase *> new_drawings = {new_drawing_array, new_drawing_array_num};
5145 const IndexRange new_drawings_dst = IndexRange::from_begin_size(
5146 0, grease_pencil_dst.drawing_array_num);
5147 const IndexRange new_drawings_src = IndexRange::from_begin_size(
5148 grease_pencil_dst.drawing_array_num, grease_pencil_src.drawing_array_num);
5149
5150 copy_drawing_array(grease_pencil_dst.drawings(), new_drawings.slice(new_drawings_dst));
5151 copy_drawing_array(grease_pencil_src.drawings(), new_drawings.slice(new_drawings_src));
5152
5153 /* Free existing drawings array. */
5154 grease_pencil_dst.resize_drawings(0);
5155 grease_pencil_dst.drawing_array = new_drawing_array;
5156 grease_pencil_dst.drawing_array_num = new_drawing_array_num;
5157
5158 /* Maps original names of source layers to new unique layer names. */
5159 Map<StringRefNull, StringRefNull> layer_name_map;
5160 /* Only copy the content of the root group, not the root node itself. */
5161 copy_layer_group_content(grease_pencil_dst,
5162 grease_pencil_dst.root_group(),
5163 grease_pencil_src.root_group(),
5164 layer_name_map);
5165
5166 grease_pencil_dst.attribute_storage.wrap() = merge_attributes(grease_pencil_src.attributes(),
5167 grease_pencil_dst.attributes(),
5168 grease_pencil_dst.layers().size());
5169
5170 /* Fix names, indices and transforms to keep relationships valid. */
5171 for (const int layer_index : grease_pencil_dst.layers().index_range()) {
5172 Layer &layer = *grease_pencil_dst.layers_for_write()[layer_index];
5173 const bool is_orig_layer = (layer_index < orig_layers_num);
5174 const float4x4 old_layer_to_world = (is_orig_layer ? layer.to_world_space(ob_dst) :
5175 layer.to_world_space(ob_src));
5176
5177 /* Update newly added layers. */
5178 if (!is_orig_layer) {
5179 /* Update name references for masks. */
5180 LISTBASE_FOREACH (GreasePencilLayerMask *, dst_mask, &layer.masks) {
5181 const StringRefNull *new_mask_name = layer_name_map.lookup_ptr(dst_mask->layer_name);
5182 if (new_mask_name) {
5183 MEM_SAFE_FREE(dst_mask->layer_name);
5184 dst_mask->layer_name = BLI_strdup(new_mask_name->c_str());
5185 }
5186 }
5187 /* Shift drawing indices to match the new drawings array. */
5188 for (const int key : layer.frames_for_write().keys()) {
5189 int &drawing_index = layer.frames_for_write().lookup(key).drawing_index;
5190 drawing_index = new_drawings_src[drawing_index];
5191 }
5192 }
5193
5194 /* Layer parent object may become invalid. This can be an original layer pointing at the joined
5195 * object which gets destroyed, or a new layer that points at the target object which is now
5196 * its owner. */
5197 if (ELEM(layer.parent, &ob_dst, &ob_src)) {
5198 layer.parent = nullptr;
5199 }
5200
5201 /* Apply relative object transform to new drawings to keep world-space positions unchanged.
5202 * Be careful where the matrix is computed: changing the parent pointer (above) can affect
5203 * this! */
5204 const float4x4 new_layer_to_world = layer.to_world_space(ob_dst);
5205 for (const int key : layer.frames_for_write().keys()) {
5206 const int drawing_index = layer.frames_for_write().lookup(key).drawing_index;
5207 GreasePencilDrawingBase *drawing_base = grease_pencil_dst.drawings()[drawing_index];
5208 if (drawing_base->type != GP_DRAWING) {
5209 continue;
5210 }
5211 Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base)->wrap();
5213 curves.transform(math::invert(new_layer_to_world) * old_layer_to_world);
5214
5215 if (!is_orig_layer) {
5216 remap_vertex_groups(drawing, vertex_group_map);
5217 remap_material_indices(drawing, material_index_map);
5218 }
5219 }
5220 }
5221
5222 /* Rename animation paths to layers. */
5223 BKE_fcurves_main_cb(&bmain, [&](ID *id, FCurve *fcu) {
5224 if (id == &grease_pencil_src.id && fcu->rna_path && strstr(fcu->rna_path, "layers[")) {
5225 /* Have to use linear search, the layer name map only contains sub-strings of RNA paths. */
5226 for (auto [name_src, name_dst] : layer_name_map.items()) {
5227 if (name_dst != name_src) {
5228 const char *old_path = fcu->rna_path;
5230 id, fcu->rna_path, "layers", name_src.c_str(), name_dst.c_str(), 0, 0, false);
5231 if (old_path != fcu->rna_path) {
5232 /* Stop after first match. */
5233 break;
5234 }
5235 }
5236 }
5237 }
5238 /* Fix driver targets. */
5239 if (fcu->driver) {
5240 LISTBASE_FOREACH (DriverVar *, dvar, &fcu->driver->variables) {
5241 /* Only change the used targets, since the others will need fixing manually anyway. */
5243 if (dtar->id != &grease_pencil_src.id) {
5244 continue;
5245 }
5246 dtar->id = &grease_pencil_dst.id;
5247
5248 if (dtar->rna_path && strstr(dtar->rna_path, "layers[")) {
5249 for (auto [name_src, name_dst] : layer_name_map.items()) {
5250 if (name_dst != name_src) {
5251 const char *old_path = fcu->rna_path;
5252 dtar->rna_path = BKE_animsys_fix_rna_path_rename(
5253 id, dtar->rna_path, "layers", name_src.c_str(), name_dst.c_str(), 0, 0, false);
5254 if (old_path != dtar->rna_path) {
5255 break;
5256 }
5257 }
5258 }
5259 }
5260 }
5262 }
5263 }
5264 });
5265
5266 /* Merge animation data of objects and grease pencil datablocks. */
5267 if (ob_src.adt) {
5268 if (ob_dst.adt == nullptr) {
5269 ob_dst.adt = BKE_animdata_copy(&bmain, ob_src.adt, 0);
5270 }
5271 else {
5272 BKE_animdata_merge_copy(&bmain, &ob_dst.id, &ob_src.id, ADT_MERGECOPY_KEEP_DST, false);
5273 }
5274
5275 if (ob_dst.adt->action) {
5277 }
5278 }
5279 if (grease_pencil_src.adt) {
5280 if (grease_pencil_dst.adt == nullptr) {
5281 grease_pencil_dst.adt = BKE_animdata_copy(&bmain, grease_pencil_src.adt, 0);
5282 }
5283 else {
5285 &bmain, &grease_pencil_dst.id, &grease_pencil_src.id, ADT_MERGECOPY_KEEP_DST, false);
5286 }
5287
5288 if (grease_pencil_dst.adt->action) {
5290 }
5291 }
5292}
5293
5294} // namespace blender::ed::greasepencil
5295
5297{
5298 Main *bmain = CTX_data_main(C);
5299 Scene *scene = CTX_data_scene(C);
5300 Object *ob_active = CTX_data_active_object(C);
5301
5302 /* Ensure we're in right mode and that the active object is correct. */
5303 if (!ob_active || ob_active->type != OB_GREASE_PENCIL) {
5304 return OPERATOR_CANCELLED;
5305 }
5306
5307 bool ok = false;
5308 CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) {
5309 if (ob_iter == ob_active) {
5310 ok = true;
5311 break;
5312 }
5313 }
5315 /* Active object must always selected. */
5316 if (ok == false) {
5317 BKE_report(op->reports, RPT_WARNING, "Active object is not a selected Grease Pencil");
5318 return OPERATOR_CANCELLED;
5319 }
5320
5321 Object *ob_dst = ob_active;
5322 GreasePencil *grease_pencil_dst = static_cast<GreasePencil *>(ob_dst->data);
5323
5326 *ob_dst, materials);
5327 /* Reassign material indices in the original layers, in case materials are deduplicated. */
5328 for (GreasePencilDrawingBase *drawing_base : grease_pencil_dst->drawings()) {
5329 if (drawing_base->type != GP_DRAWING) {
5330 continue;
5331 }
5333 reinterpret_cast<GreasePencilDrawing *>(drawing_base)->wrap();
5334 blender::ed::greasepencil::remap_material_indices(drawing, material_index_map);
5335 }
5336
5337 /* Loop and join all data. */
5338 CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) {
5339 if (ob_iter->type != OB_GREASE_PENCIL || ob_iter == ob_active) {
5340 continue;
5341 }
5342
5343 blender::ed::greasepencil::join_object_with_active(*bmain, *ob_iter, *ob_dst, materials);
5344
5345 /* Free the old object. */
5346 blender::ed::object::base_free_and_unlink(bmain, scene, ob_iter);
5347 }
5349
5350 /* Transfer material pointers. The material indices are updated for each drawing separately. */
5351 if (!materials.is_empty()) {
5352 /* Old C API, needs a const_cast but doesn't actually change anything. */
5353 Material **materials_ptr = const_cast<Material **>(materials.data());
5355 bmain, DEG_get_original(ob_dst), &materials_ptr, materials.size(), false);
5356 }
5357
5358 DEG_id_tag_update(&grease_pencil_dst->id, ID_RECALC_GEOMETRY);
5360
5363
5364 return OPERATOR_FINISHED;
5365}
5366
void BKE_animdata_merge_copy(Main *bmain, ID *dst_id, ID *src_id, eAnimData_MergeCopy_Modes action_mode, bool fix_drivers)
Definition anim_data.cc:447
AnimData * BKE_animdata_copy(Main *bmain, AnimData *adt, int flag)
Definition anim_data.cc:364
@ ADT_MERGECOPY_KEEP_DST
void BKE_fcurves_main_cb(struct Main *bmain, blender::FunctionRef< void(ID *, FCurve *)> func)
char * BKE_animsys_fix_rna_path_rename(struct ID *owner_id, char *old_path, const char *prefix, const char *oldName, const char *newName, int oldSubscript, int newSubscript, bool verify_paths)
Definition anim_data.cc:890
#define CTX_DATA_BEGIN(C, Type, instance, member)
Depsgraph * CTX_data_ensure_evaluated_depsgraph(const bContext *C)
ScrArea * CTX_wm_area(const bContext *C)
Object * CTX_data_active_object(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
Base * CTX_data_active_base(const bContext *C)
Main * CTX_data_main(const bContext *C)
RegionView3D * CTX_wm_region_view3d(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
wmWindowManager * CTX_wm_manager(const bContext *C)
#define CTX_DATA_END
View3D * CTX_wm_view3d(const bContext *C)
ViewLayer * CTX_data_view_layer(const bContext *C)
Low-level operations for curves.
CustomData interface, see also DNA_customdata_types.h.
support for deformation groups and hooks.
void BKE_object_defgroup_unique_name(bDeformGroup *dg, Object *ob)
Definition deform.cc:752
void BKE_defgroup_copy_list(ListBase *outbase, const ListBase *inbase)
Definition deform.cc:73
#define DRIVER_TARGETS_USED_LOOPER_BEGIN(dvar)
#define DRIVER_TARGETS_LOOPER_END
Low-level operations for grease pencil.
void BKE_grease_pencil_copy_parameters(const GreasePencil &src, GreasePencil &dst)
Material * BKE_grease_pencil_object_material_new(Main *bmain, Object *ob, const char *name, int *r_index)
GreasePencil * BKE_grease_pencil_add(Main *bmain, const char *name)
void BKE_grease_pencil_copy_layer_parameters(const blender::bke::greasepencil::Layer &src, blender::bke::greasepencil::Layer &dst)
void BKE_grease_pencil_copy_layer_group_parameters(const blender::bke::greasepencil::LayerGroup &src, blender::bke::greasepencil::LayerGroup &dst)
ID * BKE_libblock_find_name(Main *bmain, short type, const char *name, const std::optional< Library * > lib=std::nullopt) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition lib_id.cc:1710
General operations, lookup, etc. for materials.
short * BKE_object_material_len_p(Object *ob)
MaterialGPencilStyle * BKE_gpencil_material_settings(Object *ob, short act)
void BKE_object_material_array_assign(Main *bmain, Object *ob, Material ***matar, int totcol, bool to_object_only)
bool BKE_object_material_slot_used(Object *object, short actcol)
int BKE_object_material_ensure(Main *bmain, Object *ob, Material *material)
Material * BKE_object_material_get(Object *ob, short act)
int BKE_object_material_index_get(Object *ob, const Material *ma)
Material *** BKE_object_material_array_p(Object *ob)
bool BKE_object_material_slot_remove(Main *bmain, Object *ob)
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
@ RPT_INFO
Definition BKE_report.hh:35
@ RPT_ERROR
Definition BKE_report.hh:39
@ RPT_WARNING
Definition BKE_report.hh:38
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
void BKE_scene_graph_update_for_newframe(Depsgraph *depsgraph)
Definition scene.cc:2700
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
#define LISTBASE_FOREACH(type, var, list)
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
#define DEG2RADF(_deg)
#define M_PI
char * BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC
Definition string.cc:41
#define STRNCPY_UTF8(dst, src)
unsigned int uint
#define ELEM(...)
#define POINTER_OFFSET(v, ofs)
#define TIP_(msgid)
#define BLT_I18NCONTEXT_ID_GPENCIL
#define IFACE_(msgid)
#define BLT_I18NCONTEXT_ID_MOVIECLIP
void DEG_id_tag_update(ID *id, unsigned int flags)
void DEG_relations_tag_update(Main *bmain)
float DEG_get_ctime(const Depsgraph *graph)
T * DEG_get_original(T *id)
@ ID_RECALC_TRANSFORM
Definition DNA_ID.h:1054
@ ID_RECALC_SYNC_TO_EVAL
Definition DNA_ID.h:1118
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:1074
@ ID_RECALC_ANIMATION_NO_FLUSH
Definition DNA_ID.h:1176
#define MAX_ID_NAME
Definition DNA_ID.h:373
@ ID_MA
@ BEZIER_HANDLE_FREE
@ BEZIER_HANDLE_ALIGN
@ BEZIER_HANDLE_VECTOR
@ BEZIER_HANDLE_AUTO
@ NURBS_KNOT_MODE_ENDPOINT
@ NURBS_KNOT_MODE_NORMAL
@ NURBS_KNOT_MODE_CUSTOM
@ GP_STROKE_CAP_TYPE_FLAT
@ GP_STROKE_CAP_TYPE_ROUND
@ GP_LAYER_TREE_GROUP
#define GP_STROKE_MITER_ANGLE_BEVEL
#define GP_STROKE_MITER_ANGLE_ROUND
@ GP_MATERIAL_GRADIENT_RADIAL
@ OB_MODE_OBJECT
Object is a sort of wrapper for general info.
@ OB_GREASE_PENCIL
@ SCE_SNAP_TARGET_ALL
@ RGN_TYPE_WINDOW
@ SPACE_VIEW3D
eDupli_ID_Flags
@ USER_DUP_GPENCIL
@ V3D_AROUND_ACTIVE
@ V3D_AROUND_CENTER_BOUNDS
@ V3D_AROUND_CURSOR
@ V3D_AROUND_CENTER_MEDIAN
@ V3D_AROUND_LOCAL_ORIGINS
@ CURVE_HANDLE_NONE
@ OPERATOR_CANCELLED
@ OPERATOR_FINISHED
@ OPERATOR_RUNNING_MODAL
bool ED_view3d_win_to_ray_clipped(Depsgraph *depsgraph, const ARegion *region, const View3D *v3d, const float mval[2], float r_ray_start[3], float r_ray_normal[3], bool do_clip_planes)
float ED_view3d_grid_view_scale(const Scene *scene, const View3D *v3d, const ARegion *region, const char **r_grid_unit)
@ V3D_PROJ_TEST_NOP
Definition ED_view3d.hh:279
@ V3D_PROJ_RET_OK
Definition ED_view3d.hh:256
bool ED_view3d_win_to_3d_on_plane(const ARegion *region, const float plane[4], const float mval[2], bool do_clip, float r_out[3])
eV3DProjStatus ED_view3d_project_float_global(const ARegion *region, const float co[3], float r_co[2], eV3DProjTest flag)
static void View(GHOST_IWindow *window, bool stereo, int eye=0)
#define MEM_SAFE_FREE(v)
#define RNA_ENUM_ITEM_SEPR
Definition RNA_types.hh:676
@ PROP_SKIP_SAVE
Definition RNA_types.hh:344
@ PROP_HIDDEN
Definition RNA_types.hh:338
@ PROP_DISTANCE
Definition RNA_types.hh:256
@ PROP_ANGLE
Definition RNA_types.hh:252
#define C
Definition RandGen.cpp:29
#define UI_ITEM_NONE
#define NC_GEOM
Definition WM_types.hh:393
#define ND_DRAW
Definition WM_types.hh:461
#define ND_OB_ACTIVE
Definition WM_types.hh:440
#define ND_DATA
Definition WM_types.hh:509
@ KM_PRESS
Definition WM_types.hh:311
@ KM_RELEASE
Definition WM_types.hh:312
#define NC_SCENE
Definition WM_types.hh:378
@ OPTYPE_UNDO
Definition WM_types.hh:182
@ OPTYPE_REGISTER
Definition WM_types.hh:180
#define ND_LAYER_CONTENT
Definition WM_types.hh:453
#define NA_EDITED
Definition WM_types.hh:584
#define NC_GPENCIL
Definition WM_types.hh:399
#define ND_SPACE_VIEW3D
Definition WM_types.hh:528
#define NC_OBJECT
Definition WM_types.hh:379
#define NC_SPACE
Definition WM_types.hh:392
volatile int lock
#define U
#define A
BPy_StructRNA * depsgraph
long long int int64_t
static IndexMask from_predicate(const IndexMask &universe, GrainSize grain_size, IndexMaskMemory &memory, Fn &&predicate)
static IndexMask from_intersection(const IndexMask &mask_a, const IndexMask &mask_b, IndexMaskMemory &memory)
static constexpr IndexRange from_begin_size(const int64_t begin, const int64_t size)
const Value & lookup(const Key &key) const
Definition BLI_map.hh:545
KeyIterator keys() const &
Definition BLI_map.hh:875
ItemIterator items() const &
Definition BLI_map.hh:902
constexpr MutableSpan drop_front(const int64_t n) const
Definition BLI_span.hh:607
static VArray from_single(T value, const int64_t size)
static VArray from_span(Span< T > values)
static VArray from_container(ContainerT container)
void append(const T &value)
IndexRange index_range() const
const T & first() const
void clear()
Span< T > as_span() const
Definition BLI_array.hh:243
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:248
IndexRange index_range() const
Definition BLI_array.hh:360
bool is_empty() const
Definition BLI_array.hh:264
void copy_from(GSpan values)
void materialize_to_uninitialized(void *dst) const
static IndexMask from_predicate(const IndexMask &universe, GrainSize grain_size, IndexMaskMemory &memory, Fn &&predicate)
static IndexMask from_union(const IndexMask &mask_a, const IndexMask &mask_b, IndexMaskMemory &memory)
static IndexMask from_ranges(OffsetIndices< T > offsets, const IndexMask &mask, IndexMaskMemory &memory)
static IndexMask from_bools(Span< bool > bools, IndexMaskMemory &memory)
constexpr int64_t first() const
constexpr IndexRange drop_back(int64_t n) const
constexpr int64_t last(const int64_t n=0) const
constexpr int64_t size() const
constexpr int64_t start() const
constexpr bool contains(int64_t value) const
constexpr IndexRange drop_front(int64_t n) const
const Value * lookup_ptr(const Key &key) const
Definition BLI_map.hh:508
Value & lookup_or_add_default(const Key &key)
Definition BLI_map.hh:639
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:295
const Value & lookup(const Key &key) const
Definition BLI_map.hh:545
Value lookup_default(const Key &key, const Value &default_value) const
Definition BLI_map.hh:570
void add_new(const Key &key, const Value &value)
Definition BLI_map.hh:265
int64_t size() const
Definition BLI_map.hh:976
bool is_empty() const
Definition BLI_map.hh:986
auto add_or_modify(const Key &key, const CreateValueF &create_value, const ModifyValueF &modify_value) -> decltype(create_value(nullptr))
Definition BLI_map.hh:481
ItemIterator items() const &
Definition BLI_map.hh:902
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:573
constexpr MutableSpan drop_back(const int64_t n) const
Definition BLI_span.hh:618
constexpr void fill(const T &value) const
Definition BLI_span.hh:517
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
constexpr bool contains(const T &value) const
Definition BLI_span.hh:277
constexpr const char * c_str() const
void materialize(MutableSpan< T > r_span) const
static VArray from_single(T value, const int64_t size)
static VArray from_container(ContainerT container)
const Key * data() const
int64_t index_of_or_add(const Key &key)
int64_t size() const
int64_t size() const
void append(const T &value)
void insert(const int64_t insert_index, const T &value)
MutableSpan< T > as_mutable_span()
Span< T > as_span() const
GAttributeReader lookup_or_default(StringRef attribute_id, AttrDomain domain, AttrType data_type, const void *default_value=nullptr) const
bool contains(StringRef attribute_id) const
Attribute & add(std::string name, bke::AttrDomain domain, bke::AttrType data_type, Attribute::DataVariant data)
OffsetIndices< int > points_by_curve() const
IndexRange curves_range() const
MutableAttributeAccessor attributes_for_write()
VArray< int8_t > nurbs_knots_modes() const
void remove_curves(const IndexMask &curves_to_delete, const AttributeFilter &attribute_filter)
AttributeAccessor attributes() const
MutableSpan< int > offsets_for_write()
void remove_points(const IndexMask &points_to_delete, const AttributeFilter &attribute_filter)
bool nurbs_has_custom_knots() const
VArray< int8_t > curve_types() const
VArray< bool > cyclic() const
MutableSpan< bool > cyclic_for_write()
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)
Span< float3 > curve_plane_normals() const
MutableSpan< float > opacities_for_write()
MutableSpan< float > radii_for_write()
bke::CurvesGeometry & strokes_for_write()
const bke::CurvesGeometry & strokes() const
VArray< float > opacities() const
void set_texture_matrices(Span< float4x2 > matrices, const IndexMask &selection)
float4x4 to_world_space(const Object &object) const
const Map< FramesMapKeyT, GreasePencilFrame > & frames() const
std::optional< int > start_frame_at(int frame_number) const
float4x4 to_object_space(const Object &object) const
Map< FramesMapKeyT, GreasePencilFrame > & frames_for_write()
void to_indices(MutableSpan< T > r_indices) const
IndexMask slice_content(IndexRange range) const
void foreach_index_optimized(Fn &&fn) const
IndexMask complement(const IndexMask &universe, IndexMaskMemory &memory) const
void foreach_index(Fn &&fn) const
nullptr float
CCL_NAMESPACE_BEGIN struct Options options
static ushort indices[]
static float normals[][3]
#define INT16_MAX
uint pos
#define FLAT(type, name)
VecBase< float, D > step(VecOp< float, D >, VecOp< float, D >) RET
MatBase< 2, 4 > float2x4
MatBase< 4, 4 > float4x4
MatBase< 4, 3 > float4x3
VecBase< float, 2 > float2
VecBase< float, 4 > float4
VecBase< float, 3 > float3
void ED_operatortypes_grease_pencil_edit()
wmOperatorStatus ED_grease_pencil_join_objects_exec(bContext *C, wmOperator *op)
blender::bke::AttrDomain ED_grease_pencil_edit_selection_domain_get(const ToolSettings *tool_settings)
void GREASE_PENCIL_OT_stroke_trim(wmOperatorType *ot)
@ TOP
@ DOWN
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void * MEM_dupallocN(const void *vmemh)
Definition mallocn.cc:143
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
#define B
void invert_booleans(MutableSpan< bool > span)
void copy(const GVArray &src, GMutableSpan dst, int64_t grain_size=4096)
BooleanMix booleans_mix_calc(const VArray< bool > &varray, IndexRange range_to_check)
void fill_index_range(MutableSpan< T > span, const T start=0)
void gather_custom_knots(const bke::CurvesGeometry &src, const IndexMask &src_curves, int dst_curve_offset, bke::CurvesGeometry &dst)
void update_custom_knot_modes(const IndexMask &mask, const KnotsMode mode_for_regular, const KnotsMode mode_for_cyclic, bke::CurvesGeometry &curves)
IndexMask curve_type_point_selection(const bke::CurvesGeometry &curves, CurveType curve_type, IndexMaskMemory &memory)
void fill_points(OffsetIndices< int > points_by_curve, const IndexMask &curve_selection, GPointer value, GMutableSpan dst)
void copy_drawing_array(Span< const GreasePencilDrawingBase * > src_drawings, MutableSpan< GreasePencilDrawingBase * > dst_drawings)
CurvesGeometry curves_copy_curve_selection(const CurvesGeometry &curves, const IndexMask &curves_to_copy, const AttributeFilter &attribute_filter)
CurvesGeometry curves_copy_point_selection(const CurvesGeometry &curves, const IndexMask &points_to_copy, const AttributeFilter &attribute_filter)
auto attribute_filter_from_skip_ref(const Span< StringRef > skip)
void copy_attributes(const AttributeAccessor src_attributes, AttrDomain src_domain, AttrDomain dst_domain, const AttributeFilter &attribute_filter, MutableAttributeAccessor dst_attributes)
void gather_attributes(AttributeAccessor src_attributes, AttrDomain src_domain, AttrDomain dst_domain, const AttributeFilter &attribute_filter, const IndexMask &selection, MutableAttributeAccessor dst_attributes)
const CPPType & attribute_type_to_cpp_type(AttrType type)
Curves * curves_new_nomain(int points_num, int curves_num)
const EnumPropertyItem rna_enum_set_handle_type_items[]
static bool has_anything_selected(const Span< Curves * > curves_ids)
IndexMask retrieve_selected_curves(const bke::CurvesGeometry &curves, IndexMaskMemory &memory)
void duplicate_curves(bke::CurvesGeometry &curves, const IndexMask &mask)
void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask)
bke::CurvesGeometry split_points(const bke::CurvesGeometry &curves, const IndexMask &points_to_split)
void fill_selection_false(GMutableSpan selection)
Span< StringRef > get_curves_selection_attribute_names(const bke::CurvesGeometry &curves)
bke::GSpanAttributeWriter ensure_selection_attribute(bke::CurvesGeometry &curves, bke::AttrDomain selection_domain, bke::AttrType create_type, StringRef attribute_name)
IndexMask retrieve_selected_points(const bke::CurvesGeometry &curves, IndexMaskMemory &memory)
static void convert_to_nurbs(bke::CurvesGeometry &curves, const IndexMask &selection, const float threshold)
IndexMask retrieve_editable_and_selected_elements(Object &object, const bke::greasepencil::Drawing &drawing, int layer_index, const bke::AttrDomain selection_domain, IndexMaskMemory &memory)
static void GREASE_PENCIL_OT_extrude(wmOperatorType *ot)
static bke::GeometrySet join_geometries_with_transforms(Span< bke::GeometrySet > geometries, const VArray< float4x4 > &transforms)
static void GREASE_PENCIL_OT_texture_gradient(wmOperatorType *ot)
bool active_grease_pencil_poll(bContext *C)
IndexMask retrieve_editable_and_selected_strokes(Object &object, const bke::greasepencil::Drawing &drawing, int layer_index, IndexMaskMemory &memory)
static wmOperatorStatus grease_pencil_set_corner_type_exec(bContext *C, wmOperator *op)
static wmOperatorStatus grease_pencil_convert_curve_type_exec(bContext *C, wmOperator *op)
static wmOperatorStatus grease_pencil_set_active_material_exec(bContext *C, wmOperator *)
static void GREASE_PENCIL_OT_caps_set(wmOperatorType *ot)
blender::bke::CurvesGeometry curves_merge_by_distance(const bke::CurvesGeometry &src_curves, const float merge_distance, const IndexMask &selection, const bke::AttributeFilter &attribute_filter)
static bke::greasepencil::Layer & copy_layer(GreasePencil &grease_pencil_dst, bke::greasepencil::LayerGroup &group_dst, const bke::greasepencil::Layer &layer_src)
static void grease_pencil_reproject_ui(bContext *, wmOperator *op)
static bke::greasepencil::Layer & find_or_create_layer_in_dst_by_name(const int layer_index, const GreasePencil &grease_pencil_src, GreasePencil &grease_pencil_dst, Vector< int > &src_to_dst_layer_indices)
static wmOperatorStatus grease_pencil_delete_exec(bContext *C, wmOperator *)
static wmOperatorStatus grease_pencil_stroke_material_set_exec(bContext *C, wmOperator *op)
static void GREASE_PENCIL_OT_stroke_simplify(wmOperatorType *ot)
static void convert_to_catmull_rom(bke::CurvesGeometry &curves, const IndexMask &selection, const float threshold)
static wmOperatorStatus grease_pencil_set_curve_type_exec(bContext *C, wmOperator *op)
blender::Mutex grease_pencil_clipboard_lock
static wmOperatorStatus grease_pencil_extrude_exec(bContext *C, wmOperator *)
static wmOperatorStatus grease_pencil_outline_exec(bContext *C, wmOperator *op)
static void join_object_with_active(Main &bmain, Object &ob_src, Object &ob_dst, VectorSet< Material * > &materials)
IndexMask retrieve_editable_points(Object &object, const bke::greasepencil::Drawing &drawing, int layer_index, IndexMaskMemory &memory)
static wmOperatorStatus grease_pencil_stroke_smooth_exec(bContext *C, wmOperator *op)
static wmOperatorStatus grease_pencil_move_to_layer_invoke(bContext *C, wmOperator *op, const wmEvent *event)
bool ensure_active_keyframe(const Scene &scene, GreasePencil &grease_pencil, bke::greasepencil::Layer &layer, const bool duplicate_previous_key, bool &r_inserted_keyframe)
static void convert_to_bezier(bke::CurvesGeometry &curves, const IndexMask &selection, const float threshold)
static wmOperatorStatus grease_pencil_duplicate_exec(bContext *C, wmOperator *)
static wmOperatorStatus grease_pencil_dissolve_exec(bContext *C, wmOperator *op)
static void convert_to_poly(bke::CurvesGeometry &curves, const IndexMask &selection)
static bke::CurvesGeometry set_start_point(const bke::CurvesGeometry &curves, const IndexMask &mask)
static float calculate_radius_projection_factor(const RegionView3D *rv3d, const float3 &old_pos, const float3 &new_pos)
static bke::AttributeStorage merge_attributes(const bke::AttributeAccessor &a, const bke::AttributeAccessor &b, const int dst_size)
static wmOperatorStatus grease_pencil_texture_gradient_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static void GREASE_PENCIL_OT_set_curve_type(wmOperatorType *ot)
IndexMask retrieve_editable_and_all_selected_points(Object &object, const bke::greasepencil::Drawing &drawing, int layer_index, int handle_display, IndexMaskMemory &memory)
static void toggle_caps(MutableSpan< int8_t > caps, const IndexMask &strokes)
static void GREASE_PENCIL_OT_snap_cursor_to_selected(wmOperatorType *ot)
static wmOperatorStatus grease_pencil_copy_strokes_exec(bContext *C, wmOperator *op)
static void grease_pencil_convert_curve_type_ui(bContext *C, wmOperator *op)
static const EnumPropertyItem prop_outline_modes[]
static Array< int > clipboard_materials_remap(Main &bmain, Object &object)
static wmOperatorStatus grease_pencil_stroke_merge_by_distance_exec(bContext *C, wmOperator *op)
static bool grease_pencil_paste_strokes_poll(bContext *C)
bool editable_grease_pencil_point_selection_poll(bContext *C)
static bke::CurvesGeometry extrude_grease_pencil_curves(const bke::CurvesGeometry &src, const IndexMask &points_to_extrude)
static void GREASE_PENCIL_OT_set_start_point(wmOperatorType *ot)
static wmOperatorStatus grease_pencil_reproject_exec(bContext *C, wmOperator *op)
static void GREASE_PENCIL_OT_delete(wmOperatorType *ot)
static wmOperatorStatus grease_pencil_stroke_switch_direction_exec(bContext *C, wmOperator *)
static IndexMask simplify_fixed(const bke::CurvesGeometry &curves, const int step, const IndexMask &stroke_selection, IndexMaskMemory &memory)
bool editable_grease_pencil_with_region_view3d_poll(bContext *C)
static wmOperatorStatus grease_pencil_snap_to_cursor_exec(bContext *C, wmOperator *op)
static bool grease_pencil_separate_material(bContext &C, Main &bmain, Scene &scene, ViewLayer &view_layer, Base &base_prev, Object &object_src)
static IndexRange clipboard_paste_strokes_ex(Main &bmain, Object &object, const bke::CurvesGeometry &curves_to_paste, const float4x4 &object_to_paste_layer, const float4x4 &clipboard_to_world, const bool keep_world_transform, const bool paste_back, bke::greasepencil::Drawing &drawing)
static wmOperatorStatus grease_pencil_set_handle_type_exec(bContext *C, wmOperator *op)
static void remove_unused_materials(Main *bmain, Object *object)
static void GREASE_PENCIL_OT_stroke_switch_direction(wmOperatorType *ot)
Vector< DrawingInfo > retrieve_visible_drawings(const Scene &scene, const GreasePencil &grease_pencil, const bool do_onion_skinning)
static void GREASE_PENCIL_OT_set_active_material(wmOperatorType *ot)
static Clipboard & ensure_grease_pencil_clipboard()
static void GREASE_PENCIL_OT_outline(wmOperatorType *ot)
static wmOperatorStatus grease_pencil_set_material_exec(bContext *C, wmOperator *op)
static const EnumPropertyItem prop_simplify_modes[]
static void remap_material_indices(bke::greasepencil::Drawing &drawing, const Span< int > material_index_map)
IndexMask retrieve_editable_strokes_by_material(Object &object, const bke::greasepencil::Drawing &drawing, const int mat_i, IndexMaskMemory &memory)
static wmOperatorStatus grease_pencil_reset_uvs_exec(bContext *C, wmOperator *)
static wmOperatorStatus grease_pencil_clean_loose_exec(bContext *C, wmOperator *op)
static wmOperatorStatus grease_pencil_set_curve_resolution_exec(bContext *C, wmOperator *op)
static const EnumPropertyItem prop_separate_modes[]
static void GREASE_PENCIL_OT_copy(wmOperatorType *ot)
static void GREASE_PENCIL_OT_separate(wmOperatorType *ot)
static bool grease_pencil_separate_layer(bContext &C, Main &bmain, Scene &scene, ViewLayer &view_layer, Base &base_prev, Object &object_src)
static void GREASE_PENCIL_OT_delete_frame(wmOperatorType *ot)
static bke::greasepencil::LayerGroup & copy_layer_group_recursive(GreasePencil &grease_pencil_dst, bke::greasepencil::LayerGroup &parent_dst, const bke::greasepencil::LayerGroup &group_src, Map< StringRefNull, StringRefNull > &layer_name_map)
static void GREASE_PENCIL_OT_set_curve_resolution(wmOperatorType *ot)
static float4x3 expand_4x2_mat(float4x2 strokemat)
static wmOperatorStatus grease_pencil_cyclical_set_exec(bContext *C, wmOperator *op)
bool editable_grease_pencil_poll(bContext *C)
static void remap_vertex_groups(bke::greasepencil::Drawing &drawing, const Map< StringRefNull, StringRefNull > &vertex_group_map)
static const EnumPropertyItem prop_corner_types[]
static wmOperatorStatus grease_pencil_set_start_point_exec(bContext *C, wmOperator *)
static void GREASE_PENCIL_OT_set_uniform_opacity(wmOperatorType *ot)
static Array< int > add_materials_to_map(Object &object, VectorSet< Material * > &materials)
static void GREASE_PENCIL_OT_move_to_layer(wmOperatorType *ot)
static void GREASE_PENCIL_OT_snap_to_grid(wmOperatorType *ot)
static wmOperatorStatus grease_pencil_remove_fill_guides_exec(bContext *C, wmOperator *op)
static void GREASE_PENCIL_OT_clean_loose(wmOperatorType *ot)
static void GREASE_PENCIL_OT_stroke_smooth(wmOperatorType *ot)
IndexMask retrieve_editable_and_selected_points(Object &object, const bke::greasepencil::Drawing &drawing, int layer_index, IndexMaskMemory &memory)
static wmOperatorStatus grease_pencil_move_to_layer_exec(bContext *C, wmOperator *op)
static void GREASE_PENCIL_OT_remove_fill_guides(wmOperatorType *ot)
static void GREASE_PENCIL_OT_stroke_split(wmOperatorType *ot)
static wmOperatorStatus grease_pencil_snap_cursor_to_sel_exec(bContext *C, wmOperator *)
static void GREASE_PENCIL_OT_set_handle_type(wmOperatorType *ot)
static void GREASE_PENCIL_OT_stroke_reorder(wmOperatorType *ot)
static void GREASE_PENCIL_OT_stroke_merge_by_distance(wmOperatorType *ot)
static bool grease_pencil_snap_compute_centroid(const Scene &scene, const Object &object, const GreasePencil &grease_pencil, float3 &r_centroid, float3 &r_min, float3 &r_max)
static const EnumPropertyItem prop_dissolve_types[]
static wmOperatorStatus grease_pencil_set_uniform_opacity_exec(bContext *C, wmOperator *op)
Vector< MutableDrawingInfo > retrieve_editable_drawings_from_layer(const Scene &scene, GreasePencil &grease_pencil, const blender::bke::greasepencil::Layer &layer)
static wmOperatorStatus grease_pencil_clean_loose_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static void GREASE_PENCIL_OT_stroke_subdivide(wmOperatorType *ot)
static bool grease_pencil_snap_poll(bContext *C)
static wmOperatorStatus grease_pencil_caps_set_exec(bContext *C, wmOperator *op)
static void GREASE_PENCIL_OT_set_corner_type(wmOperatorType *ot)
static wmOperatorStatus gpencil_stroke_subdivide_exec(bContext *C, wmOperator *op)
IndexRange paste_all_strokes_from_clipboard(Main &bmain, Object &object, const float4x4 &object_to_paste_layer, const bool keep_world_transform, const bool paste_back, bke::greasepencil::Drawing &drawing)
static wmOperatorStatus grease_pencil_paste_strokes_exec(bContext *C, wmOperator *op)
static void GREASE_PENCIL_OT_reproject(wmOperatorType *ot)
static Map< StringRefNull, StringRefNull > add_vertex_groups(Object &object, GreasePencil &grease_pencil, const ListBase &vertex_group_names)
static bke::CurvesGeometry subdivide_last_segement(const bke::CurvesGeometry &curves, const IndexMask &strokes)
static bke::CurvesGeometry fit_poly_curves(bke::CurvesGeometry &curves, const IndexMask &selection, const float threshold)
static void copy_layer_group_content(GreasePencil &grease_pencil_dst, bke::greasepencil::LayerGroup &group_dst, const bke::greasepencil::LayerGroup &group_src, Map< StringRefNull, StringRefNull > &layer_name_map)
static wmOperatorStatus grease_pencil_texture_gradient_modal(bContext *C, wmOperator *op, const wmEvent *event)
static void GREASE_PENCIL_OT_convert_curve_type(wmOperatorType *ot)
static wmOperatorStatus grease_pencil_snap_to_grid_exec(bContext *C, wmOperator *)
static void GREASE_PENCIL_OT_snap_to_cursor(wmOperatorType *ot)
static wmOperatorStatus grease_pencil_set_uniform_thickness_exec(bContext *C, wmOperator *op)
IndexMask retrieve_editable_strokes(Object &object, const bke::greasepencil::Drawing &drawing, int layer_index, IndexMaskMemory &memory)
bool remove_fill_guides(bke::CurvesGeometry &curves)
static wmOperatorStatus grease_pencil_stroke_split_exec(bContext *C, wmOperator *)
Vector< MutableDrawingInfo > retrieve_editable_drawings(const Scene &scene, GreasePencil &grease_pencil)
static struct blender::ed::greasepencil::Clipboard * grease_pencil_clipboard
static void GREASE_PENCIL_OT_stroke_material_set(wmOperatorType *ot)
static void GREASE_PENCIL_OT_set_uniform_thickness(wmOperatorType *ot)
static void GREASE_PENCIL_OT_reset_uvs(wmOperatorType *ot)
static wmOperatorStatus grease_pencil_texture_gradient_exec(bContext *C, wmOperator *op)
static void GREASE_PENCIL_OT_duplicate(wmOperatorType *ot)
static const EnumPropertyItem * material_enum_itemf(bContext *C, PointerRNA *, PropertyRNA *, bool *r_free)
static Object * duplicate_grease_pencil_object(Main *bmain, Scene *scene, ViewLayer *view_layer, Base *base_prev, const GreasePencil &grease_pencil_src)
static void GREASE_PENCIL_OT_paste(wmOperatorType *ot)
static bke::GeometrySet join_geometries_with_transform(Span< bke::GeometrySet > geometries, const float4x4 &transform)
static void GREASE_PENCIL_OT_cyclical_set(wmOperatorType *ot)
Array< Vector< MutableDrawingInfo > > retrieve_editable_drawings_grouped_per_frame(const Scene &scene, GreasePencil &grease_pencil)
static wmOperatorStatus grease_pencil_stroke_simplify_exec(bContext *C, wmOperator *op)
static const EnumPropertyItem prop_cyclical_types[]
static wmOperatorStatus grease_pencil_delete_frame_exec(bContext *C, wmOperator *op)
static void grease_pencil_set_corner_type_ui(bContext *C, wmOperator *op)
static void GREASE_PENCIL_OT_dissolve(wmOperatorType *ot)
bool active_grease_pencil_layer_poll(bContext *C)
static wmOperatorStatus grease_pencil_separate_exec(bContext *C, wmOperator *op)
static bool grease_pencil_separate_selected(bContext &C, Main &bmain, Scene &scene, ViewLayer &view_layer, Base &base_prev, Object &object_src)
bke::CurvesGeometry create_curves_outline(const bke::greasepencil::Drawing &drawing, const IndexMask &strokes, const float4x4 &transform, const int corner_subdivisions, const float outline_radius, const float outline_offset, const int material_index)
static wmOperatorStatus grease_pencil_stroke_reorder_exec(bContext *C, wmOperator *op)
static const EnumPropertyItem prop_greasepencil_deleteframe_types[]
static void grease_pencil_simplify_ui(bContext *C, wmOperator *op)
static void GREASE_PENCIL_OT_set_material(wmOperatorType *ot)
static Array< bool > get_points_to_dissolve(bke::CurvesGeometry &curves, const IndexMask &mask, const DissolveMode mode)
static Array< int > get_reordered_indices(const IndexRange universe, const IndexMask &selected, const ReorderDirection direction)
Base * add_duplicate(Main *bmain, Scene *scene, ViewLayer *view_layer, Base *base, eDupli_ID_Flags dupflag)
void base_free_and_unlink(Main *bmain, Scene *scene, Object *ob)
void snap_object_context_destroy(SnapObjectContext *sctx)
SnapObjectContext * snap_object_context_create(Scene *scene, int flag)
bool snap_object_project_ray(SnapObjectContext *sctx, Depsgraph *depsgraph, const View3D *v3d, const SnapObjectParams *params, const float ray_start[3], const float ray_normal[3], float *ray_depth, float r_co[3], float r_no[3])
bke::GeometrySet join_geometries(Span< bke::GeometrySet > geometries, const bke::AttributeFilter &attribute_filter, const std::optional< Span< bke::GeometryComponent::Type > > &component_types_to_join=std::nullopt, bool allow_merging_instance_references=true)
bke::CurvesGeometry remove_points_and_split(const bke::CurvesGeometry &curves, const IndexMask &mask)
bke::CurvesGeometry reorder_curves_geometry(const bke::CurvesGeometry &src_curves, Span< int > old_by_new_map, const bke::AttributeFilter &attribute_filter)
Definition reorder.cc:348
void smooth_curve_attribute(const IndexMask &curves_to_smooth, const OffsetIndices< int > points_by_curve, const VArray< bool > &point_selection, const VArray< bool > &cyclic, int iterations, float influence, bool smooth_ends, bool keep_shape, GMutableSpan attribute_data)
IndexMask simplify_curve_attribute(const Span< float3 > positions, const IndexMask &curves_selection, const OffsetIndices< int > points_by_curve, const VArray< bool > &cyclic, float epsilon, GSpan attribute_data, IndexMaskMemory &memory)
bke::CurvesGeometry fit_poly_to_bezier_curves(const bke::CurvesGeometry &src_curves, const IndexMask &curve_selection, const VArray< float > &thresholds, const VArray< bool > &corners, FitMethod method, const bke::AttributeFilter &attribute_filter)
Definition fit_curves.cc:19
CurvesGeometry resample_to_evaluated(const CurvesGeometry &src_curves, const IndexMask &selection, const ResampleCurvesOutputAttributeIDs &output_ids={})
CurvesGeometry resample_to_length(const CurvesGeometry &src_curves, const IndexMask &selection, const VArray< float > &sample_lengths, const ResampleCurvesOutputAttributeIDs &output_ids={}, bool keep_last_segment=false)
bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves, const IndexMask &selection, const VArray< int > &cuts, const bke::AttributeFilter &attribute_filter={})
bke::CurvesGeometry convert_curves(const bke::CurvesGeometry &src_curves, const IndexMask &selection, CurveType dst_type, const bke::AttributeFilter &attribute_filter, const ConvertCurvesOptions &options={})
void masked_fill(MutableSpan< T > data, const T &value, const IndexMask &mask)
AngleRadianBase< float > AngleRadian
T length_squared(const VecBase< T, Size > &a)
MatBase< T, NumCol, NumRow > transpose(const MatBase< T, NumRow, NumCol > &mat)
T pow(const T &x, const T &power)
T floor(const T &a)
T distance(const T &a, const T &b)
T length(const VecBase< T, Size > &a)
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
CartesianBasis invert(const CartesianBasis &basis)
T midpoint(const T &a, const T &b)
AxisSigned cross(const AxisSigned a, const AxisSigned b)
MatT from_scale(const VecBase< typename MatT::base_type, ScaleDim > &scale)
void min_max(const T &value, T &min, T &max)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
VecBase< T, 3 > transform_direction(const MatBase< T, 3, 3 > &mat, const VecBase< T, 3 > &direction)
MatT from_rotation(const RotationT &rotation)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
void copy_group_sizes(OffsetIndices< int > offsets, const IndexMask &mask, MutableSpan< int > sizes)
OffsetIndices< int > accumulate_counts_to_offsets(MutableSpan< int > counts_to_offsets, int start_offset=0)
void parallel_for_each(Range &&range, const Function &function)
Definition BLI_task.hh:56
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
MatBase< float, 2, 2 > float2x2
MatBase< float, 4, 4 > float4x4
VecBase< float, 4 > float4
VecBase< float, 2 > float2
MatBase< float, 4, 2 > float4x2
MatBase< float, 3, 2 > float3x2
MatBase< float, 4, 3 > float4x3
std::mutex Mutex
Definition BLI_mutex.hh:47
VecBase< float, 3 > float3
float wrap(float value, float max, float min)
Definition node_math.h:103
static void unique_name(bNode *node)
const char * name
return ret
void RNA_string_set(PointerRNA *ptr, const char *name, const char *value)
PropertyRNA * RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
bool RNA_property_is_set(PointerRNA *ptr, PropertyRNA *prop)
int RNA_int_get(PointerRNA *ptr, const char *name)
float RNA_float_get(PointerRNA *ptr, const char *name)
std::string RNA_string_get(PointerRNA *ptr, const char *name)
bool RNA_boolean_get(PointerRNA *ptr, const char *name)
PointerRNA RNA_pointer_create_discrete(ID *id, StructRNA *type, void *data)
int RNA_enum_get(PointerRNA *ptr, const char *name)
const EnumPropertyItem rna_enum_curves_type_items[]
Definition rna_curves.cc:24
PropertyRNA * RNA_def_float_factor(StructOrFunctionRNA *cont_, const char *identifier, const float default_value, const float hardmin, const float hardmax, const char *ui_name, const char *ui_description, const float softmin, const float softmax)
PropertyRNA * RNA_def_string(StructOrFunctionRNA *cont_, const char *identifier, const char *default_value, const int maxlen, const char *ui_name, const char *ui_description)
PropertyRNA * RNA_def_float_distance(StructOrFunctionRNA *cont_, const char *identifier, const float default_value, const float hardmin, const float hardmax, const char *ui_name, const char *ui_description, const float softmin, const float softmax)
PropertyRNA * RNA_def_float(StructOrFunctionRNA *cont_, const char *identifier, const float default_value, const float hardmin, const float hardmax, const char *ui_name, const char *ui_description, const float softmin, const float softmax)
PropertyRNA * RNA_def_enum(StructOrFunctionRNA *cont_, const char *identifier, const EnumPropertyItem *items, const int default_value, const char *ui_name, const char *ui_description)
void RNA_enum_item_end(EnumPropertyItem **items, int *totitem)
void RNA_enum_item_add(EnumPropertyItem **items, int *totitem, const EnumPropertyItem *item)
PropertyRNA * RNA_def_boolean(StructOrFunctionRNA *cont_, const char *identifier, const bool default_value, const char *ui_name, const char *ui_description)
void RNA_def_property_translation_context(PropertyRNA *prop, const char *context)
void RNA_def_property_flag(PropertyRNA *prop, PropertyFlag flag)
void RNA_def_enum_funcs(PropertyRNA *prop, EnumPropertyItemFunc itemfunc)
void RNA_def_property_subtype(PropertyRNA *prop, PropertySubType subtype)
PropertyRNA * RNA_def_int(StructOrFunctionRNA *cont_, const char *identifier, const int default_value, const int hardmin, const int hardmax, const char *ui_name, const char *ui_description, const int softmin, const int softmax)
const EnumPropertyItem rna_enum_dummy_DEFAULT_items[]
Definition rna_rna.cc:32
void * regiondata
bAction * action
struct Object * object
ListBase vertex_group_names
CurvesGeometry geometry
char * rna_path
ChannelDriver * driver
GreasePencilLayerTreeNode base
GreasePencilDrawingBase ** drawing_array
struct AnimData * adt
struct AttributeStorage attribute_storage
Definition DNA_ID.h:414
char name[258]
Definition DNA_ID.h:432
unsigned int session_uid
Definition DNA_ID.h:462
ListBase materials
Definition BKE_main.hh:284
struct AnimData * adt
float viewmat[4][4]
float viewinv[4][4]
struct ToolSettings * toolsettings
struct RenderData r
View3DCursor cursor
struct Object * camera
View3DOverlay overlay
static ArrayData from_uninitialized(const CPPType &type, int64_t domain_size)
static GeometrySet from_instances(Instances *instances, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
static GeometrySet from_curves(Curves *curves, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
const Curves * get_curves() const
Vector< std::pair< uint, int > > materials
void use_property_decorate_set(bool is_sep)
uiLayout & row(bool align)
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)
wmEventType type
Definition WM_types.hh:757
short val
Definition WM_types.hh:759
uint is_active
Definition WM_types.hh:639
StructRNA * srna
Definition WM_types.hh:1127
struct ReportList * reports
IDProperty * properties
struct uiLayout * layout
struct wmOperatorType * type
struct PointerRNA * ptr
i
Definition text_draw.cc:230
ParamHandle ** handles
void WM_cursor_wait(bool val)
@ WM_CURSOR_EDIT
Definition wm_cursors.hh:19
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
void WM_menu_name_call(bContext *C, const char *menu_name, blender::wm::OpCallContext context)
@ LEFTMOUSE
PointerRNA * ptr
Definition wm_files.cc:4238
wmOperatorType * ot
Definition wm_files.cc:4237
void WM_gesture_straightline_cancel(bContext *C, wmOperator *op)
wmOperatorStatus WM_gesture_straightline_invoke(bContext *C, wmOperator *op, const wmEvent *event)
wmOperatorStatus WM_gesture_straightline_modal(bContext *C, wmOperator *op, const wmEvent *event)
void WM_operator_properties_gesture_straightline(wmOperatorType *ot, int cursor)
void WM_operatortype_append(void(*opfunc)(wmOperatorType *))
wmOperatorStatus WM_menu_invoke(bContext *C, wmOperator *op, const wmEvent *)
wmOperatorStatus WM_operator_props_popup_confirm_ex(bContext *C, wmOperator *op, const wmEvent *, std::optional< std::string > title, std::optional< std::string > confirm_text, const bool cancel_default, std::optional< std::string > message)