Blender V4.5
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_utildefines.h"
24#include "BLI_vector.hh"
25#include "BLT_translation.hh"
26
27#include "DNA_anim_types.h"
28#include "DNA_array_utils.hh"
30#include "DNA_material_types.h"
31#include "DNA_object_types.h"
32#include "DNA_scene_types.h"
33#include "DNA_space_types.h"
34#include "DNA_view3d_types.h"
36
37#include "BKE_anim_data.hh"
38#include "BKE_animsys.h"
39#include "BKE_attribute.hh"
40#include "BKE_context.hh"
41#include "BKE_curves_utils.hh"
42#include "BKE_customdata.hh"
43#include "BKE_deform.hh"
44#include "BKE_fcurve_driver.h"
45#include "BKE_grease_pencil.hh"
46#include "BKE_instances.hh"
47#include "BKE_lib_id.hh"
48#include "BKE_main.hh"
49#include "BKE_material.hh"
50#include "BKE_preview_image.hh"
51#include "BKE_report.hh"
52#include "BKE_scene.hh"
53
54#include "RNA_access.hh"
55#include "RNA_define.hh"
56#include "RNA_enum_types.hh"
57
58#include "DEG_depsgraph.hh"
60
61#include "ED_curves.hh"
62#include "ED_grease_pencil.hh"
63#include "ED_object.hh"
65#include "ED_view3d.hh"
66
68#include "GEO_fit_curves.hh"
71#include "GEO_reorder.hh"
73#include "GEO_set_curve_type.hh"
75#include "GEO_smooth_curves.hh"
77
78#include "UI_interface_c.hh"
79
80#include "UI_resources.hh"
81#include <limits>
82
84
85/* -------------------------------------------------------------------- */
88
90{
91 const Scene *scene = CTX_data_scene(C);
93 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
94
95 const int iterations = RNA_int_get(op->ptr, "iterations");
96 const float influence = RNA_float_get(op->ptr, "factor");
97 const bool keep_shape = RNA_boolean_get(op->ptr, "keep_shape");
98 const bool smooth_ends = RNA_boolean_get(op->ptr, "smooth_ends");
99
100 const bool smooth_position = RNA_boolean_get(op->ptr, "smooth_position");
101 const bool smooth_radius = RNA_boolean_get(op->ptr, "smooth_radius");
102 const bool smooth_opacity = RNA_boolean_get(op->ptr, "smooth_opacity");
103
104 if (!(smooth_position || smooth_radius || smooth_opacity)) {
105 /* There's nothing to be smoothed, return. */
106 return OPERATOR_FINISHED;
107 }
108
109 bool changed = false;
110 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
111 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
113 if (curves.is_empty()) {
114 return;
115 }
116
117 IndexMaskMemory memory;
119 *object, info.drawing, info.layer_index, memory);
120 if (strokes.is_empty()) {
121 return;
122 }
123
124 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
125 const OffsetIndices points_by_curve = curves.points_by_curve();
126 const VArray<bool> cyclic = curves.cyclic();
127 const VArray<bool> point_selection = *curves.attributes().lookup_or_default<bool>(
128 ".selection", bke::AttrDomain::Point, true);
129
130 if (smooth_position) {
131 bke::GSpanAttributeWriter positions = attributes.lookup_for_write_span("position");
133 points_by_curve,
134 point_selection,
135 cyclic,
136 iterations,
137 influence,
138 smooth_ends,
139 keep_shape,
140 positions.span);
141 positions.finish();
142 changed = true;
143 }
144 if (smooth_opacity && info.drawing.opacities().is_span()) {
145 bke::GSpanAttributeWriter opacities = attributes.lookup_for_write_span("opacity");
147 points_by_curve,
148 point_selection,
149 cyclic,
150 iterations,
151 influence,
152 smooth_ends,
153 false,
154 opacities.span);
155 opacities.finish();
156 changed = true;
157 }
158 if (smooth_radius && info.drawing.radii().is_span()) {
159 bke::GSpanAttributeWriter radii = attributes.lookup_for_write_span("radius");
161 points_by_curve,
162 point_selection,
163 cyclic,
164 iterations,
165 influence,
166 smooth_ends,
167 false,
168 radii.span);
169 radii.finish();
170 changed = true;
171 }
172 });
173
174 if (changed) {
175 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
176 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
177 }
178
179 return OPERATOR_FINISHED;
180}
181
183{
184 PropertyRNA *prop;
185
186 ot->name = "Smooth Stroke";
187 ot->idname = "GREASE_PENCIL_OT_stroke_smooth";
188 ot->description = "Smooth selected strokes";
189
192
194
195 prop = RNA_def_int(ot->srna, "iterations", 10, 1, 100, "Iterations", "", 1, 30);
197 RNA_def_float(ot->srna, "factor", 1.0f, 0.0f, 1.0f, "Factor", "", 0.0f, 1.0f);
198 RNA_def_boolean(ot->srna, "smooth_ends", false, "Smooth Endpoints", "");
199 RNA_def_boolean(ot->srna, "keep_shape", false, "Keep Shape", "");
200
201 RNA_def_boolean(ot->srna, "smooth_position", true, "Position", "");
202 RNA_def_boolean(ot->srna, "smooth_radius", true, "Radius", "");
203 RNA_def_boolean(ot->srna, "smooth_opacity", false, "Opacity", "");
204}
205
207
208/* -------------------------------------------------------------------- */
211
212enum class SimplifyMode {
213 FIXED = 0,
216 MERGE = 3,
217};
218
221 "FIXED",
222 0,
223 "Fixed",
224 "Delete alternating vertices in the stroke, except extremes"},
226 "ADAPTIVE",
227 0,
228 "Adaptive",
229 "Use a Ramer-Douglas-Peucker algorithm to simplify the stroke preserving main shape"},
231 "SAMPLE",
232 0,
233 "Sample",
234 "Re-sample the stroke with segments of the specified length"},
236 "MERGE",
237 0,
238 "Merge",
239 "Simplify the stroke by merging vertices closer than a given distance"},
240 {0, nullptr, 0, nullptr, nullptr},
241};
242
244 const int step,
245 const IndexMask &stroke_selection,
246 IndexMaskMemory &memory)
247{
248 const OffsetIndices points_by_curve = curves.points_by_curve();
249 const Array<int> point_to_curve_map = curves.point_to_curve_map();
250
251 const IndexMask selected_points = IndexMask::from_ranges(
252 points_by_curve, stroke_selection, memory);
253
254 /* Find points to keep among selected points. */
255 const IndexMask selected_to_keep = IndexMask::from_predicate(
256 selected_points, GrainSize(2048), memory, [&](const int64_t i) {
257 const int curve_i = point_to_curve_map[i];
258 const IndexRange points = points_by_curve[curve_i];
259 if (points.size() <= 2) {
260 return true;
261 }
262 const int local_i = i - points.start();
263 return (local_i % int(math::pow(2.0f, float(step))) == 0) || points.last() == i;
264 });
265
266 /* All the points that are not selected are also kept. */
268 {selected_to_keep, selected_points.complement(curves.points_range(), memory)}, memory);
269}
270
272{
273 const Scene *scene = CTX_data_scene(C);
275 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
276
277 const SimplifyMode mode = SimplifyMode(RNA_enum_get(op->ptr, "mode"));
278
279 bool changed = false;
280 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
281 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
283 if (curves.is_empty()) {
284 return;
285 }
286
287 IndexMaskMemory memory;
289 *object, info.drawing, info.layer_index, memory);
290 if (strokes.is_empty()) {
291 return;
292 }
293
294 switch (mode) {
295 case SimplifyMode::FIXED: {
296 const int steps = RNA_int_get(op->ptr, "steps");
297 const IndexMask points_to_keep = simplify_fixed(curves, steps, strokes, memory);
298 if (points_to_keep.is_empty()) {
299 info.drawing.strokes_for_write() = {};
300 break;
301 }
302 if (points_to_keep.size() == curves.points_num()) {
303 break;
304 }
306 curves, points_to_keep, {});
308 changed = true;
309 break;
310 }
312 const float simplify_factor = RNA_float_get(op->ptr, "factor");
313 const IndexMask points_to_delete = geometry::simplify_curve_attribute(
314 curves.positions(),
315 strokes,
316 curves.points_by_curve(),
317 curves.cyclic(),
318 simplify_factor,
319 curves.positions(),
320 memory);
321 info.drawing.strokes_for_write().remove_points(points_to_delete, {});
323 changed = true;
324 break;
325 }
327 const float resample_length = RNA_float_get(op->ptr, "length");
329 curves, strokes, VArray<float>::ForSingle(resample_length, curves.curves_num()), {});
331 changed = true;
332 break;
333 }
334 case SimplifyMode::MERGE: {
335 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
336 const Array<int> point_to_curve_map = curves.point_to_curve_map();
337 const float merge_distance = RNA_float_get(op->ptr, "distance");
338 const IndexMask selected_points = IndexMask::from_ranges(points_by_curve, strokes, memory);
339 const IndexMask filtered_points = IndexMask::from_predicate(
340 selected_points, GrainSize(2048), memory, [&](const int64_t i) {
341 const int curve_i = point_to_curve_map[i];
342 const IndexRange points = points_by_curve[curve_i];
343 if (points.drop_front(1).drop_back(1).contains(i)) {
344 return true;
345 }
346 return false;
347 });
349 curves, merge_distance, filtered_points, {});
351 changed = true;
352 break;
353 }
354 default:
355 break;
356 }
357 });
358
359 if (changed) {
360 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
361 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
362 }
363 return OPERATOR_FINISHED;
364}
365
367{
368 uiLayout *layout = op->layout;
370
372
373 uiLayoutSetPropSep(layout, true);
374 uiLayoutSetPropDecorate(layout, false);
375
376 layout->prop(&ptr, "mode", UI_ITEM_NONE, std::nullopt, ICON_NONE);
377
378 const SimplifyMode mode = SimplifyMode(RNA_enum_get(op->ptr, "mode"));
379
380 switch (mode) {
382 layout->prop(&ptr, "steps", UI_ITEM_NONE, std::nullopt, ICON_NONE);
383 break;
385 layout->prop(&ptr, "factor", UI_ITEM_NONE, std::nullopt, ICON_NONE);
386 break;
388 layout->prop(&ptr, "length", UI_ITEM_NONE, std::nullopt, ICON_NONE);
389 break;
391 layout->prop(&ptr, "distance", UI_ITEM_NONE, std::nullopt, ICON_NONE);
392 break;
393 default:
394 break;
395 }
396}
397
399{
400 PropertyRNA *prop;
401
402 ot->name = "Simplify Stroke";
403 ot->idname = "GREASE_PENCIL_OT_stroke_simplify";
404 ot->description = "Simplify selected strokes";
405
408
410
412
413 prop = RNA_def_float(ot->srna, "factor", 0.01f, 0.0f, 100.0f, "Factor", "", 0.0f, 100.0f);
415 prop = RNA_def_float(ot->srna, "length", 0.05f, 0.01f, 100.0f, "Length", "", 0.01f, 1.0f);
417 prop = RNA_def_float(ot->srna, "distance", 0.01f, 0.0f, 100.0f, "Distance", "", 0.0f, 1.0f);
419 prop = RNA_def_int(ot->srna, "steps", 1, 0, 50, "Steps", "", 0.0f, 10);
421 prop = RNA_def_enum(ot->srna,
422 "mode",
424 0,
425 "Mode",
426 "Method used for simplifying stroke points");
428}
429
431
432/* -------------------------------------------------------------------- */
435
437{
438 const Scene *scene = CTX_data_scene(C);
440 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
441
443 scene->toolsettings);
444
445 bool changed = false;
446 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
447 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
448 IndexMaskMemory memory;
450 *object, info.drawing, info.layer_index, selection_domain, memory);
451 if (elements.is_empty()) {
452 return;
453 }
454
456 if (selection_domain == bke::AttrDomain::Curve) {
457 curves.remove_curves(elements, {});
458 }
459 else if (selection_domain == bke::AttrDomain::Point) {
461 }
463 changed = true;
464 });
465
466 if (changed) {
467 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
468 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
469 }
470 return OPERATOR_FINISHED;
471}
472
474{
475 ot->name = "Delete";
476 ot->idname = "GREASE_PENCIL_OT_delete";
477 ot->description = "Delete selected strokes or points";
478
481
483}
484
486
487/* -------------------------------------------------------------------- */
490
491enum class DissolveMode : int8_t {
498};
499
501 {int(DissolveMode::POINTS), "POINTS", 0, "Dissolve", "Dissolve selected points"},
503 "BETWEEN",
504 0,
505 "Dissolve Between",
506 "Dissolve points between selected points"},
508 "UNSELECT",
509 0,
510 "Dissolve Unselect",
511 "Dissolve all unselected points"},
512 {0, nullptr, 0, nullptr, nullptr},
513};
514
516 const IndexMask &mask,
517 const DissolveMode mode)
518{
519 const VArray<bool> selection = *curves.attributes().lookup_or_default<bool>(
520 ".selection", bke::AttrDomain::Point, true);
521
522 Array<bool> points_to_dissolve(curves.points_num(), false);
523 selection.materialize(mask, points_to_dissolve);
524
525 if (mode == DissolveMode::POINTS) {
526 return points_to_dissolve;
527 }
528
529 /* Both `between` and `unselect` have the unselected point being the ones dissolved so we need
530 * to invert. */
532
533 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
534 /* Because we are going to invert, these become the points to keep. */
535 MutableSpan<bool> points_to_keep = points_to_dissolve.as_mutable_span();
536
537 threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) {
538 for (const int64_t curve_i : range) {
539 const IndexRange points = points_by_curve[curve_i];
540 const Span<bool> curve_selection = points_to_dissolve.as_span().slice(points);
541 /* The unselected curves should not be dissolved. */
542 if (!curve_selection.contains(true)) {
543 points_to_keep.slice(points).fill(true);
544 continue;
545 }
546
547 /* `between` is just `unselect` but with the first and last segments not getting
548 * dissolved. */
549 if (mode != DissolveMode::BETWEEN) {
550 continue;
551 }
552
553 const Vector<IndexRange> deselection_ranges = array_utils::find_all_ranges(curve_selection,
554 false);
555
556 if (deselection_ranges.size() != 0) {
557 const IndexRange first_range = deselection_ranges.first().shift(points.first());
558 const IndexRange last_range = deselection_ranges.last().shift(points.first());
559
560 /* Ranges should only be fill if the first/last point matches the start/end point
561 * of the segment. */
562 if (first_range.first() == points.first()) {
563 points_to_keep.slice(first_range).fill(true);
564 }
565 if (last_range.last() == points.last()) {
566 points_to_keep.slice(last_range).fill(true);
567 }
568 }
569 }
570 });
571
572 array_utils::invert_booleans(points_to_dissolve);
573
574 return points_to_dissolve;
575}
576
578{
579 const Scene *scene = CTX_data_scene(C);
581 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
582
583 const DissolveMode mode = DissolveMode(RNA_enum_get(op->ptr, "type"));
584
585 bool changed = false;
586 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
587 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
589 if (curves.is_empty()) {
590 return;
591 }
592
593 IndexMaskMemory memory;
595 *object, info.drawing, info.layer_index, memory);
596 if (points.is_empty()) {
597 return;
598 }
599
600 const Array<bool> points_to_dissolve = get_points_to_dissolve(curves, points, mode);
601 if (points_to_dissolve.as_span().contains(true)) {
602 curves.remove_points(IndexMask::from_bools(points_to_dissolve, memory), {});
604 changed = true;
605 }
606 });
607
608 if (changed) {
609 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
610 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
611 }
612 return OPERATOR_FINISHED;
613}
614
616{
617 PropertyRNA *prop;
618
619 ot->name = "Dissolve";
620 ot->idname = "GREASE_PENCIL_OT_dissolve";
621 ot->description = "Delete selected points without splitting strokes";
622
623 ot->invoke = WM_menu_invoke;
626
628
629 ot->prop = prop = RNA_def_enum(ot->srna,
630 "type",
632 0,
633 "Type",
634 "Method used for dissolving stroke points");
637}
638
640
641/* -------------------------------------------------------------------- */
644
645enum class DeleteFrameMode : int8_t {
650};
651
654 "ACTIVE_FRAME",
655 0,
656 "Active Frame",
657 "Deletes current frame in the active layer"},
659 "ALL_FRAMES",
660 0,
661 "All Active Frames",
662 "Delete active frames for all layers"},
663 {0, nullptr, 0, nullptr, nullptr},
664};
665
667{
668 const Scene *scene = CTX_data_scene(C);
670 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
671 const int current_frame = scene->r.cfra;
672
673 const DeleteFrameMode mode = DeleteFrameMode(RNA_enum_get(op->ptr, "type"));
674
675 bool changed = false;
676 if (mode == DeleteFrameMode::ACTIVE_FRAME && grease_pencil.has_active_layer()) {
677 bke::greasepencil::Layer &layer = *grease_pencil.get_active_layer();
678 if (layer.is_editable() && layer.start_frame_at(current_frame)) {
679 changed |= grease_pencil.remove_frames(layer, {*layer.start_frame_at(current_frame)});
680 }
681 }
682 else if (mode == DeleteFrameMode::ALL_FRAMES) {
683 for (bke::greasepencil::Layer *layer : grease_pencil.layers_for_write()) {
684 if (layer->is_editable() && layer->start_frame_at(current_frame)) {
685 changed |= grease_pencil.remove_frames(*layer, {*layer->start_frame_at(current_frame)});
686 }
687 }
688 }
689
690 if (changed) {
691 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
692 WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_EDITED, &grease_pencil);
694 }
695
696 return OPERATOR_FINISHED;
697}
698
700{
701 PropertyRNA *prop;
702
703 ot->name = "Delete Frame";
704 ot->idname = "GREASE_PENCIL_OT_delete_frame";
705 ot->description = "Delete Grease Pencil Frame(s)";
706
707 ot->invoke = WM_menu_invoke;
710
712
713 ot->prop = prop = RNA_def_enum(ot->srna,
714 "type",
716 0,
717 "Type",
718 "Method used for deleting Grease Pencil frames");
720}
721
722
723/* -------------------------------------------------------------------- */
726
728{
729 using namespace blender;
730 Main *bmain = CTX_data_main(C);
731 const Scene *scene = CTX_data_scene(C);
733 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
734 Material *ma = nullptr;
735 char name[MAX_ID_NAME - 2];
736 RNA_string_get(op->ptr, "material", name);
737
738 int material_index = object->actcol - 1;
739
740 if (name[0] != '\0') {
741 ma = reinterpret_cast<Material *>(BKE_libblock_find_name(bmain, ID_MA, name));
742 if (ma == nullptr) {
743 BKE_reportf(op->reports, RPT_WARNING, TIP_("Material '%s' could not be found"), name);
744 return OPERATOR_CANCELLED;
745 }
746
747 /* Find slot index. */
748 material_index = BKE_object_material_index_get(object, ma);
749 }
750
751 if (material_index == -1) {
752 return OPERATOR_CANCELLED;
753 }
754
755 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
756 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
757 IndexMaskMemory memory;
759 *object, info.drawing, info.layer_index, memory);
760 if (strokes.is_empty()) {
761 return;
762 }
763
766 curves.attributes_for_write().lookup_or_add_for_write_span<int>("material_index",
768 index_mask::masked_fill(materials.span, material_index, strokes);
769 materials.finish();
770 });
771
772 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
773 WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_EDITED, &grease_pencil);
774
775 return OPERATOR_FINISHED;
776}
777
779{
780 ot->name = "Assign Material";
781 ot->idname = "GREASE_PENCIL_OT_stroke_material_set";
782 ot->description = "Assign the active material slot to the selected strokes";
783
786
788
789 ot->prop = RNA_def_string(
790 ot->srna, "material", nullptr, MAX_ID_NAME - 2, "Material", "Name of the material");
792}
793
794
795/* -------------------------------------------------------------------- */
798
799enum class CyclicalMode : int8_t {
801 CLOSE = 0,
803 OPEN = 1,
806};
807
809 {int(CyclicalMode::CLOSE), "CLOSE", 0, "Close All", ""},
810 {int(CyclicalMode::OPEN), "OPEN", 0, "Open All", ""},
811 {int(CyclicalMode::TOGGLE), "TOGGLE", 0, "Toggle", ""},
812 {0, nullptr, 0, nullptr, nullptr},
813};
814
816 const IndexMask &strokes)
817{
818 const VArray<bool> cyclic = curves.cyclic();
819 const Span<float3> positions = curves.positions();
820 curves.ensure_evaluated_lengths();
821
822 Array<int> use_cuts(curves.points_num(), 0);
823 const OffsetIndices points_by_curve = curves.points_by_curve();
824
825 strokes.foreach_index(GrainSize(4096), [&](const int curve_i) {
826 if (cyclic[curve_i]) {
827 const IndexRange points = points_by_curve[curve_i];
828 const float end_distance = math::distance(positions[points.first()],
829 positions[points.last()]);
830
831 /* Because the curve is already cyclical the last segment has to be subtracted. */
832 const float curve_length = curves.evaluated_length_total_for_curve(curve_i, true) -
833 end_distance;
834
835 /* Calculate cuts to match the average density. */
836 const float point_density = float(points.size()) / curve_length;
837 use_cuts[points.last()] = int(point_density * end_distance);
838 }
839 });
840
841 const VArray<int> cuts = VArray<int>::ForSpan(use_cuts.as_span());
842
843 return geometry::subdivide_curves(curves, strokes, cuts);
844}
845
847{
848 const Scene *scene = CTX_data_scene(C);
850 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
851
852 const CyclicalMode mode = CyclicalMode(RNA_enum_get(op->ptr, "type"));
853 const bool subdivide_cyclic_segment = RNA_boolean_get(op->ptr, "subdivide_cyclic_segment");
854
855 bool changed = false;
856 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
857 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
859 if (mode == CyclicalMode::OPEN && !curves.attributes().contains("cyclic")) {
860 /* Avoid creating unneeded attribute. */
861 return;
862 }
863
864 IndexMaskMemory memory;
866 *object, info.drawing, info.layer_index, memory);
867 if (strokes.is_empty()) {
868 return;
869 }
870
871 MutableSpan<bool> cyclic = curves.cyclic_for_write();
872 switch (mode) {
874 index_mask::masked_fill(cyclic, true, strokes);
875 break;
877 index_mask::masked_fill(cyclic, false, strokes);
878 break;
880 array_utils::invert_booleans(cyclic, strokes);
881 break;
882 }
883
884 /* Remove the attribute if it is empty. */
885 if (mode != CyclicalMode::CLOSE) {
887 curves.attributes_for_write().remove("cyclic");
888 }
889 }
890
891 if (subdivide_cyclic_segment) {
892 /* Update to properly calculate the lengths. */
893 curves.tag_topology_changed();
894
896 }
897
899 changed = true;
900 });
901
902 if (changed) {
903 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
904 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
905 }
906
907 return OPERATOR_FINISHED;
908}
909
911{
912 ot->name = "Set Cyclical State";
913 ot->idname = "GREASE_PENCIL_OT_cyclical_set";
914 ot->description = "Close or open the selected stroke adding a segment from last to first point";
915
916 ot->invoke = WM_menu_invoke;
919
921
922 ot->prop = RNA_def_enum(
923 ot->srna, "type", prop_cyclical_types, int(CyclicalMode::TOGGLE), "Type", "");
924
925 RNA_def_boolean(ot->srna,
926 "subdivide_cyclic_segment",
927 true,
928 "Match Point Density",
929 "Add point in the new segment to keep the same density");
930}
931
933
934/* -------------------------------------------------------------------- */
937
939{
940 const Scene *scene = CTX_data_scene(C);
942 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
943
944 if (object->totcol == 0) {
945 return OPERATOR_CANCELLED;
946 }
947
948 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
949 for (const MutableDrawingInfo &info : drawings) {
950 IndexMaskMemory memory;
952 *object, info.drawing, info.layer_index, memory);
953 if (strokes.is_empty()) {
954 continue;
955 }
956 bke::CurvesGeometry &curves = info.drawing.strokes_for_write();
957
958 const VArray<int> materials = *curves.attributes().lookup_or_default<int>(
959 "material_index", bke::AttrDomain::Curve, 0);
960 object->actcol = materials[strokes.first()] + 1;
961 break;
962 };
963
964 WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_EDITED, &grease_pencil);
965
966 return OPERATOR_FINISHED;
967}
968
970{
971 ot->name = "Set Active Material";
972 ot->idname = "GREASE_PENCIL_OT_set_active_material";
973 ot->description = "Set the selected stroke material as the active material";
974
977
979}
980
981
982/* -------------------------------------------------------------------- */
985
987{
988 const Scene *scene = CTX_data_scene(C);
990 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
991
992 /* Radius is half of the thickness. */
993 const float radius = RNA_float_get(op->ptr, "thickness") * 0.5f;
994
995 bool changed = false;
996 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
997 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
998 IndexMaskMemory memory;
1000 *object, info.drawing, info.layer_index, memory);
1001 if (strokes.is_empty()) {
1002 return;
1003 }
1005
1006 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
1008 bke::curves::fill_points<float>(points_by_curve, strokes, radius, radii);
1009 changed = true;
1010 });
1011
1012 if (changed) {
1013 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1014 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1015 }
1016
1017 return OPERATOR_FINISHED;
1018}
1019
1021{
1022 ot->name = "Set Uniform Thickness";
1023 ot->idname = "GREASE_PENCIL_OT_set_uniform_thickness";
1024 ot->description = "Set all stroke points to same thickness";
1025
1028
1029 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1030
1031 ot->prop = RNA_def_float(
1032 ot->srna, "thickness", 0.1f, 0.0f, 1000.0f, "Thickness", "Thickness", 0.0f, 1000.0f);
1033}
1034
1036/* -------------------------------------------------------------------- */
1039
1041{
1042 using namespace blender::bke;
1043
1044 const Scene *scene = CTX_data_scene(C);
1045 Object *object = CTX_data_active_object(C);
1046 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1047
1048 const float opacity_stroke = RNA_float_get(op->ptr, "opacity_stroke");
1049 const float opacity_fill = RNA_float_get(op->ptr, "opacity_fill");
1050
1051 bool changed = false;
1052 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1053 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1054 IndexMaskMemory memory;
1056 *object, info.drawing, info.layer_index, memory);
1057 if (strokes.is_empty()) {
1058 return;
1059 }
1061 MutableAttributeAccessor attributes = curves.attributes_for_write();
1062 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
1063
1065 bke::curves::fill_points<float>(points_by_curve, strokes, opacity_stroke, opacities);
1066
1067 if (SpanAttributeWriter<float> fill_opacities = attributes.lookup_or_add_for_write_span<float>(
1068 "fill_opacity", AttrDomain::Curve))
1069 {
1070 strokes.foreach_index(GrainSize(2048), [&](const int64_t curve) {
1071 fill_opacities.span[curve] = opacity_fill;
1072 });
1073 }
1074
1075 changed = true;
1076 });
1077
1078 if (changed) {
1079 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1080 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1081 }
1082
1083 return OPERATOR_FINISHED;
1084}
1085
1087{
1088 ot->name = "Set Uniform Opacity";
1089 ot->idname = "GREASE_PENCIL_OT_set_uniform_opacity";
1090 ot->description = "Set all stroke points to same opacity";
1091
1094
1095 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1096
1097 /* Differentiate default opacities for stroke & fills so shapes with same stroke+fill colors will
1098 * be more readable. */
1099 RNA_def_float(ot->srna, "opacity_stroke", 1.0f, 0.0f, 1.0f, "Stroke Opacity", "", 0.0f, 1.0f);
1100 RNA_def_float(ot->srna, "opacity_fill", 0.5f, 0.0f, 1.0f, "Fill Opacity", "", 0.0f, 1.0f);
1101}
1102
1104
1105/* -------------------------------------------------------------------- */
1108
1110 wmOperator * /*op*/)
1111{
1112 const Scene *scene = CTX_data_scene(C);
1113 Object *object = CTX_data_active_object(C);
1114 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1115
1116 bool changed = false;
1117 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1118 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1119 IndexMaskMemory memory;
1121 *object, info.drawing, info.layer_index, memory);
1122 if (strokes.is_empty()) {
1123 return;
1124 }
1126
1127 /* Switch stroke direction. */
1128 curves.reverse_curves(strokes);
1129
1130 changed = true;
1131 });
1132
1133 if (changed) {
1134 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1135 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1136 }
1137
1138 return OPERATOR_FINISHED;
1139}
1140
1142{
1143 /* identifiers */
1144 ot->name = "Switch Direction";
1145 ot->idname = "GREASE_PENCIL_OT_stroke_switch_direction";
1146 ot->description = "Change direction of the points of the selected strokes";
1147
1148 /* Callbacks. */
1151
1152 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1153}
1154
1156/* -------------------------------------------------------------------- */
1160 const IndexMask &mask)
1161{
1162
1163 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
1164 const VArray<bool> src_cyclic = curves.cyclic();
1165
1166 /* Early-return if no cyclic curves. */
1168 return curves;
1169 }
1170
1171 Array<bool> start_set_points(curves.points_num());
1172 mask.to_bools(start_set_points.as_mutable_span());
1173
1174 Array<int> dst_to_src_point(curves.points_num());
1175
1176 threading::parallel_for(curves.curves_range(), 1024, [&](const IndexRange range) {
1177 for (const int curve_i : range) {
1178 const IndexRange points = points_by_curve[curve_i];
1179 const Span<bool> curve_i_selected_points = start_set_points.as_span().slice(points);
1180 const int first_selected = curve_i_selected_points.first_index_try(true);
1181
1182 MutableSpan<int> dst_to_src_slice = dst_to_src_point.as_mutable_span().slice(points);
1183
1184 array_utils::fill_index_range<int>(dst_to_src_slice, points.start());
1185
1186 if (first_selected == -1 || src_cyclic[curve_i] == false) {
1187 continue;
1188 }
1189
1190 std::rotate(dst_to_src_slice.begin(),
1191 dst_to_src_slice.begin() + first_selected,
1192 dst_to_src_slice.end());
1193 }
1194 });
1195
1196 /* New CurvesGeometry to copy to. */
1197 bke::CurvesGeometry dst_curves(curves.points_num(), curves.curves_num());
1198 BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &curves.vertex_group_names);
1199
1200 /* Copy offsets. */
1201 array_utils::copy(curves.offsets(), dst_curves.offsets_for_write());
1202
1203 /* Attribute accessors for copying. */
1204 bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
1205 const bke::AttributeAccessor src_attributes = curves.attributes();
1206
1207 /* Copy curve attrs. */
1209 src_attributes, bke::AttrDomain::Curve, bke::AttrDomain::Curve, {}, dst_attributes);
1210 array_utils::copy(src_cyclic, dst_curves.cyclic_for_write());
1211
1212 /* Copy point attrs */
1213 gather_attributes(src_attributes,
1216 {},
1217 dst_to_src_point,
1218 dst_attributes);
1219
1220 dst_curves.update_curve_types();
1221 /* TODO: change to copying knots by point. */
1222 if (curves.nurbs_has_custom_knots()) {
1224 dst_curves.curves_range(), NURBS_KNOT_MODE_NORMAL, NURBS_KNOT_MODE_NORMAL, dst_curves);
1225 }
1226 return dst_curves;
1227}
1228
1230{
1231 using namespace bke::greasepencil;
1232 const Scene *scene = CTX_data_scene(C);
1233 Object *object = CTX_data_active_object(C);
1234 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1235
1236 std::atomic<bool> changed = false;
1237 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1238 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1239 IndexMaskMemory memory;
1241 *object, info.drawing, info.layer_index, memory);
1242 if (selection.is_empty()) {
1243 return;
1244 }
1245
1246 info.drawing.strokes_for_write() = set_start_point(info.drawing.strokes(), selection);
1247
1249 changed = true;
1250 });
1251
1252 if (changed) {
1253 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1254 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1255 }
1256 return OPERATOR_FINISHED;
1257}
1259{
1260 /* Identifiers */
1261 ot->name = "Set Start Point";
1262 ot->idname = "GREASE_PENCIL_OT_set_start_point";
1263 ot->description = "Select which point is the beginning of the curve";
1264
1265 /* Callbacks */
1268
1269 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1270}
1271
1272
1273/* -------------------------------------------------------------------- */
1276
1277enum class CapsMode : int8_t {
1279 FLAT = 0,
1283 END = 2,
1286};
1287
1288static void toggle_caps(MutableSpan<int8_t> caps, const IndexMask &strokes)
1289{
1290 strokes.foreach_index([&](const int stroke_i) {
1291 if (caps[stroke_i] == GP_STROKE_CAP_FLAT) {
1292 caps[stroke_i] = GP_STROKE_CAP_ROUND;
1293 }
1294 else {
1295 caps[stroke_i] = GP_STROKE_CAP_FLAT;
1296 }
1297 });
1298}
1299
1301{
1302 const Scene *scene = CTX_data_scene(C);
1303 Object *object = CTX_data_active_object(C);
1304 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1305
1306 const CapsMode mode = CapsMode(RNA_enum_get(op->ptr, "type"));
1307
1308 bool changed = false;
1309 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1310 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1312 IndexMaskMemory memory;
1314 *object, info.drawing, info.layer_index, memory);
1315 if (strokes.is_empty()) {
1316 return;
1317 }
1318
1319 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
1320
1321 if (ELEM(mode, CapsMode::ROUND, CapsMode::FLAT)) {
1322 const int8_t flag_set = (mode == CapsMode::ROUND) ? int8_t(GP_STROKE_CAP_TYPE_ROUND) :
1324 if (bke::SpanAttributeWriter<int8_t> start_caps =
1325 attributes.lookup_or_add_for_write_span<int8_t>("start_cap", bke::AttrDomain::Curve))
1326 {
1327 index_mask::masked_fill(start_caps.span, flag_set, strokes);
1328 start_caps.finish();
1329 }
1331 attributes.lookup_or_add_for_write_span<int8_t>("end_cap", bke::AttrDomain::Curve))
1332 {
1333 index_mask::masked_fill(end_caps.span, flag_set, strokes);
1334 end_caps.finish();
1335 }
1336 }
1337 else {
1338 switch (mode) {
1339 case CapsMode::START: {
1341 attributes.lookup_or_add_for_write_span<int8_t>("start_cap",
1343 {
1344 toggle_caps(caps.span, strokes);
1345 caps.finish();
1346 }
1347 break;
1348 }
1349 case CapsMode::END: {
1351 attributes.lookup_or_add_for_write_span<int8_t>("end_cap",
1353 {
1354 toggle_caps(caps.span, strokes);
1355 caps.finish();
1356 }
1357 break;
1358 }
1359 case CapsMode::ROUND:
1360 case CapsMode::FLAT:
1361 break;
1362 }
1363 }
1364
1365 changed = true;
1366 });
1367
1368 if (changed) {
1369 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1370 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1371 }
1372
1373 return OPERATOR_FINISHED;
1374}
1375
1377{
1378 static const EnumPropertyItem prop_caps_types[] = {
1379 {int(CapsMode::ROUND), "ROUND", 0, "Rounded", "Set as default rounded"},
1380 {int(CapsMode::FLAT), "FLAT", 0, "Flat", ""},
1382 {int(CapsMode::START), "START", 0, "Toggle Start", ""},
1383 {int(CapsMode::END), "END", 0, "Toggle End", ""},
1384 {0, nullptr, 0, nullptr, nullptr},
1385 };
1386
1387 ot->name = "Set Curve Caps";
1388 ot->idname = "GREASE_PENCIL_OT_caps_set";
1389 ot->description = "Change curve caps mode (rounded or flat)";
1390
1391 ot->invoke = WM_menu_invoke;
1394
1395 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1396
1397 ot->prop = RNA_def_enum(ot->srna, "type", prop_caps_types, int(CapsMode::ROUND), "Type", "");
1398}
1399
1401
1402/* -------------------------------------------------------------------- */
1405
1406/* Retry enum items with object materials. */
1408 PointerRNA * /*ptr*/,
1409 PropertyRNA * /*prop*/,
1410 bool *r_free)
1411{
1413 EnumPropertyItem *item = nullptr, item_tmp = {0};
1414 int totitem = 0;
1415
1416 if (ob == nullptr) {
1418 }
1419
1420 /* Existing materials */
1421 for (const int i : IndexRange(ob->totcol)) {
1422 if (Material *ma = BKE_object_material_get(ob, i + 1)) {
1423 item_tmp.identifier = ma->id.name + 2;
1424 item_tmp.name = ma->id.name + 2;
1425 item_tmp.value = i + 1;
1426 item_tmp.icon = ma->preview ? ma->preview->runtime->icon_id : ICON_NONE;
1427
1428 RNA_enum_item_add(&item, &totitem, &item_tmp);
1429 }
1430 }
1431 RNA_enum_item_end(&item, &totitem);
1432 *r_free = true;
1433
1434 return item;
1435}
1436
1438{
1439 Object *object = CTX_data_active_object(C);
1440 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1441 const int slot = RNA_enum_get(op->ptr, "slot");
1442
1443 /* Try to get material slot. */
1444 if ((slot < 1) || (slot > object->totcol)) {
1445 return OPERATOR_CANCELLED;
1446 }
1447
1448 /* Set active material. */
1449 object->actcol = slot;
1450
1451 WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_EDITED, &grease_pencil);
1452
1453 return OPERATOR_FINISHED;
1454}
1455
1457{
1458 ot->name = "Set Active Material";
1459 ot->idname = "GREASE_PENCIL_OT_set_material";
1460 ot->description = "Set active material";
1461
1464
1465 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1466
1467 /* Material to use (dynamic enum) */
1468 ot->prop = RNA_def_enum(ot->srna, "slot", rna_enum_dummy_DEFAULT_items, 0, "Material Slot", "");
1470}
1471
1472
1473/* -------------------------------------------------------------------- */
1476
1478{
1479 const Scene *scene = CTX_data_scene(C);
1480 Object *object = CTX_data_active_object(C);
1481 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1482
1484 scene->toolsettings);
1485
1486 std::atomic<bool> changed = false;
1487 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1488 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1489 IndexMaskMemory memory;
1491 *object, info.drawing, info.layer_index, selection_domain, memory);
1492 if (elements.is_empty()) {
1493 return;
1494 }
1495
1497 if (selection_domain == bke::AttrDomain::Curve) {
1499 }
1500 else if (selection_domain == bke::AttrDomain::Point) {
1502 }
1504 changed.store(true, std::memory_order_relaxed);
1505 });
1506
1507 if (changed) {
1508 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1509 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1510 }
1511 return OPERATOR_FINISHED;
1512}
1513
1515{
1516 ot->name = "Duplicate";
1517 ot->idname = "GREASE_PENCIL_OT_duplicate";
1518 ot->description = "Duplicate the selected points";
1519
1522
1523 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1524}
1525
1527{
1528 Object *object = CTX_data_active_object(C);
1529 Scene &scene = *CTX_data_scene(C);
1530 const int limit = RNA_int_get(op->ptr, "limit");
1531
1532 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1533 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
1534
1535 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1537 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
1538
1539 IndexMaskMemory memory;
1541 *object, info.drawing, info.layer_index, memory);
1542
1543 const IndexMask curves_to_delete = IndexMask::from_predicate(
1544 editable_strokes, GrainSize(4096), memory, [&](const int i) {
1545 return points_by_curve[i].size() <= limit;
1546 });
1547
1548 curves.remove_curves(curves_to_delete, {});
1549 });
1550
1551 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1552 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1553
1554 return OPERATOR_FINISHED;
1555}
1556
1558 wmOperator *op,
1559 const wmEvent *event)
1560{
1562 C, op, event, IFACE_("Remove Loose Points"), IFACE_("Delete"));
1563}
1564
1566{
1567 ot->name = "Clean Loose Points";
1568 ot->idname = "GREASE_PENCIL_OT_clean_loose";
1569 ot->description = "Remove loose points";
1570
1574
1575 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1576
1577 RNA_def_int(ot->srna,
1578 "limit",
1579 1,
1580 1,
1581 INT_MAX,
1582 "Limit",
1583 "Number of points to consider stroke as loose",
1584 1,
1585 INT_MAX);
1586}
1587
1589
1590/* -------------------------------------------------------------------- */
1593
1595{
1596 const int cuts = RNA_int_get(op->ptr, "number_cuts");
1597 const bool only_selected = RNA_boolean_get(op->ptr, "only_selected");
1598
1599 std::atomic<bool> changed = false;
1600
1601 const Scene *scene = CTX_data_scene(C);
1602 Object *object = CTX_data_active_object(C);
1603 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1605 scene->toolsettings);
1606
1607 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1608
1609 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1610 IndexMaskMemory memory;
1612 *object, info.drawing, info.layer_index, memory);
1613 if (strokes.is_empty()) {
1614 return;
1615 }
1617
1618 VArray<int> vcuts = {};
1619
1620 if (selection_domain == bke::AttrDomain::Curve || !only_selected) {
1621 /* Subdivide entire selected curve, every stroke subdivides to the same cut. */
1622 vcuts = VArray<int>::ForSingle(cuts, curves.points_num());
1623 }
1624 else if (selection_domain == bke::AttrDomain::Point) {
1625 /* Subdivide between selected points. Only cut between selected points.
1626 * Make the cut array the same length as point count for specifying
1627 * cut/uncut for each segment. */
1628 const VArray<bool> selection = *curves.attributes().lookup_or_default<bool>(
1629 ".selection", bke::AttrDomain::Point, true);
1630
1631 const OffsetIndices points_by_curve = curves.points_by_curve();
1632 const VArray<bool> cyclic = curves.cyclic();
1633
1634 Array<int> use_cuts(curves.points_num(), 0);
1635
1636 /* The cut is after each point, so the last point selected wouldn't need to be registered. */
1637 for (const int curve : curves.curves_range()) {
1638 /* No need to loop to the last point since the cut is registered on the point before the
1639 * segment. */
1640 for (const int point : points_by_curve[curve].drop_back(1)) {
1641 /* The point itself should be selected. */
1642 if (!selection[point]) {
1643 continue;
1644 }
1645 /* If the next point in the curve is selected, then cut this segment. */
1646 if (selection[point + 1]) {
1647 use_cuts[point] = cuts;
1648 }
1649 }
1650 /* Check for cyclic and selection. */
1651 if (cyclic[curve]) {
1652 const int first_point = points_by_curve[curve].first();
1653 const int last_point = points_by_curve[curve].last();
1654 if (selection[first_point] && selection[last_point]) {
1655 use_cuts[last_point] = cuts;
1656 }
1657 }
1658 }
1659 vcuts = VArray<int>::ForContainer(std::move(use_cuts));
1660 }
1661
1662 curves = geometry::subdivide_curves(curves, strokes, vcuts);
1664 changed.store(true, std::memory_order_relaxed);
1665 });
1666
1667 if (changed) {
1668 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1670 }
1671
1672 return OPERATOR_FINISHED;
1673}
1674
1676{
1677 PropertyRNA *prop;
1678
1679 ot->name = "Subdivide Stroke";
1680 ot->idname = "GREASE_PENCIL_OT_stroke_subdivide";
1681 ot->description =
1682 "Subdivide between continuous selected points of the stroke adding a point half way "
1683 "between "
1684 "them";
1685
1688
1689 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1690
1691 prop = RNA_def_int(ot->srna, "number_cuts", 1, 1, 32, "Number of Cuts", "", 1, 5);
1692 /* Avoid re-using last var because it can cause _very_ high value and annoy users. */
1694
1695 RNA_def_boolean(ot->srna,
1696 "only_selected",
1697 true,
1698 "Selected Points",
1699 "Smooth only selected points in the stroke");
1700}
1701
1703
1704/* -------------------------------------------------------------------- */
1707
1708enum class ReorderDirection : int8_t {
1710 TOP = 0,
1712 UP = 1,
1714 DOWN = 2,
1717};
1718
1720 const IndexMask &selected,
1721 const ReorderDirection direction)
1722{
1723 Array<int> indices(universe.size());
1724
1726 /* Initialize the indices. */
1728 }
1729
1731 /*
1732 * Take the selected indices and move them to the start for `Bottom` or the end for `Top`
1733 * And fill the reset with the unselected indices.
1734 *
1735 * Here's a diagram:
1736 *
1737 * Input
1738 * 0 1 2 3 4 5 6 7 8 9
1739 * ^ ^ ^
1740 *
1741 * Top
1742 * |-----A-----| |-B-|
1743 * 0 1 3 6 7 8 9 2 4 5
1744 * ^ ^ ^
1745 *
1746 * Bottom
1747 * |-A-| |-----B-----|
1748 * 2 4 5 0 1 3 6 7 8 9
1749 * ^ ^ ^
1750 */
1751
1752 IndexMaskMemory memory;
1753 const IndexMask unselected = selected.complement(universe, memory);
1754
1755 const IndexMask &A = (direction == ReorderDirection::BOTTOM) ? selected : unselected;
1756 const IndexMask &B = (direction == ReorderDirection::BOTTOM) ? unselected : selected;
1757
1758 A.to_indices(indices.as_mutable_span().take_front(A.size()));
1759 B.to_indices(indices.as_mutable_span().take_back(B.size()));
1760 }
1761 else if (direction == ReorderDirection::DOWN) {
1762 selected.foreach_index_optimized<int>([&](const int curve_i, const int pos) {
1763 /* Check if the curve index is touching the beginning without any gaps. */
1764 if (curve_i != pos) {
1765 /* Move a index down by flipping it with the one below it. */
1766 std::swap(indices[curve_i], indices[curve_i - 1]);
1767 }
1768 });
1769 }
1770 else if (direction == ReorderDirection::UP) {
1771 Array<int> selected_indices(selected.size());
1772 selected.to_indices(selected_indices.as_mutable_span());
1773
1774 /* Because each index is moving up we need to loop through the indices backwards,
1775 * starting at the largest. */
1776 for (const int i : selected_indices.index_range()) {
1777 const int pos = selected_indices.index_range().last(i);
1778 const int curve_i = selected_indices[pos];
1779
1780 /* Check if the curve index is touching the end without any gaps. */
1781 if (curve_i != universe.last(i)) {
1782 /* Move a index up by flipping it with the one above it. */
1783 std::swap(indices[curve_i], indices[curve_i + 1]);
1784 }
1785 }
1786 }
1787
1788 return indices;
1789}
1790
1792{
1793 const Scene *scene = CTX_data_scene(C);
1794 Object *object = CTX_data_active_object(C);
1795 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1796
1797 const ReorderDirection direction = ReorderDirection(RNA_enum_get(op->ptr, "direction"));
1798
1799 std::atomic<bool> changed = false;
1800 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1801 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1802 IndexMaskMemory memory;
1804 *object, info.drawing, info.layer_index, memory);
1805 if (strokes.is_empty()) {
1806 return;
1807 }
1809
1810 /* Return if everything is selected. */
1811 if (strokes.size() == curves.curves_num()) {
1812 return;
1813 }
1814
1815 const Array<int> indices = get_reordered_indices(curves.curves_range(), strokes, direction);
1816
1819 changed.store(true, std::memory_order_relaxed);
1820 });
1821
1822 if (changed) {
1823 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1824 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1825 }
1826
1827 return OPERATOR_FINISHED;
1828}
1829
1831{
1832 static const EnumPropertyItem prop_reorder_direction[] = {
1833 {int(ReorderDirection::TOP), "TOP", 0, "Bring to Front", ""},
1834 {int(ReorderDirection::UP), "UP", 0, "Bring Forward", ""},
1836 {int(ReorderDirection::DOWN), "DOWN", 0, "Send Backward", ""},
1837 {int(ReorderDirection::BOTTOM), "BOTTOM", 0, "Send to Back", ""},
1838 {0, nullptr, 0, nullptr, nullptr},
1839 };
1840
1841 ot->name = "Reorder";
1842 ot->idname = "GREASE_PENCIL_OT_reorder";
1843 ot->description = "Change the display order of the selected strokes";
1844
1847
1848 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1849
1850 ot->prop = RNA_def_enum(
1851 ot->srna, "direction", prop_reorder_direction, int(ReorderDirection::TOP), "Direction", "");
1852}
1853
1855
1856/* -------------------------------------------------------------------- */
1859
1861{
1862 using namespace bke::greasepencil;
1863 const Scene *scene = CTX_data_scene(C);
1864 bool changed = false;
1865
1866 Object *object = CTX_data_active_object(C);
1867 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1868
1869 int target_layer_name_length;
1870 char *target_layer_name = RNA_string_get_alloc(
1871 op->ptr, "target_layer_name", nullptr, 0, &target_layer_name_length);
1872 BLI_SCOPED_DEFER([&] { MEM_SAFE_FREE(target_layer_name); });
1873 const bool add_new_layer = RNA_boolean_get(op->ptr, "add_new_layer");
1874 TreeNode *target_node = nullptr;
1875
1876 if (add_new_layer) {
1877 target_node = &grease_pencil.add_layer(target_layer_name).as_node();
1878 }
1879 else {
1880 target_node = grease_pencil.find_node_by_name(target_layer_name);
1881 }
1882
1883 if (target_node == nullptr || !target_node->is_layer()) {
1884 BKE_reportf(op->reports, RPT_ERROR, "There is no layer '%s'", target_layer_name);
1885 return OPERATOR_CANCELLED;
1886 }
1887
1888 Layer &layer_dst = target_node->as_layer();
1889 if (layer_dst.is_locked()) {
1890 BKE_reportf(op->reports, RPT_ERROR, "'%s' Layer is locked", target_layer_name);
1891 return OPERATOR_CANCELLED;
1892 }
1893
1894 /* Iterate through all the drawings at current scene frame. */
1895 const Vector<MutableDrawingInfo> drawings_src = retrieve_editable_drawings(*scene,
1896 grease_pencil);
1897 for (const MutableDrawingInfo &info : drawings_src) {
1898 bke::CurvesGeometry &curves_src = info.drawing.strokes_for_write();
1899 IndexMaskMemory memory;
1900 const IndexMask selected_strokes = ed::curves::retrieve_selected_curves(curves_src, memory);
1901 if (selected_strokes.is_empty()) {
1902 continue;
1903 }
1904
1905 if (!layer_dst.frames().lookup_ptr(info.frame_number)) {
1906 /* Move geometry to a new drawing in target layer. */
1907 Drawing &drawing_dst = *grease_pencil.insert_frame(layer_dst, info.frame_number);
1909 curves_src, selected_strokes, {});
1910
1911 curves_src.remove_curves(selected_strokes, {});
1912 drawing_dst.tag_topology_changed();
1913 }
1914 else if (Drawing *drawing_dst = grease_pencil.get_drawing_at(layer_dst, info.frame_number)) {
1915 /* Append geometry to drawing in target layer. */
1917 curves_src, selected_strokes, {});
1918 Curves *selected_curves = bke::curves_new_nomain(std::move(selected_elems));
1919 Curves *layer_curves = bke::curves_new_nomain(std::move(drawing_dst->strokes_for_write()));
1920 std::array<bke::GeometrySet, 2> geometry_sets{
1921 bke::GeometrySet::from_curves(layer_curves),
1922 bke::GeometrySet::from_curves(selected_curves)};
1923 bke::GeometrySet joined = geometry::join_geometries(geometry_sets, {});
1924 drawing_dst->strokes_for_write() = std::move(joined.get_curves_for_write()->geometry.wrap());
1925
1926 curves_src.remove_curves(selected_strokes, {});
1927
1928 drawing_dst->tag_topology_changed();
1929 }
1930
1931 info.drawing.tag_topology_changed();
1932 changed = true;
1933 }
1934
1935 if (changed) {
1936 /* updates */
1939 }
1940
1941 return OPERATOR_FINISHED;
1942}
1943
1945 wmOperator *op,
1946 const wmEvent *event)
1947{
1948 const bool add_new_layer = RNA_boolean_get(op->ptr, "add_new_layer");
1949 if (add_new_layer) {
1950 Object *object = CTX_data_active_object(C);
1951 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1952
1953 const std::string unique_name = grease_pencil.unique_layer_name("Layer");
1954 RNA_string_set(op->ptr, "target_layer_name", unique_name.c_str());
1955
1957 C, op, event, IFACE_("Move to New Layer"), IFACE_("Create"));
1958 }
1959
1960 /* Show the move menu if this operator is invoked from operator search without any property
1961 * pre-set. */
1962 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "target_layer_name");
1963 if (!RNA_property_is_set(op->ptr, prop)) {
1964 WM_menu_name_call(C, "GREASE_PENCIL_MT_move_to_layer", 0);
1965 return OPERATOR_FINISHED;
1966 }
1967
1969}
1970
1972{
1973 PropertyRNA *prop;
1974
1975 ot->name = "Move to Layer";
1976 ot->idname = "GREASE_PENCIL_OT_move_to_layer";
1977 ot->description = "Move selected strokes to another layer";
1978
1982
1983 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1984
1985 prop = RNA_def_string(
1986 ot->srna, "target_layer_name", nullptr, INT16_MAX, "Name", "Target Grease Pencil Layer");
1988 prop = RNA_def_boolean(
1989 ot->srna, "add_new_layer", false, "New Layer", "Move selection to a new layer");
1991}
1992
1994
1995/* -------------------------------------------------------------------- */
1998
1999enum class SeparateMode : int8_t {
2000 /* Selected Points/Strokes. */
2002 /* By Material. */
2003 MATERIAL = 1,
2004 /* By each Layer. */
2006};
2007
2009 {int(SeparateMode::SELECTED), "SELECTED", 0, "Selection", "Separate selected geometry"},
2010 {int(SeparateMode::MATERIAL), "MATERIAL", 0, "By Material", "Separate by material"},
2011 {int(SeparateMode::LAYER), "LAYER", 0, "By Layer", "Separate by layer"},
2012 {0, nullptr, 0, nullptr, nullptr},
2013};
2014
2015static void remove_unused_materials(Main *bmain, Object *object)
2016{
2017 int actcol = object->actcol;
2018 for (int slot = 1; slot <= object->totcol; slot++) {
2019 while (slot <= object->totcol && !BKE_object_material_slot_used(object, slot)) {
2020 object->actcol = slot;
2021 if (!BKE_object_material_slot_remove(bmain, object)) {
2022 break;
2023 }
2024
2025 if (actcol >= slot) {
2026 actcol--;
2027 }
2028 }
2029 }
2030 object->actcol = actcol;
2031}
2032
2034 Scene *scene,
2035 ViewLayer *view_layer,
2036 Base *base_prev,
2037 const GreasePencil &grease_pencil_src)
2038{
2039 const eDupli_ID_Flags dupflag = eDupli_ID_Flags(U.dupflag & USER_DUP_GPENCIL);
2040 Base *base_new = object::add_duplicate(bmain, scene, view_layer, base_prev, dupflag);
2041 Object *object_dst = base_new->object;
2042 object_dst->mode = OB_MODE_OBJECT;
2043 GreasePencil *grease_pencil_dst = BKE_grease_pencil_add(bmain, grease_pencil_src.id.name + 2);
2044 BKE_grease_pencil_copy_parameters(grease_pencil_src, *grease_pencil_dst);
2045 object_dst->data = grease_pencil_dst;
2046
2047 return object_dst;
2048}
2049
2051 const int layer_index,
2052 const GreasePencil &grease_pencil_src,
2053 GreasePencil &grease_pencil_dst,
2054 Vector<int> &src_to_dst_layer_indices)
2055{
2056 using namespace bke::greasepencil;
2057
2058 /* This assumes that the index is valid. Will cause an assert if it is not. */
2059 const Layer &layer_src = grease_pencil_src.layer(layer_index);
2060 if (TreeNode *node = grease_pencil_dst.find_node_by_name(layer_src.name())) {
2061 return node->as_layer();
2062 }
2063
2064 /* If the layer can't be found in `grease_pencil_dst` by name add a new layer. */
2065 Layer &new_layer = grease_pencil_dst.add_layer(layer_src.name());
2066 BKE_grease_pencil_copy_layer_parameters(layer_src, new_layer);
2067 src_to_dst_layer_indices.append(layer_index);
2068
2069 return new_layer;
2070}
2071
2073 Main &bmain,
2074 Scene &scene,
2075 ViewLayer &view_layer,
2076 Base &base_prev,
2077 Object &object_src)
2078{
2079 using namespace bke::greasepencil;
2080 bool changed = false;
2081
2082 GreasePencil &grease_pencil_src = *static_cast<GreasePencil *>(object_src.data);
2084 &bmain, &scene, &view_layer, &base_prev, grease_pencil_src);
2085 GreasePencil &grease_pencil_dst = *static_cast<GreasePencil *>(object_dst->data);
2086
2087 /* Iterate through all the drawings at current scene frame. */
2088 const Vector<MutableDrawingInfo> drawings_src = retrieve_editable_drawings(scene,
2089 grease_pencil_src);
2090 Vector<int> src_to_dst_layer_indices;
2091 for (const MutableDrawingInfo &info : drawings_src) {
2092 bke::CurvesGeometry &curves_src = info.drawing.strokes_for_write();
2093 IndexMaskMemory memory;
2094 const IndexMask selected_points = ed::curves::retrieve_selected_points(curves_src, memory);
2095 if (selected_points.is_empty()) {
2096 continue;
2097 }
2098
2099 /* Insert Keyframe at current frame/layer. */
2101 info.layer_index, grease_pencil_src, grease_pencil_dst, src_to_dst_layer_indices);
2102
2103 Drawing *drawing_dst = grease_pencil_dst.insert_frame(layer_dst, info.frame_number);
2104 BLI_assert(drawing_dst != nullptr);
2105
2106 /* Copy strokes to new CurvesGeometry. */
2108 curves_src, selected_points, {});
2109 curves_src = geometry::remove_points_and_split(curves_src, selected_points);
2110
2111 info.drawing.tag_topology_changed();
2112 drawing_dst->tag_topology_changed();
2113
2114 changed = true;
2115 }
2116
2117 if (changed) {
2118 /* Transfer layer attributes. */
2119 bke::gather_attributes(grease_pencil_src.attributes(),
2122 {},
2123 src_to_dst_layer_indices.as_span(),
2124 grease_pencil_dst.attributes_for_write());
2125
2126 /* Set the active layer in the target object. */
2127 if (grease_pencil_src.has_active_layer()) {
2128 const Layer &active_layer_src = *grease_pencil_src.get_active_layer();
2129 TreeNode *active_layer_dst = grease_pencil_dst.find_node_by_name(active_layer_src.name());
2130 if (active_layer_dst && active_layer_dst->is_layer()) {
2131 grease_pencil_dst.set_active_layer(&active_layer_dst->as_layer());
2132 }
2133 }
2134
2135 /* Add object materials to target object. */
2137 object_dst,
2138 BKE_object_material_array_p(&object_src),
2139 *BKE_object_material_len_p(&object_src),
2140 false);
2141
2142 remove_unused_materials(&bmain, object_dst);
2143 DEG_id_tag_update(&grease_pencil_dst.id, ID_RECALC_GEOMETRY);
2144 WM_event_add_notifier(&C, NC_OBJECT | ND_DRAW, &grease_pencil_dst);
2145 }
2146 return changed;
2147}
2148
2150 Main &bmain,
2151 Scene &scene,
2152 ViewLayer &view_layer,
2153 Base &base_prev,
2154 Object &object_src)
2155{
2156 using namespace bke::greasepencil;
2157 bool changed = false;
2158
2159 GreasePencil &grease_pencil_src = *static_cast<GreasePencil *>(object_src.data);
2160
2161 /* Create a new object for each layer. */
2162 for (const int layer_i : grease_pencil_src.layers().index_range()) {
2163 Layer &layer_src = grease_pencil_src.layer(layer_i);
2164 if (layer_src.is_locked()) {
2165 continue;
2166 }
2167
2169 &bmain, &scene, &view_layer, &base_prev, grease_pencil_src);
2170 GreasePencil &grease_pencil_dst = *static_cast<GreasePencil *>(object_dst->data);
2171 Vector<int> src_to_dst_layer_indices;
2173 layer_i, grease_pencil_src, grease_pencil_dst, src_to_dst_layer_indices);
2174
2175 /* Iterate through all the drawings at current frame. */
2177 scene, grease_pencil_src, layer_src);
2178 for (const MutableDrawingInfo &info : drawings_src) {
2179 bke::CurvesGeometry &curves_src = info.drawing.strokes_for_write();
2180 IndexMaskMemory memory;
2181 const IndexMask strokes = retrieve_editable_strokes(
2182 object_src, info.drawing, info.layer_index, memory);
2183 if (strokes.is_empty()) {
2184 continue;
2185 }
2186
2187 /* Add object materials. */
2189 object_dst,
2190 BKE_object_material_array_p(&object_src),
2191 *BKE_object_material_len_p(&object_src),
2192 false);
2193
2194 /* Insert Keyframe at current frame/layer. */
2195 Drawing *drawing_dst = grease_pencil_dst.insert_frame(layer_dst, info.frame_number);
2196 /* TODO: Can we assume the insert never fails? */
2197 BLI_assert(drawing_dst != nullptr);
2198
2199 /* Copy strokes to new CurvesGeometry. */
2201 info.drawing.strokes(), strokes, {});
2202 curves_src.remove_curves(strokes, {});
2203
2204 info.drawing.tag_topology_changed();
2205 drawing_dst->tag_topology_changed();
2206
2207 changed = true;
2208 }
2209
2210 /* Transfer layer attributes. */
2211 bke::gather_attributes(grease_pencil_src.attributes(),
2214 {},
2215 src_to_dst_layer_indices.as_span(),
2216 grease_pencil_dst.attributes_for_write());
2217
2218 remove_unused_materials(&bmain, object_dst);
2219
2220 DEG_id_tag_update(&grease_pencil_dst.id, ID_RECALC_GEOMETRY);
2221 WM_event_add_notifier(&C, NC_OBJECT | ND_DRAW, &grease_pencil_dst);
2222 }
2223
2224 return changed;
2225}
2226
2228 Main &bmain,
2229 Scene &scene,
2230 ViewLayer &view_layer,
2231 Base &base_prev,
2232 Object &object_src)
2233{
2234 using namespace blender::bke;
2235 using namespace bke::greasepencil;
2236 bool changed = false;
2237
2238 GreasePencil &grease_pencil_src = *static_cast<GreasePencil *>(object_src.data);
2239
2240 /* Create a new object for each material. */
2241 for (const int mat_i : IndexRange(object_src.totcol).drop_front(1)) {
2242 if (!BKE_object_material_slot_used(&object_src, mat_i + 1)) {
2243 continue;
2244 }
2245
2247 &bmain, &scene, &view_layer, &base_prev, grease_pencil_src);
2248 GreasePencil &grease_pencil_dst = *static_cast<GreasePencil *>(object_dst->data);
2249
2250 /* Add object materials. */
2252 object_dst,
2253 BKE_object_material_array_p(&object_src),
2254 *BKE_object_material_len_p(&object_src),
2255 false);
2256
2257 /* Iterate through all the drawings at current scene frame. */
2258 const Vector<MutableDrawingInfo> drawings_src = retrieve_editable_drawings(scene,
2259 grease_pencil_src);
2260 Vector<int> src_to_dst_layer_indices;
2261 for (const MutableDrawingInfo &info : drawings_src) {
2262 bke::CurvesGeometry &curves_src = info.drawing.strokes_for_write();
2263 IndexMaskMemory memory;
2265 object_src, info.drawing, mat_i, memory);
2266 if (strokes.is_empty()) {
2267 continue;
2268 }
2269
2270 /* Insert Keyframe at current frame/layer. */
2272 info.layer_index, grease_pencil_src, grease_pencil_dst, src_to_dst_layer_indices);
2273
2274 Drawing *drawing_dst = grease_pencil_dst.insert_frame(layer_dst, info.frame_number);
2275 /* TODO: Can we assume the insert never fails? */
2276 BLI_assert(drawing_dst != nullptr);
2277
2278 /* Copy strokes to new CurvesGeometry. */
2279 drawing_dst->strokes_for_write() = bke::curves_copy_curve_selection(curves_src, strokes, {});
2280 curves_src.remove_curves(strokes, {});
2281
2282 info.drawing.tag_topology_changed();
2283 drawing_dst->tag_topology_changed();
2284
2285 changed = true;
2286 }
2287
2288 /* Transfer layer attributes. */
2289 bke::gather_attributes(grease_pencil_src.attributes(),
2292 {},
2293 src_to_dst_layer_indices.as_span(),
2294 grease_pencil_dst.attributes_for_write());
2295
2296 remove_unused_materials(&bmain, object_dst);
2297
2298 DEG_id_tag_update(&grease_pencil_dst.id, ID_RECALC_GEOMETRY);
2299 WM_event_add_notifier(&C, NC_OBJECT | ND_DRAW, &grease_pencil_dst);
2300 }
2301
2302 if (changed) {
2303 remove_unused_materials(&bmain, &object_src);
2304 }
2305
2306 return changed;
2307}
2308
2310{
2311 using namespace bke::greasepencil;
2312 Main *bmain = CTX_data_main(C);
2313 Scene *scene = CTX_data_scene(C);
2314 ViewLayer *view_layer = CTX_data_view_layer(C);
2315 Base *base_prev = CTX_data_active_base(C);
2316 Object *object_src = CTX_data_active_object(C);
2317 GreasePencil &grease_pencil_src = *static_cast<GreasePencil *>(object_src->data);
2318
2319 const SeparateMode mode = SeparateMode(RNA_enum_get(op->ptr, "mode"));
2320 bool changed = false;
2321
2322 WM_cursor_wait(true);
2323
2324 switch (mode) {
2326 /* Cancel if nothing selected. */
2328 grease_pencil_src);
2329 const bool has_selection = std::any_of(
2330 drawings.begin(), drawings.end(), [&](const MutableDrawingInfo &info) {
2331 return ed::curves::has_anything_selected(info.drawing.strokes());
2332 });
2333 if (!has_selection) {
2334 BKE_report(op->reports, RPT_ERROR, "Nothing selected");
2335 WM_cursor_wait(false);
2336 return OPERATOR_CANCELLED;
2337 }
2338
2340 *C, *bmain, *scene, *view_layer, *base_prev, *object_src);
2341 break;
2342 }
2344 /* Cancel if the object only has one material. */
2345 if (object_src->totcol == 1) {
2346 BKE_report(op->reports, RPT_ERROR, "The object has only one material");
2347 WM_cursor_wait(false);
2348 return OPERATOR_CANCELLED;
2349 }
2350
2352 *C, *bmain, *scene, *view_layer, *base_prev, *object_src);
2353 break;
2354 }
2355 case SeparateMode::LAYER: {
2356 /* Cancel if the object only has one layer. */
2357 if (grease_pencil_src.layers().size() == 1) {
2358 BKE_report(op->reports, RPT_ERROR, "The object has only one layer");
2359 WM_cursor_wait(false);
2360 return OPERATOR_CANCELLED;
2361 }
2363 *C, *bmain, *scene, *view_layer, *base_prev, *object_src);
2364 break;
2365 }
2366 }
2367
2368 WM_cursor_wait(false);
2369
2370 if (changed) {
2371 DEG_id_tag_update(&grease_pencil_src.id, ID_RECALC_GEOMETRY);
2372 WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_EDITED, &grease_pencil_src);
2373 }
2374
2375 return OPERATOR_FINISHED;
2376}
2377
2379{
2380 ot->name = "Separate";
2381 ot->idname = "GREASE_PENCIL_OT_separate";
2382 ot->description = "Separate the selected geometry into a new Grease Pencil object";
2383
2384 ot->invoke = WM_menu_invoke;
2387
2388 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2389
2390 ot->prop = RNA_def_enum(
2391 ot->srna, "mode", prop_separate_modes, int(SeparateMode::SELECTED), "Mode", "");
2392}
2393
2395
2396/* -------------------------------------------------------------------- */
2399
2400/* Global clipboard for Grease Pencil curves. */
2401static struct Clipboard {
2403 /* Name of the layer. */
2404 std::string name;
2405 /* Curves for this layer. */
2407 };
2409 /* Object transform of stored curves. */
2411 /* We store the material uid's of the copied curves, so we can match those when pasting the
2412 * clipboard into another object. */
2415} *grease_pencil_clipboard = nullptr;
2416
2419
2421{
2422 std::scoped_lock lock(grease_pencil_clipboard_lock);
2423
2424 if (grease_pencil_clipboard == nullptr) {
2425 grease_pencil_clipboard = MEM_new<Clipboard>(__func__);
2426 }
2428}
2429
2431{
2432 std::scoped_lock lock(grease_pencil_clipboard_lock);
2433
2435 MEM_delete(grease_pencil_clipboard);
2436 grease_pencil_clipboard = nullptr;
2437 }
2438}
2439
2441{
2442 using namespace blender::ed::greasepencil;
2443
2444 /* Get a list of all materials in the scene. */
2445 Map<uint, Material *> scene_materials;
2446 LISTBASE_FOREACH (Material *, material, &bmain.materials) {
2447 scene_materials.add(material->id.session_uid, material);
2448 }
2449
2450 const Clipboard &clipboard = ensure_grease_pencil_clipboard();
2451 Array<int> clipboard_material_remap(clipboard.materials_in_source_num, 0);
2452 for (const int i : clipboard.materials.index_range()) {
2453 /* Check if the material name exists in the scene. */
2454 int target_index;
2455 uint material_id = clipboard.materials[i].first;
2456 Material *material = scene_materials.lookup_default(material_id, nullptr);
2457 if (!material) {
2458 /* Material is removed, so create a new material. */
2459 BKE_grease_pencil_object_material_new(&bmain, &object, nullptr, &target_index);
2460 clipboard_material_remap[clipboard.materials[i].second] = target_index;
2461 continue;
2462 }
2463
2464 /* Find or add the material to the target object. */
2465 target_index = BKE_object_material_ensure(&bmain, &object, material);
2466 clipboard_material_remap[clipboard.materials[i].second] = target_index;
2467 }
2468
2469 return clipboard_material_remap;
2470}
2471
2473 const VArray<float4x4> &transforms)
2474{
2475 BLI_assert(geometries.size() == transforms.size());
2476
2477 std::unique_ptr<bke::Instances> instances = std::make_unique<bke::Instances>();
2478 instances->resize(geometries.size());
2479 transforms.materialize(instances->transforms_for_write());
2480 MutableSpan<int> handles = instances->reference_handles_for_write();
2481 for (const int i : geometries.index_range()) {
2482 handles[i] = instances->add_new_reference(bke::InstanceReference{geometries[i]});
2483 }
2484
2486 options.keep_original_ids = true;
2487 options.realize_instance_attributes = false;
2488 return realize_instances(bke::GeometrySet::from_instances(instances.release()), options);
2489}
2496
2498{
2500
2501 const Scene *scene = CTX_data_scene(C);
2502 Object *object = CTX_data_active_object(C);
2503 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
2505 scene->toolsettings);
2506
2508
2509 int num_elements_copied = 0;
2510 Map<const Layer *, Vector<bke::GeometrySet>> copied_curves_per_layer;
2511
2512 /* Collect all selected strokes/points on all editable layers. */
2513 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
2514 for (const MutableDrawingInfo &drawing_info : drawings) {
2515 const bke::CurvesGeometry &curves = drawing_info.drawing.strokes();
2516 const Layer &layer = grease_pencil.layer(drawing_info.layer_index);
2517
2518 if (curves.is_empty()) {
2519 continue;
2520 }
2522 continue;
2523 }
2524
2525 /* Get a copy of the selected geometry on this layer. */
2526 IndexMaskMemory memory;
2527 bke::CurvesGeometry copied_curves;
2528
2529 if (selection_domain == bke::AttrDomain::Curve) {
2530 const IndexMask selected_curves = ed::curves::retrieve_selected_curves(curves, memory);
2531 copied_curves = curves_copy_curve_selection(curves, selected_curves, {});
2532 num_elements_copied += copied_curves.curves_num();
2533 }
2534 else if (selection_domain == bke::AttrDomain::Point) {
2535 const IndexMask selected_points = ed::curves::retrieve_selected_points(curves, memory);
2536 copied_curves = geometry::remove_points_and_split(
2537 curves, selected_points.complement(curves.points_range(), memory));
2538 num_elements_copied += copied_curves.points_num();
2539 }
2540
2541 /* Add the layer selection to the set of copied curves. */
2542 copied_curves_per_layer.lookup_or_add_default(&layer).append(
2543 bke::GeometrySet::from_curves(curves_new_nomain(std::move(copied_curves))));
2544 }
2545
2546 if (copied_curves_per_layer.is_empty()) {
2547 clipboard.layers.reinitialize(0);
2548 return OPERATOR_CANCELLED;
2549 }
2550
2551 clipboard.layers.reinitialize(copied_curves_per_layer.size());
2552
2553 int i = 0;
2554 for (auto const &[layer, geometries] : copied_curves_per_layer.items()) {
2555 const float4x4 layer_to_object = layer->to_object_space(*object);
2556 Clipboard::ClipboardLayer &cliplayer = clipboard.layers[i];
2557
2558 bke::GeometrySet joined_copied_curves = join_geometries_with_transform(geometries.as_span(),
2559 layer_to_object);
2560 cliplayer.curves = joined_copied_curves.get_curves()->geometry.wrap();
2561 cliplayer.name = layer->name();
2562 i++;
2563 }
2564 clipboard.object_to_world = object->object_to_world();
2565
2566 /* Store the session uid of the materials used by the curves in the clipboard. We use the uid to
2567 * remap the material indices when pasting. */
2568 clipboard.materials.clear();
2569 clipboard.materials_in_source_num = grease_pencil.material_array_num;
2570
2571 const auto is_material_index_used = [&](const int material_index) -> bool {
2572 for (const Clipboard::ClipboardLayer &layer : clipboard.layers) {
2573 const bke::AttributeAccessor attributes = layer.curves.attributes();
2574 const VArraySpan<int> material_indices = *attributes.lookup_or_default<int>(
2575 "material_index", bke::AttrDomain::Curve, 0);
2576 if (material_indices.contains(material_index)) {
2577 return true;
2578 }
2579 }
2580 return false;
2581 };
2582
2583 for (const int material_index : IndexRange(grease_pencil.material_array_num)) {
2584 if (!is_material_index_used(material_index)) {
2585 continue;
2586 }
2587 const Material *material = BKE_object_material_get(object, material_index + 1);
2588 clipboard.materials.append({material ? material->id.session_uid : 0, material_index});
2589 }
2590
2591 /* Report the numbers. */
2592 if (selection_domain == bke::AttrDomain::Curve) {
2593 BKE_reportf(op->reports, RPT_INFO, "Copied %d selected curve(s)", num_elements_copied);
2594 }
2595 else if (selection_domain == bke::AttrDomain::Point) {
2596 BKE_reportf(op->reports, RPT_INFO, "Copied %d selected point(s)", num_elements_copied);
2597 }
2598
2599 return OPERATOR_FINISHED;
2600}
2601
2603{
2604 ot->name = "Copy Strokes";
2605 ot->idname = "GREASE_PENCIL_OT_copy";
2606 ot->description = "Copy the selected Grease Pencil points or strokes to the internal clipboard";
2607
2610
2611 ot->flag = OPTYPE_REGISTER;
2612}
2613
2615 Object &object,
2616 const bke::CurvesGeometry &curves_to_paste,
2617 const float4x4 &object_to_paste_layer,
2618 const float4x4 &clipboard_to_world,
2619 const bool keep_world_transform,
2620 const bool paste_back,
2622{
2623 /* Get a list of all materials in the scene. */
2624 const Array<int> clipboard_material_remap = ed::greasepencil::clipboard_materials_remap(bmain,
2625 object);
2626
2627 /* Get the index range of the pasted curves in the target layer. */
2628 const IndexRange pasted_curves_range = paste_back ? IndexRange(0, curves_to_paste.curves_num()) :
2629 IndexRange(drawing.strokes().curves_num(),
2630 curves_to_paste.curves_num());
2631
2632 /* Append the geometry from the clipboard to the target layer. */
2633 Curves *clipboard_id = bke::curves_new_nomain(curves_to_paste);
2634 Curves *target_id = curves_new_nomain(std::move(drawing.strokes_for_write()));
2635
2636 const Array<bke::GeometrySet> geometry_sets = {
2637 bke::GeometrySet::from_curves(paste_back ? clipboard_id : target_id),
2638 bke::GeometrySet::from_curves(paste_back ? target_id : clipboard_id)};
2639
2640 const float4x4 transform = object_to_paste_layer *
2641 (keep_world_transform ?
2642 object.world_to_object() * clipboard_to_world :
2644 const Array<float4x4> transforms = paste_back ? Span<float4x4>{transform, float4x4::identity()} :
2647 geometry_sets, VArray<float4x4>::ForContainer(transforms));
2648
2649 drawing.strokes_for_write() = std::move(joined_curves.get_curves_for_write()->geometry.wrap());
2650
2651 if (!clipboard_material_remap.is_empty()) {
2652 /* Remap the material indices of the pasted curves to the target object material indices. */
2654 bke::SpanAttributeWriter<int> material_indices = attributes.lookup_or_add_for_write_span<int>(
2655 "material_index", bke::AttrDomain::Curve);
2656 if (material_indices) {
2657 for (const int i : pasted_curves_range) {
2658 material_indices.span[i] = clipboard_material_remap[material_indices.span[i]];
2659 }
2660 material_indices.finish();
2661 }
2662 }
2663
2664 drawing.tag_topology_changed();
2665
2666 return pasted_curves_range;
2667}
2668
2669enum class PasteType {
2672};
2673
2675{
2676 using namespace bke::greasepencil;
2677 Main *bmain = CTX_data_main(C);
2678 const Scene &scene = *CTX_data_scene(C);
2679 Object *object = CTX_data_active_object(C);
2681 scene.toolsettings);
2682 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
2683
2684 const PasteType type = PasteType(RNA_enum_get(op->ptr, "type"));
2685
2686 const bool keep_world_transform = RNA_boolean_get(op->ptr, "keep_world_transform");
2687 const bool paste_on_back = RNA_boolean_get(op->ptr, "paste_back");
2688
2690 if (clipboard.layers.is_empty()) {
2691 return OPERATOR_CANCELLED;
2692 }
2693
2694 /* Make sure everything on the clipboard is selected, in the correct selection domain. */
2696 bke::GSpanAttributeWriter selection = ed::curves::ensure_selection_attribute(
2697 layer.curves, selection_domain, CD_PROP_BOOL);
2698 selection.finish();
2699 });
2700
2701 if (type == PasteType::Active) {
2702 Layer *active_layer = grease_pencil.get_active_layer();
2703 if (!active_layer) {
2704 BKE_report(op->reports, RPT_ERROR, "No active Grease Pencil layer to paste into");
2705 return OPERATOR_CANCELLED;
2706 }
2707 if (!active_layer->is_editable()) {
2708 BKE_report(op->reports, RPT_ERROR, "Active layer is not editable");
2709 return OPERATOR_CANCELLED;
2710 }
2711
2712 /* Deselect everything from editable drawings. The pasted strokes are the only ones then after
2713 * the paste. That's convenient for the user. */
2714 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
2715 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
2717 info.drawing.strokes_for_write(), selection_domain, CD_PROP_BOOL);
2718 ed::curves::fill_selection_false(selection_in_target.span);
2719 selection_in_target.finish();
2720 });
2721
2722 const float4x4 object_to_layer = math::invert(active_layer->to_object_space(*object));
2723
2724 /* Ensure active keyframe. */
2725 bool inserted_keyframe = false;
2726 if (!ensure_active_keyframe(scene, grease_pencil, *active_layer, false, inserted_keyframe)) {
2727 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil frame to draw on");
2728 return OPERATOR_CANCELLED;
2729 }
2730
2731 Vector<MutableDrawingInfo> drawing_infos =
2733 scene, grease_pencil, *active_layer);
2734 for (const MutableDrawingInfo info : drawing_infos) {
2736 *bmain, *object, object_to_layer, keep_world_transform, paste_on_back, info.drawing);
2737 }
2738
2739 if (inserted_keyframe) {
2741 }
2742 }
2743 else if (type == PasteType::ByLayer) {
2744 Layer *active_layer = grease_pencil.get_active_layer();
2745 /* Find layers to paste strokes into. */
2746 Array<Layer *> layers_to_paste_into(clipboard.layers.size());
2747 for (const int clip_layer_i : clipboard.layers.index_range()) {
2748 const Clipboard::ClipboardLayer &layer = clipboard.layers[clip_layer_i];
2749 bke::greasepencil::TreeNode *node = grease_pencil.find_node_by_name(layer.name);
2750 const bool found_layer = node && node->is_layer() && node->as_layer().is_editable();
2751 if (found_layer) {
2752 layers_to_paste_into[clip_layer_i] = &node->as_layer();
2753 continue;
2754 }
2755 if (active_layer && active_layer->is_editable()) {
2756 /* Fall back to active layer. */
2757 BKE_report(
2758 op->reports, RPT_WARNING, "Couldn't find matching layer, pasting into active layer");
2759 layers_to_paste_into[clip_layer_i] = active_layer;
2760 continue;
2761 }
2762
2763 if (!active_layer) {
2764 BKE_report(op->reports, RPT_ERROR, "No active Grease Pencil layer to paste into");
2765 }
2766 if (!active_layer->is_editable()) {
2767 BKE_report(op->reports, RPT_ERROR, "Active layer is not editable");
2768 }
2769 return OPERATOR_CANCELLED;
2770 }
2771
2772 /* Deselect everything from editable drawings. The pasted strokes are the only ones then after
2773 * the paste. That's convenient for the user. */
2774 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
2775 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
2777 info.drawing.strokes_for_write(), selection_domain, CD_PROP_BOOL);
2778 ed::curves::fill_selection_false(selection_in_target.span);
2779 selection_in_target.finish();
2780 });
2781
2782 for (const int clip_layer_i : clipboard.layers.index_range()) {
2783 const Clipboard::ClipboardLayer &clip_layer = clipboard.layers[clip_layer_i];
2784 const bke::CurvesGeometry &curves_to_paste = clip_layer.curves;
2785
2786 BLI_assert(layers_to_paste_into[clip_layer_i] != nullptr);
2787 Layer &paste_layer = *layers_to_paste_into[clip_layer_i];
2788 const float4x4 object_to_paste_layer = math::invert(paste_layer.to_object_space(*object));
2789
2790 /* Ensure active keyframe. */
2791 bool inserted_keyframe = false;
2792 if (!ensure_active_keyframe(scene, grease_pencil, paste_layer, false, inserted_keyframe)) {
2793 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil frame to draw on");
2794 return OPERATOR_CANCELLED;
2795 }
2796
2797 Vector<MutableDrawingInfo> drawing_infos =
2799 scene, grease_pencil, paste_layer);
2800 for (const MutableDrawingInfo info : drawing_infos) {
2802 *object,
2803 curves_to_paste,
2804 object_to_paste_layer,
2805 clipboard.object_to_world,
2806 keep_world_transform,
2807 paste_on_back,
2808 info.drawing);
2809 }
2810
2811 if (inserted_keyframe) {
2813 }
2814 }
2815 }
2816 else {
2818 }
2819
2820 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
2821 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
2822
2823 return OPERATOR_FINISHED;
2824}
2825
2827{
2829 return false;
2830 }
2831
2832 std::scoped_lock lock(grease_pencil_clipboard_lock);
2833 /* Check for curves in the Grease Pencil clipboard. */
2834 return (grease_pencil_clipboard && grease_pencil_clipboard->layers.size() > 0);
2835}
2836
2838{
2839 PropertyRNA *prop;
2840
2841 static const EnumPropertyItem rna_paste_items[] = {
2842 {int(PasteType::Active), "ACTIVE", 0, "Paste to Active", ""},
2843 {int(PasteType::ByLayer), "LAYER", 0, "Paste by Layer", ""},
2844 {0, nullptr, 0, nullptr, nullptr},
2845 };
2846
2847 ot->name = "Paste Strokes";
2848 ot->idname = "GREASE_PENCIL_OT_paste";
2849 ot->description =
2850 "Paste Grease Pencil points or strokes from the internal clipboard to the active layer";
2851
2854
2855 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2856
2857 ot->prop = RNA_def_enum(ot->srna, "type", rna_paste_items, int(PasteType::Active), "Type", "");
2858
2859 prop = RNA_def_boolean(
2860 ot->srna, "paste_back", false, "Paste on Back", "Add pasted strokes behind all strokes");
2862
2863 prop = RNA_def_boolean(ot->srna,
2864 "keep_world_transform",
2865 false,
2866 "Keep World Transform",
2867 "Keep the world transform of strokes from the clipboard unchanged");
2869}
2870
2872
2874 Object &object,
2875 const float4x4 &object_to_paste_layer,
2876 const bool keep_world_transform,
2877 const bool paste_back,
2879{
2881 if (clipboard.layers.is_empty()) {
2882 return {};
2883 }
2884
2885 Vector<bke::GeometrySet> geometries_to_join;
2886 for (Clipboard::ClipboardLayer &layer : clipboard.layers) {
2888 }
2889 bke::GeometrySet joined_clipboard_set = geometry::join_geometries(geometries_to_join.as_span(),
2890 {});
2891 BLI_assert(joined_clipboard_set.has_curves());
2892 const bke::CurvesGeometry &joined_clipboard_curves =
2893 joined_clipboard_set.get_curves()->geometry.wrap();
2894
2895 return clipboard_paste_strokes_ex(bmain,
2896 object,
2897 joined_clipboard_curves,
2898 object_to_paste_layer,
2899 clipboard.object_to_world,
2900 keep_world_transform,
2901 paste_back,
2902 drawing);
2903}
2904
2905/* -------------------------------------------------------------------- */
2909{
2910 const Scene *scene = CTX_data_scene(C);
2911 Object *object = CTX_data_active_object(C);
2912 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
2913
2914 const float threshold = RNA_float_get(op->ptr, "threshold");
2915 const bool use_unselected = RNA_boolean_get(op->ptr, "use_unselected");
2916
2917 std::atomic<bool> changed = false;
2918
2919 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
2920 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
2921 bke::greasepencil::Drawing &drawing = info.drawing;
2922 IndexMaskMemory memory;
2923 const IndexMask points = use_unselected ?
2925 *object, drawing, info.layer_index, memory) :
2927 *object, info.drawing, info.layer_index, memory);
2928 if (points.is_empty()) {
2929 return;
2930 }
2932 drawing.strokes(), threshold, points, {});
2933 drawing.tag_topology_changed();
2934 changed.store(true, std::memory_order_relaxed);
2935 });
2936 if (changed) {
2937 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
2938 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
2939 }
2940 return OPERATOR_FINISHED;
2941}
2942
2944{
2945 PropertyRNA *prop;
2946
2947 ot->name = "Merge by Distance";
2948 ot->idname = "GREASE_PENCIL_OT_stroke_merge_by_distance";
2949 ot->description = "Merge points by distance";
2950
2953
2954 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2955
2956 prop = RNA_def_float(ot->srna, "threshold", 0.001f, 0.0f, 100.0f, "Threshold", "", 0.0f, 100.0f);
2957 /* Avoid re-using last var. */
2959
2960 prop = RNA_def_boolean(ot->srna,
2961 "use_unselected",
2962 false,
2963 "Unselected",
2964 "Use whole stroke, not only selected points");
2966}
2967
2969
2970/* -------------------------------------------------------------------- */
2973
2975 const IndexMask &points_to_extrude)
2976{
2977 const OffsetIndices<int> points_by_curve = src.points_by_curve();
2978
2979 const int old_curves_num = src.curves_num();
2980 const int old_points_num = src.points_num();
2981
2982 Vector<int> dst_to_src_points(old_points_num);
2984
2985 Vector<int> dst_to_src_curves(old_curves_num);
2987
2988 Vector<bool> dst_selected(old_points_num, false);
2989
2990 Vector<int> dst_curve_counts(old_curves_num);
2992 points_by_curve, src.curves_range(), dst_curve_counts.as_mutable_span());
2993
2994 const VArray<bool> &src_cyclic = src.cyclic();
2995
2996 /* Point offset keeps track of the points inserted. */
2997 int point_offset = 0;
2998 for (const int curve_index : src.curves_range()) {
2999 const IndexRange curve_points = points_by_curve[curve_index];
3000 const IndexMask curve_points_to_extrude = points_to_extrude.slice_content(curve_points);
3001 const bool curve_cyclic = src_cyclic[curve_index];
3002
3003 curve_points_to_extrude.foreach_index([&](const int src_point_index) {
3004 if (!curve_cyclic && (src_point_index == curve_points.first())) {
3005 /* Start-point extruded, we insert a new point at the beginning of the curve.
3006 * NOTE: all points of a cyclic curve behave like an inner-point. */
3007 dst_to_src_points.insert(src_point_index + point_offset, src_point_index);
3008 dst_selected.insert(src_point_index + point_offset, true);
3009 ++dst_curve_counts[curve_index];
3010 ++point_offset;
3011 return;
3012 }
3013 if (!curve_cyclic && (src_point_index == curve_points.last())) {
3014 /* End-point extruded, we insert a new point at the end of the curve.
3015 * NOTE: all points of a cyclic curve behave like an inner-point. */
3016 dst_to_src_points.insert(src_point_index + point_offset + 1, src_point_index);
3017 dst_selected.insert(src_point_index + point_offset + 1, true);
3018 ++dst_curve_counts[curve_index];
3019 ++point_offset;
3020 return;
3021 }
3022
3023 /* Inner-point extruded: we create a new curve made of two points located at the same
3024 * position. Only one of them is selected so that the other one remains stuck to the curve.
3025 */
3026 dst_to_src_points.append(src_point_index);
3027 dst_selected.append(false);
3028 dst_to_src_points.append(src_point_index);
3029 dst_selected.append(true);
3030 dst_to_src_curves.append(curve_index);
3031 dst_curve_counts.append(2);
3032 });
3033 }
3034
3035 const int new_points_num = dst_to_src_points.size();
3036 const int new_curves_num = dst_to_src_curves.size();
3037
3038 bke::CurvesGeometry dst(new_points_num, new_curves_num);
3040
3041 /* Setup curve offsets, based on the number of points in each curve. */
3042 MutableSpan<int> new_curve_offsets = dst.offsets_for_write();
3043 array_utils::copy(dst_curve_counts.as_span(), new_curve_offsets.drop_back(1));
3045
3046 /* Attributes. */
3047 const bke::AttributeAccessor src_attributes = src.attributes();
3049
3050 /* Selection attribute. */
3051 /* Copy the value of control point selections to all selection attributes.
3052 *
3053 * This will lead to the extruded control point always having both handles selected, if it's a
3054 * bezier type stroke. This is to circumvent the issue of source curves handles not being
3055 * deselected when the user extrudes a bezier control point with both handles selected*/
3056 for (const StringRef selection_attribute_name :
3058 {
3060 dst, bke::AttrDomain::Point, CD_PROP_BOOL, selection_attribute_name);
3061 selection.span.copy_from(dst_selected.as_span());
3062 selection.finish();
3063 }
3064
3065 bke::gather_attributes(src_attributes,
3068 {},
3069 dst_to_src_curves,
3070 dst_attributes);
3071
3072 /* Cyclic attribute : newly created curves cannot be cyclic. */
3073 dst.cyclic_for_write().drop_front(old_curves_num).fill(false);
3074
3075 bke::gather_attributes(src_attributes,
3079 {".selection", ".selection_handle_left", ".selection_handle_right"}),
3080 dst_to_src_points,
3081 dst_attributes);
3082
3083 dst.update_curve_types();
3084 if (src.nurbs_has_custom_knots()) {
3085 IndexMaskMemory memory;
3086 const VArray<int8_t> curve_types = src.curve_types();
3087 const VArray<int8_t> knot_modes = dst.nurbs_knots_modes();
3088 const OffsetIndices<int> dst_points_by_curve = dst.points_by_curve();
3089 const IndexMask include_curves = IndexMask::from_predicate(
3090 src.curves_range(), GrainSize(512), memory, [&](const int64_t curve_index) {
3091 return curve_types[curve_index] == CURVE_TYPE_NURBS &&
3092 knot_modes[curve_index] == NURBS_KNOT_MODE_CUSTOM &&
3093 points_by_curve[curve_index].size() == dst_points_by_curve[curve_index].size();
3094 });
3096 include_curves.complement(dst.curves_range(), memory),
3099 dst);
3100 bke::curves::nurbs::gather_custom_knots(src, include_curves, 0, dst);
3101 }
3102 return dst;
3103}
3104
3106{
3107 const Scene *scene = CTX_data_scene(C);
3108 Object *object = CTX_data_active_object(C);
3109 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
3110
3111 std::atomic<bool> changed = false;
3112 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
3113 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3114 IndexMaskMemory memory;
3115 const IndexMask points_to_extrude = retrieve_editable_and_selected_points(
3116 *object, info.drawing, info.layer_index, memory);
3117 if (points_to_extrude.is_empty()) {
3118 return;
3119 }
3120
3121 const bke::CurvesGeometry &curves = info.drawing.strokes();
3122 info.drawing.strokes_for_write() = extrude_grease_pencil_curves(curves, points_to_extrude);
3123
3125 changed.store(true, std::memory_order_relaxed);
3126 });
3127
3128 if (changed) {
3129 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
3130 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
3131 }
3132
3133 return OPERATOR_FINISHED;
3134}
3135
3137{
3138 ot->name = "Extrude Stroke Points";
3139 ot->idname = "GREASE_PENCIL_OT_extrude";
3140 ot->description = "Extrude the selected points";
3141
3144
3145 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3146}
3147
3149
3150/* -------------------------------------------------------------------- */
3153
3154/* Determine how much the radius needs to be scaled to look the same from the view. */
3156 const float3 &old_pos,
3157 const float3 &new_pos)
3158{
3159 /* Don't scale the radius when the view is orthographic. */
3160 if (!rv3d->is_persp) {
3161 return 1.0f;
3162 }
3163
3164 const float3 view_center = float3(rv3d->viewinv[3]);
3165 return math::length(new_pos - view_center) / math::length(old_pos - view_center);
3166}
3167
3169{
3170 Scene &scene = *CTX_data_scene(C);
3172
3173 View3D *v3d = CTX_wm_view3d(C);
3174 ARegion *region = CTX_wm_region(C);
3175
3176 RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
3177
3178 const ReprojectMode mode = ReprojectMode(RNA_enum_get(op->ptr, "type"));
3179 const bool keep_original = RNA_boolean_get(op->ptr, "keep_original");
3180
3181 Object *object = CTX_data_active_object(C);
3182 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
3183 const float offset = RNA_float_get(op->ptr, "offset");
3184
3185 /* Init snap context for geometry projection. */
3187 [&]() -> transform::SnapObjectContext * {
3188 if (mode == ReprojectMode::Surface) {
3189 return transform::snap_object_context_create(&scene, 0);
3190 }
3191 return nullptr;
3192 });
3193
3195 scene.toolsettings);
3196
3197 const int oldframe = int(DEG_get_ctime(depsgraph));
3198 if (keep_original) {
3199 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
3200 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3201 IndexMaskMemory memory;
3203 *object, info.drawing, info.layer_index, selection_domain, memory);
3204 if (elements.is_empty()) {
3205 return;
3206 }
3207
3209 if (selection_domain == bke::AttrDomain::Curve) {
3211 }
3212 else if (selection_domain == bke::AttrDomain::Point) {
3214 }
3216 });
3217 }
3218
3219 /* TODO: This can probably be optimized further for the non-Surface projection use case by
3220 * considering all drawings for the parallel loop instead of having to partition by frame number.
3221 */
3222 std::atomic<bool> changed = false;
3223 Array<Vector<MutableDrawingInfo>> drawings_per_frame =
3225 for (const Span<MutableDrawingInfo> drawings : drawings_per_frame) {
3226 if (drawings.is_empty()) {
3227 continue;
3228 }
3229 const int current_frame_number = drawings.first().frame_number;
3230
3231 if (mode == ReprojectMode::Surface) {
3232 scene.r.cfra = current_frame_number;
3234 }
3235
3236 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3238 MutableSpan<float> radii = curves.radius_for_write();
3239
3240 IndexMaskMemory memory;
3241 const IndexMask editable_points = retrieve_editable_points(
3242 *object, info.drawing, info.layer_index, memory);
3243
3244 for (const StringRef selection_name :
3246 {
3247 const IndexMask selected_points = ed::curves::retrieve_selected_points(
3248 curves, selection_name, memory);
3249 const IndexMask points_to_reproject = IndexMask::from_intersection(
3250 editable_points, selected_points, memory);
3251
3252 if (points_to_reproject.is_empty()) {
3253 return;
3254 }
3255
3256 MutableSpan<float3> positions = curves.positions_for_write();
3257 if (selection_name == ".selection_handle_left") {
3258 positions = curves.handle_positions_left_for_write();
3259 }
3260 else if (selection_name == ".selection_handle_right") {
3261 positions = curves.handle_positions_right_for_write();
3262 }
3263
3264 const bke::greasepencil::Layer &layer = grease_pencil.layer(info.layer_index);
3265 if (mode == ReprojectMode::Surface) {
3266 const float4x4 layer_space_to_world_space = layer.to_world_space(*object);
3267 const float4x4 world_space_to_layer_space = math::invert(layer_space_to_world_space);
3268 points_to_reproject.foreach_index(GrainSize(4096), [&](const int point_i) {
3269 float3 &position = positions[point_i];
3270 const float3 world_pos = math::transform_point(layer_space_to_world_space, position);
3271 float2 screen_co;
3272 if (ED_view3d_project_float_global(region, world_pos, screen_co, V3D_PROJ_TEST_NOP) !=
3274 {
3275 return;
3276 }
3277
3278 float3 ray_start, ray_direction;
3280 depsgraph, region, v3d, screen_co, ray_start, ray_direction, true))
3281 {
3282 return;
3283 }
3284
3285 float hit_depth = std::numeric_limits<float>::max();
3286 float3 hit_position(0.0f);
3287 float3 hit_normal(0.0f);
3288
3290 params.snap_target_select = SCE_SNAP_TARGET_ALL;
3291 transform::SnapObjectContext *snap_context = thread_snap_contexts.local();
3292 if (transform::snap_object_project_ray(snap_context,
3293 depsgraph,
3294 v3d,
3295 &params,
3296 ray_start,
3297 ray_direction,
3298 &hit_depth,
3299 hit_position,
3300 hit_normal))
3301 {
3302 /* Apply offset over surface. */
3303 const float3 new_pos = math::transform_point(
3304 world_space_to_layer_space,
3305 hit_position + math::normalize(ray_start - hit_position) * offset);
3306
3307 if (selection_name == ".selection") {
3308 radii[point_i] *= calculate_radius_projection_factor(rv3d, position, new_pos);
3309 }
3310 position = new_pos;
3311 }
3312 });
3313 }
3314 else {
3315 const DrawingPlacement drawing_placement(
3316 scene, *region, *v3d, *object, &layer, mode, offset, nullptr);
3317 points_to_reproject.foreach_index(GrainSize(4096), [&](const int point_i) {
3318 const float3 new_pos = drawing_placement.reproject(positions[point_i]);
3319 if (selection_name == ".selection") {
3320 radii[point_i] *= calculate_radius_projection_factor(
3321 rv3d, positions[point_i], new_pos);
3322 }
3323 positions[point_i] = new_pos;
3324 });
3325 }
3326
3328 changed.store(true, std::memory_order_relaxed);
3329 }
3330 });
3331 }
3332
3333 for (transform::SnapObjectContext *snap_context : thread_snap_contexts) {
3334 if (snap_context != nullptr) {
3336 }
3337 }
3338
3339 if (mode == ReprojectMode::Surface) {
3340 scene.r.cfra = oldframe;
3342 }
3343
3344 if (changed) {
3345 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
3346 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
3347 }
3348
3349 return OPERATOR_FINISHED;
3350}
3351
3353{
3354 uiLayout *layout = op->layout;
3355 uiLayout *row;
3356
3357 const ReprojectMode type = ReprojectMode(RNA_enum_get(op->ptr, "type"));
3358
3359 uiLayoutSetPropSep(layout, true);
3360 uiLayoutSetPropDecorate(layout, false);
3361 row = &layout->row(true);
3362 row->prop(op->ptr, "type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
3363
3364 if (type == ReprojectMode::Surface) {
3365 row = &layout->row(true);
3366 row->prop(op->ptr, "offset", UI_ITEM_NONE, std::nullopt, ICON_NONE);
3367 }
3368 row = &layout->row(true);
3369 row->prop(op->ptr, "keep_original", UI_ITEM_NONE, std::nullopt, ICON_NONE);
3370}
3371
3373{
3374 static const EnumPropertyItem reproject_type[] = {
3376 "FRONT",
3377 0,
3378 "Front",
3379 "Reproject the strokes using the X-Z plane"},
3380 {int(ReprojectMode::Side), "SIDE", 0, "Side", "Reproject the strokes using the Y-Z plane"},
3381 {int(ReprojectMode::Top), "TOP", 0, "Top", "Reproject the strokes using the X-Y plane"},
3382 {int(ReprojectMode::View),
3383 "VIEW",
3384 0,
3385 "View",
3386 "Reproject the strokes to end up on the same plane, as if drawn from the current "
3387 "viewpoint "
3388 "using 'Cursor' Stroke Placement"},
3390 "SURFACE",
3391 0,
3392 "Surface",
3393 "Reproject the strokes on to the scene geometry, as if drawn using 'Surface' placement"},
3395 "CURSOR",
3396 0,
3397 "Cursor",
3398 "Reproject the strokes using the orientation of 3D cursor"},
3399 {0, nullptr, 0, nullptr, nullptr},
3400 };
3401
3402 /* identifiers */
3403 ot->name = "Reproject Strokes";
3404 ot->idname = "GREASE_PENCIL_OT_reproject";
3405 ot->description =
3406 "Reproject the selected strokes from the current viewpoint as if they had been newly "
3407 "drawn "
3408 "(e.g. to fix problems from accidental 3D cursor movement or accidental viewport changes, "
3409 "or for matching deforming geometry)";
3410
3411 /* callbacks */
3412 ot->invoke = WM_menu_invoke;
3416
3417 /* flags */
3418 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3419
3420 /* properties */
3421 ot->prop = RNA_def_enum(
3422 ot->srna, "type", reproject_type, int(ReprojectMode::View), "Projection Type", "");
3423
3425 ot->srna,
3426 "keep_original",
3427 false,
3428 "Keep Original",
3429 "Keep original strokes and create a copy before reprojecting");
3431
3432 RNA_def_float(ot->srna, "offset", 0.0f, 0.0f, 10.0f, "Surface Offset", "", 0.0f, 10.0f);
3433}
3434
3436/* -------------------------------------------------------------------- */
3439
3440/* Poll callback for snap operators */
3441/* NOTE: For now, we only allow these in the 3D view, as other editors do not
3442 * define a cursor or grid-step which can be used.
3443 */
3445{
3447 return false;
3448 }
3449
3450 const ScrArea *area = CTX_wm_area(C);
3451 if (!(area && area->spacetype == SPACE_VIEW3D)) {
3452 return false;
3453 }
3454 const ARegion *region = CTX_wm_region(C);
3455 if (!(region && region->regiontype == RGN_TYPE_WINDOW)) {
3456 return false;
3457 }
3458
3459 return true;
3460}
3461
3463{
3465
3466 const Scene &scene = *CTX_data_scene(C);
3467 Object &object = *CTX_data_active_object(C);
3468 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
3469 const View3D &v3d = *CTX_wm_view3d(C);
3470 const ARegion &region = *CTX_wm_region(C);
3471 const float grid_size = ED_view3d_grid_view_scale(&scene, &v3d, &region, nullptr);
3472
3473 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
3474 for (const MutableDrawingInfo &drawing_info : drawings) {
3475 bke::CurvesGeometry &curves = drawing_info.drawing.strokes_for_write();
3476 if (curves.is_empty()) {
3477 continue;
3478 }
3480 continue;
3481 }
3482
3484 {
3485 IndexMaskMemory memory;
3486 const IndexMask selected_points = ed::curves::retrieve_selected_points(
3487 curves, selection_name, memory);
3488
3489 const Layer &layer = grease_pencil.layer(drawing_info.layer_index);
3490 const float4x4 layer_to_world = layer.to_world_space(object);
3491 const float4x4 world_to_layer = math::invert(layer_to_world);
3492
3493 MutableSpan<float3> positions = curves.positions_for_write();
3494 if (selection_name == ".selection_handle_left") {
3495 positions = curves.handle_positions_left_for_write();
3496 }
3497 else if (selection_name == ".selection_handle_right") {
3498 positions = curves.handle_positions_right_for_write();
3499 }
3500 selected_points.foreach_index(GrainSize(4096), [&](const int point_i) {
3501 const float3 pos_world = math::transform_point(layer_to_world, positions[point_i]);
3502 const float3 pos_snapped = grid_size * math::floor(pos_world / grid_size + 0.5f);
3503 positions[point_i] = math::transform_point(world_to_layer, pos_snapped);
3504 });
3505 }
3506
3507 drawing_info.drawing.tag_positions_changed();
3511 WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, &grease_pencil);
3512 }
3513
3514 return OPERATOR_FINISHED;
3515}
3516
3518{
3519 ot->name = "Snap Selection to Grid";
3520 ot->idname = "GREASE_PENCIL_OT_snap_to_grid";
3521 ot->description = "Snap selected points to the nearest grid points";
3522
3525
3526 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3527}
3528
3530
3531/* -------------------------------------------------------------------- */
3534
3536{
3538
3539 const Scene &scene = *CTX_data_scene(C);
3540 Object &object = *CTX_data_active_object(C);
3541 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
3542 const bool use_offset = RNA_boolean_get(op->ptr, "use_offset");
3543 const float3 cursor_world = scene.cursor.location;
3544
3545 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
3546 for (const MutableDrawingInfo &drawing_info : drawings) {
3547 bke::CurvesGeometry &curves = drawing_info.drawing.strokes_for_write();
3548 if (curves.is_empty()) {
3549 continue;
3550 }
3552 continue;
3553 }
3554
3555 IndexMaskMemory selected_points_memory;
3557 selected_points_memory);
3558
3559 const Layer &layer = grease_pencil.layer(drawing_info.layer_index);
3560 const float4x4 layer_to_world = layer.to_world_space(object);
3561 const float4x4 world_to_layer = math::invert(layer_to_world);
3562 const float3 cursor_layer = math::transform_point(world_to_layer, cursor_world);
3563
3564 MutableSpan<float3> positions = curves.positions_for_write();
3565 if (use_offset) {
3566 const OffsetIndices points_by_curve = curves.points_by_curve();
3567 IndexMaskMemory selected_curves_memory;
3568 const IndexMask selected_curves = ed::curves::retrieve_selected_curves(
3569 curves, selected_curves_memory);
3570
3571 selected_curves.foreach_index(GrainSize(512), [&](const int curve_i) {
3572 const IndexRange points = points_by_curve[curve_i];
3573
3574 /* Offset from first point of the curve. */
3575 const float3 offset = cursor_layer - positions[points.first()];
3576 selected_points.slice_content(points).foreach_index(
3577 GrainSize(4096), [&](const int point_i) { positions[point_i] += offset; });
3578 });
3579 }
3580 else {
3581 /* Set all selected positions to the cursor location. */
3582 index_mask::masked_fill(positions, cursor_layer, selected_points);
3583 }
3584
3585 curves.calculate_bezier_auto_handles();
3586 drawing_info.drawing.tag_positions_changed();
3590 WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, &grease_pencil);
3591 }
3592
3593 return OPERATOR_FINISHED;
3594}
3595
3597{
3598 /* identifiers */
3599 ot->name = "Snap Selection to Cursor";
3600 ot->idname = "GREASE_PENCIL_OT_snap_to_cursor";
3601 ot->description = "Snap selected points/strokes to the cursor";
3602
3603 /* callbacks */
3606
3607 /* flags */
3608 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3609
3610 /* props */
3611 ot->prop = RNA_def_boolean(ot->srna,
3612 "use_offset",
3613 true,
3614 "With Offset",
3615 "Offset the entire stroke instead of selected points only");
3616}
3617
3619
3620/* -------------------------------------------------------------------- */
3623
3625 const Object &object,
3626 const GreasePencil &grease_pencil,
3627 float3 &r_centroid,
3628 float3 &r_min,
3629 float3 &r_max)
3630{
3632
3633 int num_selected = 0;
3634 r_centroid = float3(0.0f);
3635 r_min = float3(std::numeric_limits<float>::max());
3636 r_max = float3(std::numeric_limits<float>::lowest());
3637
3638 const Vector<DrawingInfo> drawings = retrieve_visible_drawings(scene, grease_pencil, false);
3639 for (const DrawingInfo &drawing_info : drawings) {
3640 const Layer &layer = grease_pencil.layer(drawing_info.layer_index);
3641 if (layer.is_locked()) {
3642 continue;
3643 }
3644 const bke::CurvesGeometry &curves = drawing_info.drawing.strokes();
3645 if (curves.is_empty()) {
3646 continue;
3647 }
3649 continue;
3650 }
3651
3652 IndexMaskMemory selected_points_memory;
3654 selected_points_memory);
3655 const float4x4 layer_to_world = layer.to_world_space(object);
3656
3657 Span<float3> positions = curves.positions();
3658 selected_points.foreach_index(GrainSize(4096), [&](const int point_i) {
3659 const float3 pos_world = math::transform_point(layer_to_world, positions[point_i]);
3660 r_centroid += pos_world;
3661 math::min_max(pos_world, r_min, r_max);
3662 });
3663 num_selected += selected_points.size();
3664 }
3665 if (num_selected == 0) {
3666 r_min = r_max = float3(0.0f);
3667 return false;
3668 }
3669
3670 r_centroid /= num_selected;
3671 return true;
3672}
3673
3675{
3676 Scene &scene = *CTX_data_scene(C);
3677 const Object &object = *CTX_data_active_object(C);
3678 const GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
3679 float3 &cursor = reinterpret_cast<float3 &>(scene.cursor.location);
3680
3681 float3 centroid, points_min, points_max;
3683 scene, object, grease_pencil, centroid, points_min, points_max))
3684 {
3685 return OPERATOR_FINISHED;
3686 }
3687
3688 switch (scene.toolsettings->transform_pivot_point) {
3690 cursor = math::midpoint(points_min, points_max);
3691 break;
3693 case V3D_AROUND_CURSOR:
3695 case V3D_AROUND_ACTIVE:
3696 cursor = centroid;
3697 break;
3698 default:
3700 }
3701
3704
3705 return OPERATOR_FINISHED;
3706}
3707
3709{
3710 /* identifiers */
3711 ot->name = "Snap Cursor to Selected Points";
3712 ot->idname = "GREASE_PENCIL_OT_snap_cursor_to_selected";
3713 ot->description = "Snap cursor to center of selected points";
3714
3715 /* callbacks */
3718
3719 /* flags */
3720 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3721}
3722
3724{
3725 float4x3 strokemat4x3 = float4x3(strokemat);
3726
3727 /*
3728 * We need the diagonal of ones to start from the bottom right instead top left to properly
3729 * apply the two matrices.
3730 *
3731 * i.e.
3732 * # # # # # # # #
3733 * We need # # # # Instead of # # # #
3734 * 0 0 0 1 0 0 1 0
3735 *
3736 */
3737 strokemat4x3[2][2] = 0.0f;
3738 strokemat4x3[3][2] = 1.0f;
3739
3740 return strokemat4x3;
3741}
3742
3744{
3745 const Scene *scene = CTX_data_scene(C);
3746 Object *object = CTX_data_active_object(C);
3747 ARegion *region = CTX_wm_region(C);
3748 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
3749
3750 std::atomic<bool> changed = false;
3751 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
3752 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3753 IndexMaskMemory memory;
3755 *object, info.drawing, info.layer_index, memory);
3756 if (strokes.is_empty()) {
3757 return;
3758 }
3759
3760 const bke::greasepencil::Layer &layer = grease_pencil.layer(info.layer_index);
3761 const float4x4 layer_space_to_world_space = layer.to_world_space(*object);
3762
3763 /* Calculate screen space points. */
3764 const float2 screen_start(RNA_int_get(op->ptr, "xstart"), RNA_int_get(op->ptr, "ystart"));
3765 const float2 screen_end(RNA_int_get(op->ptr, "xend"), RNA_int_get(op->ptr, "yend"));
3766 const float2 screen_direction = screen_end - screen_start;
3767 const float2 screen_tangent = screen_start + float2(-screen_direction[1], screen_direction[0]);
3768
3769 const bke::CurvesGeometry &curves = info.drawing.strokes();
3770 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
3771 const Span<float3> positions = curves.positions();
3773 const VArray<int> materials = *curves.attributes().lookup_or_default<int>(
3774 "material_index", bke::AttrDomain::Curve, 0);
3775
3776 Array<float4x2> texture_matrices(strokes.size());
3777
3778 strokes.foreach_index([&](const int curve_i, const int pos) {
3779 const int material_index = materials[curve_i];
3780
3781 const MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(object,
3782 material_index + 1);
3783 const bool is_radial = gp_style->gradient_type == GP_MATERIAL_GRADIENT_RADIAL;
3784
3785 const float texture_angle = gp_style->texture_angle;
3786 const float2 texture_scale = float2(gp_style->texture_scale);
3787 const float2 texture_offset = float2(gp_style->texture_offset);
3788
3789 const float2x2 texture_rotation = math::from_rotation<float2x2>(
3790 math::AngleRadian(texture_angle));
3791
3792 const float3 point = math::transform_point(layer_space_to_world_space,
3793 positions[points_by_curve[curve_i].first()]);
3794 const float3 normal = math::transform_direction(layer_space_to_world_space,
3795 normals[curve_i]);
3796
3797 const float4 plane = float4(normal, -math::dot(normal, point));
3798
3799 float3 start;
3800 float3 tangent;
3801 float3 end;
3802 ED_view3d_win_to_3d_on_plane(region, plane, screen_start, false, start);
3803 ED_view3d_win_to_3d_on_plane(region, plane, screen_tangent, false, tangent);
3804 ED_view3d_win_to_3d_on_plane(region, plane, screen_end, false, end);
3805
3806 const float3 origin = start;
3807 /* Invert the length by dividing by the length squared. */
3808 const float3 u_dir = (end - origin) / math::length_squared(end - origin);
3809 float3 v_dir = math::cross(u_dir, normal);
3810
3811 /* Flip the texture if need so that it is not mirrored. */
3812 if (math::dot(tangent - start, v_dir) < 0.0f) {
3813 v_dir = -v_dir;
3814 }
3815
3816 /* Calculate the texture space before the texture offset transformation. */
3817 const float4x2 base_texture_space = math::transpose(float2x4(
3818 float4(u_dir, -math::dot(u_dir, origin)), float4(v_dir, -math::dot(v_dir, origin))));
3819
3820 float3x2 offset_matrix = float3x2::identity();
3821
3822 if (is_radial) {
3823 /* Radial gradients are scaled down by a factor of 2 and have the center at 0.5 */
3824 offset_matrix *= 0.5f;
3825 offset_matrix[2] += float2(0.5f, 0.5f);
3826 }
3827
3828 /* For some reason 0.5 is added to the offset before being rendered, so remove it here. */
3829 offset_matrix[2] -= float2(0.5f, 0.5f);
3830
3831 offset_matrix = math::from_scale<float2x2>(texture_scale) * offset_matrix;
3832 offset_matrix = texture_rotation * offset_matrix;
3833 offset_matrix[2] -= texture_offset;
3834
3835 texture_matrices[pos] = (offset_matrix * expand_4x2_mat(base_texture_space)) *
3836 layer_space_to_world_space;
3837 });
3838
3839 info.drawing.set_texture_matrices(texture_matrices, strokes);
3840
3841 changed.store(true, std::memory_order_relaxed);
3842 });
3843
3844 if (changed) {
3845 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
3846 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
3847 }
3848
3850}
3851
3853 wmOperator *op,
3854 const wmEvent *event)
3855{
3857
3858 /* Check for mouse release. */
3859 if ((ret & OPERATOR_RUNNING_MODAL) != 0 && event->type == LEFTMOUSE && event->val == KM_RELEASE)
3860 {
3864 }
3865
3866 return ret;
3867}
3868
3870 wmOperator *op,
3871 const wmEvent *event)
3872{
3873 /* Invoke interactive line drawing (representing the gradient) in viewport. */
3875
3876 if ((ret & OPERATOR_RUNNING_MODAL) != 0) {
3877 ARegion *region = CTX_wm_region(C);
3878 if (region->regiontype == RGN_TYPE_WINDOW && event->type == LEFTMOUSE &&
3879 event->val == KM_PRESS)
3880 {
3881 wmGesture *gesture = static_cast<wmGesture *>(op->customdata);
3882 gesture->is_active = true;
3883 }
3884 }
3885
3886 return ret;
3887}
3888
3890{
3891 /* Identifiers. */
3892 ot->name = "Texture Gradient";
3893 ot->idname = "GREASE_PENCIL_OT_texture_gradient";
3894 ot->description = "Draw a line to set the fill material gradient for the selected strokes";
3895
3896 /* API callbacks. */
3902
3903 /* Flags. */
3904 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3905
3907}
3908
3910
3911/* -------------------------------------------------------------------- */
3914
3916{
3917 const Scene *scene = CTX_data_scene(C);
3918 Object *object = CTX_data_active_object(C);
3919 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
3920
3921 const CurveType dst_type = CurveType(RNA_enum_get(op->ptr, "type"));
3922 const bool use_handles = RNA_boolean_get(op->ptr, "use_handles");
3923
3924 bool changed = false;
3925 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
3926 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3928 IndexMaskMemory memory;
3930 *object, info.drawing, info.layer_index, memory);
3931 if (strokes.is_empty()) {
3932 return;
3933 }
3934
3936 options.convert_bezier_handles_to_poly_points = use_handles;
3937 options.convert_bezier_handles_to_catmull_rom_points = use_handles;
3938 options.keep_bezier_shape_as_nurbs = use_handles;
3939 options.keep_catmull_rom_shape_as_nurbs = use_handles;
3940
3941 curves = geometry::convert_curves(curves, strokes, dst_type, {}, options);
3943
3944 changed = true;
3945 });
3946
3947 if (changed) {
3948 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
3949 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
3950 }
3951
3952 return OPERATOR_FINISHED;
3953}
3954
3956{
3957 ot->name = "Set Curve Type";
3958 ot->idname = "GREASE_PENCIL_OT_set_curve_type";
3959 ot->description = "Set type of selected curves";
3960
3961 ot->invoke = WM_menu_invoke;
3964
3965 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3966
3967 ot->prop = RNA_def_enum(
3968 ot->srna, "type", rna_enum_curves_type_items, CURVE_TYPE_POLY, "Type", "Curve type");
3969
3970 RNA_def_boolean(ot->srna,
3971 "use_handles",
3972 false,
3973 "Handles",
3974 "Take handle information into account in the conversion");
3975}
3976
3978
3979/* -------------------------------------------------------------------- */
3982
3984{
3985 const Scene *scene = CTX_data_scene(C);
3986 Object *object = CTX_data_active_object(C);
3987 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
3988
3989 const HandleType dst_handle_type = HandleType(RNA_enum_get(op->ptr, "type"));
3990
3991 bool changed = false;
3992 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
3993 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3995 if (!curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
3996 return;
3997 }
3998 IndexMaskMemory memory;
4000 *object, info.drawing, info.layer_index, memory);
4001 const IndexMask bezier_curves = curves.indices_for_curve_type(
4002 CURVE_TYPE_BEZIER, editable_strokes, memory);
4003
4004 const bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
4005 const VArraySpan<bool> selection = *attributes.lookup_or_default<bool>(
4006 ".selection", bke::AttrDomain::Point, true);
4007 const VArraySpan<bool> selection_left = *attributes.lookup_or_default<bool>(
4008 ".selection_handle_left", bke::AttrDomain::Point, true);
4009 const VArraySpan<bool> selection_right = *attributes.lookup_or_default<bool>(
4010 ".selection_handle_right", bke::AttrDomain::Point, true);
4011
4012 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
4013 MutableSpan<int8_t> handle_types_left = curves.handle_types_left_for_write();
4014 MutableSpan<int8_t> handle_types_right = curves.handle_types_right_for_write();
4015 bezier_curves.foreach_index(GrainSize(256), [&](const int curve_i) {
4016 const IndexRange points = points_by_curve[curve_i];
4017 for (const int point_i : points) {
4018 if (selection_left[point_i] || selection[point_i]) {
4019 handle_types_left[point_i] = int8_t(dst_handle_type);
4020 }
4021 if (selection_right[point_i] || selection[point_i]) {
4022 handle_types_right[point_i] = int8_t(dst_handle_type);
4023 }
4024 }
4025 });
4026
4027 curves.calculate_bezier_auto_handles();
4028 curves.tag_topology_changed();
4030
4031 changed = true;
4032 });
4033
4034 if (changed) {
4035 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4036 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4037 }
4038
4039 return OPERATOR_FINISHED;
4040}
4041
4043{
4044 ot->name = "Set Handle Type";
4045 ot->idname = "GREASE_PENCIL_OT_set_handle_type";
4046 ot->description = "Set the handle type for bezier curves";
4047
4048 ot->invoke = WM_menu_invoke;
4051
4052 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4053
4054 ot->prop = RNA_def_enum(
4055 ot->srna, "type", rna_enum_curves_handle_type_items, CURVE_TYPE_POLY, "Type", nullptr);
4056}
4057
4059
4060/* -------------------------------------------------------------------- */
4063
4065{
4066 const Scene *scene = CTX_data_scene(C);
4067 Object *object = CTX_data_active_object(C);
4068 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
4069
4070 const int resolution = RNA_int_get(op->ptr, "resolution");
4071
4072 bool changed = false;
4073 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
4074 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4076 IndexMaskMemory memory;
4078 *object, info.drawing, info.layer_index, memory);
4079 if (editable_strokes.is_empty()) {
4080 return;
4081 }
4082
4083 if (curves.is_single_type(CURVE_TYPE_POLY)) {
4084 return;
4085 }
4086
4087 index_mask::masked_fill(curves.resolution_for_write(), resolution, editable_strokes);
4089 changed = true;
4090 });
4091
4092 if (changed) {
4093 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4094 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4095 }
4096
4097 return OPERATOR_FINISHED;
4098}
4099
4101{
4102 ot->name = "Set Curve Resolution";
4103 ot->idname = "GREASE_PENCIL_OT_set_curve_resolution";
4104 ot->description = "Set resolution of selected curves";
4105
4108
4109 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4110
4111 RNA_def_int(ot->srna,
4112 "resolution",
4113 12,
4114 0,
4115 10000,
4116 "Resolution",
4117 "The resolution to use for each curve segment",
4118 1,
4119 64);
4120}
4121
4123
4124/* -------------------------------------------------------------------- */
4127
4129{
4130 const Scene *scene = CTX_data_scene(C);
4131 Object *object = CTX_data_active_object(C);
4132 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
4133
4134 bool changed = false;
4135 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
4136 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4138 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
4139 IndexMaskMemory memory;
4141 *object, info.drawing, info.layer_index, memory);
4142 if (editable_strokes.is_empty()) {
4143 return;
4144 }
4145
4146 if (attributes.contains("uv_rotation")) {
4147 if (editable_strokes.size() == curves.curves_num()) {
4148 attributes.remove("uv_rotation");
4149 }
4150 else {
4151 bke::SpanAttributeWriter<float> uv_rotations = attributes.lookup_for_write_span<float>(
4152 "uv_rotation");
4153 index_mask::masked_fill(uv_rotations.span, 0.0f, editable_strokes);
4154 uv_rotations.finish();
4155 }
4156 }
4157
4158 if (attributes.contains("uv_translation")) {
4159 if (editable_strokes.size() == curves.curves_num()) {
4160 attributes.remove("uv_translation");
4161 }
4162 else {
4163 bke::SpanAttributeWriter<float2> uv_translations =
4164 attributes.lookup_for_write_span<float2>("uv_translation");
4165 index_mask::masked_fill(uv_translations.span, float2(0.0f, 0.0f), editable_strokes);
4166 uv_translations.finish();
4167 }
4168 }
4169
4170 if (attributes.contains("uv_scale")) {
4171 if (editable_strokes.size() == curves.curves_num()) {
4172 attributes.remove("uv_scale");
4173 }
4174 else {
4176 "uv_scale");
4177 index_mask::masked_fill(uv_scales.span, float2(1.0f, 1.0f), editable_strokes);
4178 uv_scales.finish();
4179 }
4180 }
4181
4182 if (attributes.contains("uv_shear")) {
4183 if (editable_strokes.size() == curves.curves_num()) {
4184 attributes.remove("uv_shear");
4185 }
4186 else {
4187 bke::SpanAttributeWriter<float> uv_shears = attributes.lookup_for_write_span<float>(
4188 "uv_shear");
4189 index_mask::masked_fill(uv_shears.span, 0.0f, editable_strokes);
4190 uv_shears.finish();
4191 }
4192 }
4193
4195 changed = true;
4196 });
4197
4198 if (changed) {
4199 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4200 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4201 }
4202
4203 return OPERATOR_FINISHED;
4204}
4205
4207{
4208 /* Identifiers. */
4209 ot->name = "Reset UVs";
4210 ot->idname = "GREASE_PENCIL_OT_reset_uvs";
4211 ot->description = "Reset UV transformation to default values";
4212
4213 /* Callbacks. */
4216
4217 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4218}
4219
4221{
4222 const Scene &scene = *CTX_data_scene(C);
4223 Object &object = *CTX_data_active_object(C);
4224 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
4225 std::atomic<bool> changed = false;
4226
4227 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
4228 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4229 IndexMaskMemory memory;
4230 const IndexMask selected_points =
4232 object, info.drawing, info.layer_index, memory);
4233
4234 if (selected_points.is_empty()) {
4235 return;
4236 }
4237
4239 selected_points);
4241 changed.store(true, std::memory_order_relaxed);
4242 });
4243
4244 if (changed) {
4245 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4246 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4247 return OPERATOR_FINISHED;
4248 }
4249
4250 return OPERATOR_CANCELLED;
4251}
4252
4254{
4255 /* Identifiers. */
4256 ot->name = "Split stroke";
4257 ot->idname = "GREASE_PENCIL_OT_stroke_split";
4258 ot->description = "Split selected points to a new stroke";
4259
4260 /* Callbacks. */
4263
4264 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4265}
4266
4268
4269/* -------------------------------------------------------------------- */
4272
4273enum class RemoveFillGuidesMode : int8_t { ActiveFrame = 0, AllFrames = 1 };
4274
4276{
4277 using namespace blender::bke::greasepencil;
4278 const Scene &scene = *CTX_data_scene(C);
4279 Object &object = *CTX_data_active_object(C);
4280 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
4281
4282 const RemoveFillGuidesMode mode = RemoveFillGuidesMode(RNA_enum_get(op->ptr, "mode"));
4283
4284 std::atomic<bool> changed = false;
4287 for (const int layer_i : grease_pencil.layers().index_range()) {
4288 const Layer &layer = grease_pencil.layer(layer_i);
4289 if (!layer.is_editable()) {
4290 continue;
4291 }
4292 if (Drawing *drawing = grease_pencil.get_editable_drawing_at(layer, scene.r.cfra)) {
4293 drawings.append({*drawing, layer_i, scene.r.cfra, 1.0f});
4294 }
4295 }
4296 }
4297 else if (mode == RemoveFillGuidesMode::AllFrames) {
4298 for (const int layer_i : grease_pencil.layers().index_range()) {
4299 const Layer &layer = grease_pencil.layer(layer_i);
4300 if (!layer.is_editable()) {
4301 continue;
4302 }
4303 for (const auto [frame_number, frame] : layer.frames().items()) {
4304 if (Drawing *drawing = grease_pencil.get_editable_drawing_at(layer, frame_number)) {
4305 drawings.append({*drawing, layer_i, frame_number, 1.0f});
4306 }
4307 }
4308 }
4309 }
4310 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4313 changed.store(true, std::memory_order_relaxed);
4314 }
4315 });
4316
4317 if (changed) {
4318 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4319 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4320 return OPERATOR_FINISHED;
4321 }
4322
4323 return OPERATOR_CANCELLED;
4324}
4325
4327{
4328 static const EnumPropertyItem rna_mode_items[] = {
4329 {int(RemoveFillGuidesMode::ActiveFrame), "ACTIVE_FRAME", 0, "Active Frame", ""},
4330 {int(RemoveFillGuidesMode::AllFrames), "ALL_FRAMES", 0, "All Frames", ""},
4331 {0, nullptr, 0, nullptr, nullptr},
4332 };
4333
4334 /* Identifiers. */
4335 ot->name = "Remove Fill Guides";
4336 ot->idname = "GREASE_PENCIL_OT_remove_fill_guides";
4337 ot->description = "Remove all the strokes that were created from the fill tool as guides";
4338
4339 /* Callbacks. */
4342
4343 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4344
4345 ot->prop = RNA_def_enum(
4346 ot->srna, "mode", rna_mode_items, int(RemoveFillGuidesMode::AllFrames), "Mode", "");
4347}
4348
4349/* -------------------------------------------------------------------- */
4352
4353enum class OutlineMode : int8_t {
4354 View = 0,
4356 Side = 2,
4357 Top = 3,
4360};
4361
4363 {int(OutlineMode::View), "VIEW", 0, "View", ""},
4364 {int(OutlineMode::Front), "FRONT", 0, "Front", ""},
4365 {int(OutlineMode::Side), "SIDE", 0, "Side", ""},
4366 {int(OutlineMode::Top), "TOP", 0, "Top", ""},
4367 {int(OutlineMode::Cursor), "CURSOR", 0, "Cursor", ""},
4368 {int(OutlineMode::Camera), "CAMERA", 0, "Camera", ""},
4369 {0, nullptr, 0, nullptr, nullptr},
4370};
4371
4373{
4375
4376 const Scene *scene = CTX_data_scene(C);
4377 Object *object = CTX_data_active_object(C);
4378 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
4379
4380 const float radius = RNA_float_get(op->ptr, "radius");
4381 const float offset_factor = RNA_float_get(op->ptr, "offset_factor");
4382 const int corner_subdivisions = RNA_int_get(op->ptr, "corner_subdivisions");
4383 const float outline_offset = radius * offset_factor;
4384 const int mat_nr = -1;
4385
4386 const OutlineMode mode = OutlineMode(RNA_enum_get(op->ptr, "type"));
4387
4388 float4x4 viewinv = float4x4::identity();
4389 switch (mode) {
4390 case OutlineMode::View: {
4392 viewinv = float4x4(rv3d->viewmat);
4393 break;
4394 }
4395 case OutlineMode::Front:
4396 viewinv = float4x4({1.0f, 0.0f, 0.0f, 0.0f},
4397 {0.0f, 0.0f, 1.0f, 0.0f},
4398 {0.0f, 1.0f, 0.0f, 0.0f},
4399 {0.0f, 0.0f, 0.0f, 1.0f});
4400 break;
4401 case OutlineMode::Side:
4402 viewinv = float4x4({0.0f, 0.0f, 1.0f, 0.0f},
4403 {0.0f, 1.0f, 0.0f, 0.0f},
4404 {1.0f, 0.0f, 0.0f, 0.0f},
4405 {0.0f, 0.0f, 0.0f, 1.0f});
4406 break;
4407 case OutlineMode::Top:
4408 viewinv = float4x4::identity();
4409 break;
4410 case OutlineMode::Cursor: {
4411 viewinv = scene->cursor.matrix<float4x4>();
4412 break;
4413 }
4415 viewinv = scene->camera->world_to_object();
4416 break;
4417 default:
4419 break;
4420 }
4421
4422 bool changed = false;
4423 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
4424 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4425 IndexMaskMemory memory;
4427 *object, info.drawing, info.layer_index, memory);
4428 if (editable_strokes.is_empty()) {
4429 return;
4430 }
4431
4432 const Layer &layer = grease_pencil.layer(info.layer_index);
4433 const float4x4 viewmat = viewinv * layer.to_world_space(*object);
4434
4436 editable_strokes,
4437 viewmat,
4438 corner_subdivisions,
4439 radius,
4440 outline_offset,
4441 mat_nr);
4442
4443 info.drawing.strokes_for_write().remove_curves(editable_strokes, {});
4444
4445 /* Join the outline stroke into the drawing. */
4446 Curves *strokes = bke::curves_new_nomain(std::move(outline));
4447
4448 Curves *other_curves = bke::curves_new_nomain(std::move(info.drawing.strokes_for_write()));
4449 const std::array<bke::GeometrySet, 2> geometry_sets = {
4451
4452 info.drawing.strokes_for_write() = std::move(
4453 geometry::join_geometries(geometry_sets, {}).get_curves_for_write()->geometry.wrap());
4454
4456 changed = true;
4457 });
4458
4459 if (changed) {
4460 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4461 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4462 }
4463
4464 return OPERATOR_FINISHED;
4465}
4466
4468{
4469 /* Identifiers. */
4470 ot->name = "Outline";
4471 ot->idname = "GREASE_PENCIL_OT_outline";
4472 ot->description = "Convert selected strokes to perimeter";
4473
4474 /* Callbacks. */
4477
4478 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4479
4480 /* Properties */
4481 ot->prop = RNA_def_enum(
4482 ot->srna, "type", prop_outline_modes, int(OutlineMode::View), "Projection Mode", "");
4483 RNA_def_float_distance(ot->srna, "radius", 0.01f, 0.0f, 10.0f, "Radius", "", 0.0f, 10.0f);
4485 ot->srna, "offset_factor", -1.0f, -1.0f, 1.0f, "Offset Factor", "", -1.0f, 1.0f);
4486 RNA_def_int(ot->srna, "corner_subdivisions", 2, 0, 10, "Corner Subdivisions", "", 0, 5);
4487}
4488
4490
4491/* -------------------------------------------------------------------- */
4494
4496 const IndexMask &selection,
4497 const float threshold)
4498{
4499 const VArray<float> thresholds = VArray<float>::ForSingle(threshold, curves.curves_num());
4500 /* TODO: Detect or manually provide corners. */
4501 const VArray<bool> corners = VArray<bool>::ForSingle(false, curves.points_num());
4503 curves, selection, thresholds, corners, geometry::FitMethod::Refit, {});
4504}
4505
4507 const IndexMask &selection,
4508 const float threshold)
4509{
4510 if (curves.is_single_type(CURVE_TYPE_CATMULL_ROM)) {
4511 return;
4512 }
4513 IndexMaskMemory memory;
4514 const IndexMask non_catmull_rom_curves_selection =
4515 curves.indices_for_curve_type(CURVE_TYPE_CATMULL_ROM, selection, memory)
4516 .complement(selection, memory);
4517 BLI_assert(!non_catmull_rom_curves_selection.is_empty());
4518 curves = geometry::resample_to_evaluated(curves, non_catmull_rom_curves_selection);
4519
4520 /* To avoid having too many control points, simplify the position attribute based on the
4521 * threshold. This doesn't replace an actual curve fitting (which would be better), but
4522 * is a decent approximation for the meantime. */
4523 const IndexMask points_to_remove = geometry::simplify_curve_attribute(
4524 curves.positions(),
4525 non_catmull_rom_curves_selection,
4526 curves.points_by_curve(),
4527 curves.cyclic(),
4528 threshold,
4529 curves.positions(),
4530 memory);
4531 curves.remove_points(points_to_remove, {});
4532
4534 options.convert_bezier_handles_to_poly_points = false;
4535 options.convert_bezier_handles_to_catmull_rom_points = false;
4536 options.keep_bezier_shape_as_nurbs = true;
4537 options.keep_catmull_rom_shape_as_nurbs = true;
4539 curves, non_catmull_rom_curves_selection, CURVE_TYPE_CATMULL_ROM, {}, options);
4540}
4541
4543{
4544 if (curves.is_single_type(CURVE_TYPE_POLY)) {
4545 return;
4546 }
4547 IndexMaskMemory memory;
4548 const IndexMask non_poly_curves_selection = curves
4549 .indices_for_curve_type(
4550 CURVE_TYPE_POLY, selection, memory)
4551 .complement(selection, memory);
4552 BLI_assert(!non_poly_curves_selection.is_empty());
4553 curves = geometry::resample_to_evaluated(curves, non_poly_curves_selection);
4554}
4555
4557 const IndexMask &selection,
4558 const float threshold)
4559{
4560 if (curves.is_single_type(CURVE_TYPE_BEZIER)) {
4561 return;
4562 }
4563 IndexMaskMemory memory;
4564 const IndexMask poly_curves_selection = curves.indices_for_curve_type(
4565 CURVE_TYPE_POLY, selection, memory);
4566 if (!poly_curves_selection.is_empty()) {
4567 curves = fit_poly_curves(curves, poly_curves_selection, threshold);
4568 }
4569
4571 options.convert_bezier_handles_to_poly_points = false;
4572 options.convert_bezier_handles_to_catmull_rom_points = false;
4573 options.keep_bezier_shape_as_nurbs = true;
4574 options.keep_catmull_rom_shape_as_nurbs = true;
4576}
4577
4579 const IndexMask &selection,
4580 const float threshold)
4581{
4582 if (curves.is_single_type(CURVE_TYPE_NURBS)) {
4583 return;
4584 }
4585
4586 IndexMaskMemory memory;
4587 const IndexMask poly_curves_selection = curves.indices_for_curve_type(
4588 CURVE_TYPE_POLY, selection, memory);
4589 if (!poly_curves_selection.is_empty()) {
4590 curves = fit_poly_curves(curves, poly_curves_selection, threshold);
4591 }
4592
4594 options.convert_bezier_handles_to_poly_points = false;
4595 options.convert_bezier_handles_to_catmull_rom_points = false;
4596 options.keep_bezier_shape_as_nurbs = true;
4597 options.keep_catmull_rom_shape_as_nurbs = true;
4599}
4600
4602{
4603 const Scene *scene = CTX_data_scene(C);
4604 Object *object = CTX_data_active_object(C);
4605 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
4606
4607 const CurveType dst_type = CurveType(RNA_enum_get(op->ptr, "type"));
4608 const float threshold = RNA_float_get(op->ptr, "threshold");
4609
4610 std::atomic<bool> changed = false;
4611 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
4612 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4614 IndexMaskMemory memory;
4616 *object, info.drawing, info.layer_index, memory);
4617 if (strokes.is_empty()) {
4618 return;
4619 }
4620
4621 switch (dst_type) {
4623 convert_to_catmull_rom(curves, strokes, threshold);
4624 break;
4625 case CURVE_TYPE_POLY:
4626 convert_to_poly(curves, strokes);
4627 break;
4628 case CURVE_TYPE_BEZIER:
4629 convert_to_bezier(curves, strokes, threshold);
4630 break;
4631 case CURVE_TYPE_NURBS:
4632 convert_to_nurbs(curves, strokes, threshold);
4633 break;
4634 }
4635
4637 changed.store(true, std::memory_order_relaxed);
4638 });
4639
4640 if (changed) {
4641 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4642 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4643 }
4644
4645 return OPERATOR_FINISHED;
4646}
4647
4649{
4650 uiLayout *layout = op->layout;
4652
4654
4655 uiLayoutSetPropSep(layout, true);
4656 uiLayoutSetPropDecorate(layout, false);
4657
4658 layout->prop(&ptr, "type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
4659
4660 const CurveType dst_type = CurveType(RNA_enum_get(op->ptr, "type"));
4661
4662 if (dst_type == CURVE_TYPE_POLY) {
4663 return;
4664 }
4665
4666 layout->prop(&ptr, "threshold", UI_ITEM_NONE, std::nullopt, ICON_NONE);
4667}
4668
4670{
4671 ot->name = "Convert Curve Type";
4672 ot->idname = "GREASE_PENCIL_OT_convert_curve_type";
4673 ot->description = "Convert type of selected curves";
4674
4675 ot->invoke = WM_menu_invoke;
4679
4680 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4681
4682 ot->prop = RNA_def_enum(
4683 ot->srna, "type", rna_enum_curves_type_items, CURVE_TYPE_POLY, "Type", "");
4685
4686 PropertyRNA *prop = RNA_def_float(
4687 ot->srna,
4688 "threshold",
4689 0.01f,
4690 0.0f,
4691 100.0f,
4692 "Threshold",
4693 "The distance that the resulting points are allowed to be within",
4694 0.0f,
4695 100.0f);
4697}
4698
4700
4701} // namespace blender::ed::greasepencil
4702
4704{
4705 using namespace blender::ed::greasepencil;
4744}
4745
4746/* -------------------------------------------------------------------- */
4749
4750namespace blender::ed::greasepencil {
4751
4752/* Note: the `duplicate_layer` API would be nicer, but only supports duplicating groups from the
4753 * same datablock. */
4756 const bke::greasepencil::Layer &layer_src)
4757{
4758 using namespace blender::bke::greasepencil;
4759
4760 Layer &layer_dst = grease_pencil_dst.add_layer(group_dst, layer_src.name());
4761 BKE_grease_pencil_copy_layer_parameters(layer_src, layer_dst);
4762
4763 layer_dst.frames_for_write() = layer_src.frames();
4764 layer_dst.tag_frames_map_changed();
4765
4766 return layer_dst;
4767}
4768
4770 GreasePencil &grease_pencil_dst,
4772 const bke::greasepencil::LayerGroup &group_src,
4773 Map<StringRefNull, StringRefNull> &layer_name_map);
4774
4775static void copy_layer_group_content(GreasePencil &grease_pencil_dst,
4777 const bke::greasepencil::LayerGroup &group_src,
4778 Map<StringRefNull, StringRefNull> &layer_name_map)
4779{
4780 using namespace blender::bke::greasepencil;
4781
4782 LISTBASE_FOREACH (GreasePencilLayerTreeNode *, child, &group_src.children) {
4783 switch (child->type) {
4784 case GP_LAYER_TREE_LEAF: {
4785 Layer &layer_src = reinterpret_cast<GreasePencilLayer *>(child)->wrap();
4786 Layer &layer_dst = copy_layer(grease_pencil_dst, group_dst, layer_src);
4787 layer_name_map.add_new(layer_src.name(), layer_dst.name());
4788 break;
4789 }
4790 case GP_LAYER_TREE_GROUP: {
4791 LayerGroup &group_src = reinterpret_cast<GreasePencilLayerTreeGroup *>(child)->wrap();
4792 copy_layer_group_recursive(grease_pencil_dst, group_dst, group_src, layer_name_map);
4793 break;
4794 }
4795 }
4796 }
4797}
4798
4800 GreasePencil &grease_pencil_dst,
4802 const bke::greasepencil::LayerGroup &group_src,
4803 Map<StringRefNull, StringRefNull> &layer_name_map)
4804{
4805 bke::greasepencil::LayerGroup &group_dst = grease_pencil_dst.add_layer_group(
4806 parent_dst, group_src.base.name);
4808
4809 copy_layer_group_content(grease_pencil_dst, group_dst, group_src, layer_name_map);
4810 return group_dst;
4811}
4812
4814{
4815 BLI_assert(object.type == OB_GREASE_PENCIL);
4816 Array<int> material_index_map(*BKE_object_material_len_p(&object));
4817 for (const int i : material_index_map.index_range()) {
4818 Material *material = BKE_object_material_get(&object, i + 1);
4819 if (material != nullptr) {
4820 material_index_map[i] = materials.index_of_or_add(material);
4821 }
4822 else {
4823 material_index_map[i] = 0;
4824 }
4825 }
4826 return material_index_map;
4827}
4828
4830 const Span<int> material_index_map)
4831{
4833 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
4834 /* Validate material indices and add missing materials. */
4835 bke::SpanAttributeWriter<int> material_writer = attributes.lookup_or_add_for_write_span<int>(
4836 "material_index", bke::AttrDomain::Curve);
4837 threading::parallel_for(curves.curves_range(), 1024, [&](const IndexRange range) {
4838 for (const int curve_i : range) {
4839 material_writer.span[curve_i] = material_index_map[material_writer.span[curve_i]];
4840 }
4841 });
4842 material_writer.finish();
4843}
4844
4846 GreasePencil &grease_pencil,
4847 const ListBase &vertex_group_names)
4848{
4849 Map<StringRefNull, StringRefNull> vertex_group_map;
4850 LISTBASE_FOREACH (bDeformGroup *, dg, &vertex_group_names) {
4851 bDeformGroup *vgroup = static_cast<bDeformGroup *>(MEM_dupallocN(dg));
4852 BKE_object_defgroup_unique_name(vgroup, &object);
4853 BLI_addtail(&grease_pencil.vertex_group_names, vgroup);
4854 vertex_group_map.add_new(dg->name, vgroup->name);
4855 }
4856 return vertex_group_map;
4857}
4858
4860 const Map<StringRefNull, StringRefNull> &vertex_group_map)
4861{
4863 STRNCPY(dg->name, vertex_group_map.lookup(dg->name).c_str());
4864 }
4865
4866 /* Indices in vertex weights remain valid, they are local to the drawing's vertex groups.
4867 * Only the names of the groups change. */
4868}
4869
4871 Object &ob_src,
4872 Object &ob_dst,
4873 VectorSet<Material *> &materials)
4874{
4875 using namespace blender::bke::greasepencil;
4876
4877 /* Skip if the datablock is already used by the active object. */
4878 if (ob_src.data == ob_dst.data) {
4879 return;
4880 }
4881
4882 BLI_assert(ob_src.type == OB_GREASE_PENCIL);
4883 BLI_assert(ob_dst.type == OB_GREASE_PENCIL);
4884 GreasePencil &grease_pencil_src = *static_cast<GreasePencil *>(ob_src.data);
4885 GreasePencil &grease_pencil_dst = *static_cast<GreasePencil *>(ob_dst.data);
4886 /* Number of existing layers that don't need to be updated. */
4887 const int orig_layers_num = grease_pencil_dst.layers().size();
4888
4890 ob_dst, grease_pencil_dst, grease_pencil_src.vertex_group_names);
4891 const Array<int> material_index_map = add_materials_to_map(ob_src, materials);
4892
4893 /* Concatenate drawing arrays. Existing drawings in dst keep their position, new drawings are
4894 * mapped to the new index range. */
4895 const int new_drawing_array_num = grease_pencil_dst.drawing_array_num +
4896 grease_pencil_src.drawing_array_num;
4897 GreasePencilDrawingBase **new_drawing_array = static_cast<GreasePencilDrawingBase **>(
4898 MEM_malloc_arrayN(new_drawing_array_num, sizeof(GreasePencilDrawingBase *), __func__));
4899 MutableSpan<GreasePencilDrawingBase *> new_drawings = {new_drawing_array, new_drawing_array_num};
4900 const IndexRange new_drawings_dst = IndexRange::from_begin_size(
4901 0, grease_pencil_dst.drawing_array_num);
4902 const IndexRange new_drawings_src = IndexRange::from_begin_size(
4903 grease_pencil_dst.drawing_array_num, grease_pencil_src.drawing_array_num);
4904
4905 copy_drawing_array(grease_pencil_dst.drawings(), new_drawings.slice(new_drawings_dst));
4906 copy_drawing_array(grease_pencil_src.drawings(), new_drawings.slice(new_drawings_src));
4907
4908 /* Free existing drawings array. */
4909 grease_pencil_dst.resize_drawings(0);
4910 grease_pencil_dst.drawing_array = new_drawing_array;
4911 grease_pencil_dst.drawing_array_num = new_drawing_array_num;
4912
4913 /* Maps original names of source layers to new unique layer names. */
4914 Map<StringRefNull, StringRefNull> layer_name_map;
4915 /* Only copy the content of the root group, not the root node itself. */
4916 copy_layer_group_content(grease_pencil_dst,
4917 grease_pencil_dst.root_group(),
4918 grease_pencil_src.root_group(),
4919 layer_name_map);
4920
4921 /* Copy custom attributes for new layers. */
4922 CustomData_merge_layout(&grease_pencil_src.layers_data,
4923 &grease_pencil_dst.layers_data,
4926 grease_pencil_dst.layers().size());
4927 CustomData_copy_data(&grease_pencil_src.layers_data,
4928 &grease_pencil_dst.layers_data,
4929 0,
4930 orig_layers_num,
4931 grease_pencil_src.layers().size());
4932
4933 /* Fix names, indices and transforms to keep relationships valid. */
4934 for (const int layer_index : grease_pencil_dst.layers().index_range()) {
4935 Layer &layer = *grease_pencil_dst.layers_for_write()[layer_index];
4936 const bool is_orig_layer = (layer_index < orig_layers_num);
4937 const float4x4 old_layer_to_world = (is_orig_layer ? layer.to_world_space(ob_dst) :
4938 layer.to_world_space(ob_src));
4939
4940 /* Update newly added layers. */
4941 if (!is_orig_layer) {
4942 /* Update name references for masks. */
4943 LISTBASE_FOREACH (GreasePencilLayerMask *, dst_mask, &layer.masks) {
4944 const StringRefNull *new_mask_name = layer_name_map.lookup_ptr(dst_mask->layer_name);
4945 if (new_mask_name) {
4946 MEM_SAFE_FREE(dst_mask->layer_name);
4947 dst_mask->layer_name = BLI_strdup(new_mask_name->c_str());
4948 }
4949 }
4950 /* Shift drawing indices to match the new drawings array. */
4951 for (const int key : layer.frames_for_write().keys()) {
4952 int &drawing_index = layer.frames_for_write().lookup(key).drawing_index;
4953 drawing_index = new_drawings_src[drawing_index];
4954 }
4955 }
4956
4957 /* Layer parent object may become invalid. This can be an original layer pointing at the joined
4958 * object which gets destroyed, or a new layer that points at the target object which is now
4959 * its owner. */
4960 if (ELEM(layer.parent, &ob_dst, &ob_src)) {
4961 layer.parent = nullptr;
4962 }
4963
4964 /* Apply relative object transform to new drawings to keep world-space positions unchanged.
4965 * Be careful where the matrix is computed: changing the parent pointer (above) can affect
4966 * this! */
4967 const float4x4 new_layer_to_world = layer.to_world_space(ob_dst);
4968 for (const int key : layer.frames_for_write().keys()) {
4969 const int drawing_index = layer.frames_for_write().lookup(key).drawing_index;
4970 GreasePencilDrawingBase *drawing_base = grease_pencil_dst.drawings()[drawing_index];
4971 if (drawing_base->type != GP_DRAWING) {
4972 continue;
4973 }
4974 Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base)->wrap();
4976 curves.transform(math::invert(new_layer_to_world) * old_layer_to_world);
4977
4978 if (!is_orig_layer) {
4979 remap_vertex_groups(drawing, vertex_group_map);
4980 remap_material_indices(drawing, material_index_map);
4981 }
4982 }
4983 }
4984
4985 /* Rename animation paths to layers. */
4986 BKE_fcurves_main_cb(&bmain, [&](ID *id, FCurve *fcu) {
4987 if (id == &grease_pencil_src.id && fcu->rna_path && strstr(fcu->rna_path, "layers[")) {
4988 /* Have to use linear search, the layer name map only contains sub-strings of RNA paths. */
4989 for (auto [name_src, name_dst] : layer_name_map.items()) {
4990 if (name_dst != name_src) {
4991 const char *old_path = fcu->rna_path;
4993 id, fcu->rna_path, "layers", name_src.c_str(), name_dst.c_str(), 0, 0, false);
4994 if (old_path != fcu->rna_path) {
4995 /* Stop after first match. */
4996 break;
4997 }
4998 }
4999 }
5000 }
5001 /* Fix driver targets. */
5002 if (fcu->driver) {
5003 LISTBASE_FOREACH (DriverVar *, dvar, &fcu->driver->variables) {
5004 /* Only change the used targets, since the others will need fixing manually anyway. */
5006 if (dtar->id != &grease_pencil_src.id) {
5007 continue;
5008 }
5009 dtar->id = &grease_pencil_dst.id;
5010
5011 if (dtar->rna_path && strstr(dtar->rna_path, "layers[")) {
5012 for (auto [name_src, name_dst] : layer_name_map.items()) {
5013 if (name_dst != name_src) {
5014 const char *old_path = fcu->rna_path;
5015 dtar->rna_path = BKE_animsys_fix_rna_path_rename(
5016 id, dtar->rna_path, "layers", name_src.c_str(), name_dst.c_str(), 0, 0, false);
5017 if (old_path != dtar->rna_path) {
5018 break;
5019 }
5020 }
5021 }
5022 }
5023 }
5025 }
5026 }
5027 });
5028
5029 /* Merge animation data of objects and grease pencil datablocks. */
5030 if (ob_src.adt) {
5031 if (ob_dst.adt == nullptr) {
5032 ob_dst.adt = BKE_animdata_copy(&bmain, ob_src.adt, 0);
5033 }
5034 else {
5035 BKE_animdata_merge_copy(&bmain, &ob_dst.id, &ob_src.id, ADT_MERGECOPY_KEEP_DST, false);
5036 }
5037
5038 if (ob_dst.adt->action) {
5040 }
5041 }
5042 if (grease_pencil_src.adt) {
5043 if (grease_pencil_dst.adt == nullptr) {
5044 grease_pencil_dst.adt = BKE_animdata_copy(&bmain, grease_pencil_src.adt, 0);
5045 }
5046 else {
5048 &bmain, &grease_pencil_dst.id, &grease_pencil_src.id, ADT_MERGECOPY_KEEP_DST, false);
5049 }
5050
5051 if (grease_pencil_dst.adt->action) {
5053 }
5054 }
5055}
5056
5057} // namespace blender::ed::greasepencil
5058
5060{
5061 Main *bmain = CTX_data_main(C);
5062 Scene *scene = CTX_data_scene(C);
5063 Object *ob_active = CTX_data_active_object(C);
5064
5065 /* Ensure we're in right mode and that the active object is correct. */
5066 if (!ob_active || ob_active->type != OB_GREASE_PENCIL) {
5067 return OPERATOR_CANCELLED;
5068 }
5069
5070 bool ok = false;
5071 CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) {
5072 if (ob_iter == ob_active) {
5073 ok = true;
5074 break;
5075 }
5076 }
5078 /* Active object must always selected. */
5079 if (ok == false) {
5080 BKE_report(op->reports, RPT_WARNING, "Active object is not a selected Grease Pencil");
5081 return OPERATOR_CANCELLED;
5082 }
5083
5084 Object *ob_dst = ob_active;
5085 GreasePencil *grease_pencil_dst = static_cast<GreasePencil *>(ob_dst->data);
5086
5089 *ob_dst, materials);
5090 /* Reassign material indices in the original layers, in case materials are deduplicated. */
5091 for (GreasePencilDrawingBase *drawing_base : grease_pencil_dst->drawings()) {
5092 if (drawing_base->type != GP_DRAWING) {
5093 continue;
5094 }
5096 reinterpret_cast<GreasePencilDrawing *>(drawing_base)->wrap();
5097 blender::ed::greasepencil::remap_material_indices(drawing, material_index_map);
5098 }
5099
5100 /* Loop and join all data. */
5101 CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) {
5102 if (ob_iter->type != OB_GREASE_PENCIL || ob_iter == ob_active) {
5103 continue;
5104 }
5105
5106 blender::ed::greasepencil::join_object_with_active(*bmain, *ob_iter, *ob_dst, materials);
5107
5108 /* Free the old object. */
5109 blender::ed::object::base_free_and_unlink(bmain, scene, ob_iter);
5110 }
5112
5113 /* Transfer material pointers. The material indices are updated for each drawing separately. */
5114 if (!materials.is_empty()) {
5115 /* Old C API, needs a const_cast but doesn't actually change anything. */
5116 Material **materials_ptr = const_cast<Material **>(materials.data());
5118 bmain, DEG_get_original(ob_dst), &materials_ptr, materials.size(), false);
5119 }
5120
5121 DEG_id_tag_update(&grease_pencil_dst->id, ID_RECALC_GEOMETRY);
5123
5126
5127 return OPERATOR_FINISHED;
5128}
5129
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:450
AnimData * BKE_animdata_copy(Main *bmain, AnimData *adt, int flag)
Definition anim_data.cc:363
@ 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:898
#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.
@ CD_SET_DEFAULT
void CustomData_copy_data(const CustomData *source, CustomData *dest, int source_index, int dest_index, int count)
bool CustomData_merge_layout(const CustomData *source, CustomData *dest, eCustomDataMask mask, eCDAllocType alloctype, int totelem)
support for deformation groups and hooks.
void BKE_object_defgroup_unique_name(bDeformGroup *dg, Object *ob)
Definition deform.cc:741
void BKE_defgroup_copy_list(ListBase *outbase, const ListBase *inbase)
Definition deform.cc:71
#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:1679
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
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:126
void BKE_scene_graph_update_for_newframe(Depsgraph *depsgraph)
Definition scene.cc:2697
#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 BLI_SCOPED_DEFER(function_to_defer)
char * BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC
Definition string.cc:41
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
unsigned int uint
#define ELEM(...)
#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:962
@ ID_RECALC_SYNC_TO_EVAL
Definition DNA_ID.h:1026
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:982
@ ID_RECALC_ANIMATION_NO_FLUSH
Definition DNA_ID.h:1084
@ ID_MA
CurveType
@ CURVE_TYPE_BEZIER
@ CURVE_TYPE_NURBS
@ CURVE_TYPE_POLY
@ CURVE_TYPE_CATMULL_ROM
HandleType
@ 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
@ 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
@ 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 RNA_ENUM_ITEM_SEPR
Definition RNA_types.hh:645
@ PROP_SKIP_SAVE
Definition RNA_types.hh:330
@ PROP_HIDDEN
Definition RNA_types.hh:324
@ PROP_DISTANCE
Definition RNA_types.hh:244
#define C
Definition RandGen.cpp:29
void uiLayoutSetPropSep(uiLayout *layout, bool is_sep)
#define UI_ITEM_NONE
void uiLayoutSetPropDecorate(uiLayout *layout, bool is_sep)
#define NC_GEOM
Definition WM_types.hh:390
#define ND_DRAW
Definition WM_types.hh:458
#define ND_OB_ACTIVE
Definition WM_types.hh:437
#define ND_DATA
Definition WM_types.hh:506
@ KM_PRESS
Definition WM_types.hh:308
@ KM_RELEASE
Definition WM_types.hh:309
#define NC_SCENE
Definition WM_types.hh:375
#define ND_LAYER_CONTENT
Definition WM_types.hh:450
#define NA_EDITED
Definition WM_types.hh:581
#define NC_GPENCIL
Definition WM_types.hh:396
#define ND_SPACE_VIEW3D
Definition WM_types.hh:525
#define NC_OBJECT
Definition WM_types.hh:376
@ OPTYPE_UNDO
Definition WM_types.hh:182
@ OPTYPE_REGISTER
Definition WM_types.hh:180
#define NC_SPACE
Definition WM_types.hh:389
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_ptr(const Key &key) const
Definition BLI_map.hh:508
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 ForContainer(ContainerT container)
static VArray ForSingle(T value, const int64_t size)
static VArray ForSpan(Span< T > values)
void append(const T &value)
IndexRange index_range() const
const T & first() const
void clear()
Span< T > as_span() const
Definition BLI_array.hh:232
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:237
IndexRange index_range() const
Definition BLI_array.hh:349
bool is_empty() const
Definition BLI_array.hh:253
void copy_from(GSpan values)
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
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 ForContainer(ContainerT container)
static VArray ForSingle(T value, const int64_t size)
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
bool contains(StringRef attribute_id) const
GAttributeReader lookup_or_default(StringRef attribute_id, AttrDomain domain, eCustomDataType data_type, const void *default_value=nullptr) const
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, eCustomDataType 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
CCL_NAMESPACE_BEGIN struct Options options
static ushort indices[]
static float normals[][3]
#define INT16_MAX
uint pos
VecBase< float, 2 > float2
MatBase< 2, 4 > float2x4
VecBase< float, 4 > float4
MatBase< 4, 4 > float4x4
VecBase< float, 3 > float3
MatBase< 4, 3 > float4x3
VecBase< float, D > step(VecOp< float, D >, VecOp< float, D >) RET
#define CD_MASK_ALL
#define MEM_SAFE_FREE(v)
#define MAX_ID_NAME
#define FLAT(type, name)
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)
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)
Curves * curves_new_nomain(int points_num, int curves_num)
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, eCustomDataType 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_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 wmOperatorStatus grease_pencil_texture_gradient_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static void GREASE_PENCIL_OT_set_curve_type(wmOperatorType *ot)
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 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 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 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 const bke::CurvesGeometry fit_poly_curves(bke::CurvesGeometry &curves, const IndexMask &selection, const float threshold)
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_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::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={})
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)
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:71
static void unique_name(bNode *node)
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)
void RNA_string_get(PointerRNA *ptr, const char *name, char *value)
int RNA_int_get(PointerRNA *ptr, const char *name)
char * RNA_string_get_alloc(PointerRNA *ptr, const char *name, char *fixedbuf, int fixedlen, int *r_len)
float RNA_float_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_handle_type_items[]
Definition rna_curves.cc:30
const EnumPropertyItem rna_enum_curves_type_items[]
Definition rna_curves.cc:22
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
static const int steps
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
Definition DNA_ID.h:404
char name[66]
Definition DNA_ID.h:415
unsigned int session_uid
Definition DNA_ID.h:444
ListBase materials
Definition BKE_main.hh:251
struct AnimData * adt
float viewmat[4][4]
float viewinv[4][4]
struct ToolSettings * toolsettings
struct RenderData r
View3DCursor cursor
struct Object * camera
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
uiLayout & row(bool align)
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:754
short val
Definition WM_types.hh:756
uint is_active
Definition WM_types.hh:636
StructRNA * srna
Definition WM_types.hh:1124
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_menu_name_call(bContext *C, const char *menu_name, short context)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
@ LEFTMOUSE
PointerRNA * ptr
Definition wm_files.cc:4227
wmOperatorType * ot
Definition wm_files.cc:4226
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_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)
wmOperatorStatus WM_menu_invoke(bContext *C, wmOperator *op, const wmEvent *)