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 /* Remap the material indices of the pasted curves to the target object material indices. */
2653 bke::SpanAttributeWriter<int> material_indices = attributes.lookup_or_add_for_write_span<int>(
2654 "material_index", bke::AttrDomain::Curve);
2655 if (material_indices) {
2656 for (const int i : pasted_curves_range) {
2657 material_indices.span[i] = clipboard_material_remap[material_indices.span[i]];
2658 }
2659 material_indices.finish();
2660 }
2661
2662 drawing.tag_topology_changed();
2663
2664 return pasted_curves_range;
2665}
2666
2667enum class PasteType {
2670};
2671
2673{
2674 using namespace bke::greasepencil;
2675 Main *bmain = CTX_data_main(C);
2676 const Scene &scene = *CTX_data_scene(C);
2677 Object *object = CTX_data_active_object(C);
2679 scene.toolsettings);
2680 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
2681
2682 const PasteType type = PasteType(RNA_enum_get(op->ptr, "type"));
2683
2684 const bool keep_world_transform = RNA_boolean_get(op->ptr, "keep_world_transform");
2685 const bool paste_on_back = RNA_boolean_get(op->ptr, "paste_back");
2686
2688 if (clipboard.layers.is_empty()) {
2689 return OPERATOR_CANCELLED;
2690 }
2691
2692 /* Make sure everything on the clipboard is selected, in the correct selection domain. */
2694 bke::GSpanAttributeWriter selection = ed::curves::ensure_selection_attribute(
2695 layer.curves, selection_domain, CD_PROP_BOOL);
2696 selection.finish();
2697 });
2698
2699 if (type == PasteType::Active) {
2700 Layer *active_layer = grease_pencil.get_active_layer();
2701 if (!active_layer) {
2702 BKE_report(op->reports, RPT_ERROR, "No active Grease Pencil layer to paste into");
2703 return OPERATOR_CANCELLED;
2704 }
2705 if (!active_layer->is_editable()) {
2706 BKE_report(op->reports, RPT_ERROR, "Active layer is not editable");
2707 return OPERATOR_CANCELLED;
2708 }
2709
2710 /* Deselect everything from editable drawings. The pasted strokes are the only ones then after
2711 * the paste. That's convenient for the user. */
2712 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
2713 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
2715 info.drawing.strokes_for_write(), selection_domain, CD_PROP_BOOL);
2716 ed::curves::fill_selection_false(selection_in_target.span);
2717 selection_in_target.finish();
2718 });
2719
2720 const float4x4 object_to_layer = math::invert(active_layer->to_object_space(*object));
2721
2722 /* Ensure active keyframe. */
2723 bool inserted_keyframe = false;
2724 if (!ensure_active_keyframe(scene, grease_pencil, *active_layer, false, inserted_keyframe)) {
2725 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil frame to draw on");
2726 return OPERATOR_CANCELLED;
2727 }
2728
2729 Vector<MutableDrawingInfo> drawing_infos =
2731 scene, grease_pencil, *active_layer);
2732 for (const MutableDrawingInfo info : drawing_infos) {
2734 *bmain, *object, object_to_layer, keep_world_transform, paste_on_back, info.drawing);
2735 }
2736
2737 if (inserted_keyframe) {
2739 }
2740 }
2741 else if (type == PasteType::ByLayer) {
2742 Layer *active_layer = grease_pencil.get_active_layer();
2743 /* Find layers to paste strokes into. */
2744 Array<Layer *> layers_to_paste_into(clipboard.layers.size());
2745 for (const int clip_layer_i : clipboard.layers.index_range()) {
2746 const Clipboard::ClipboardLayer &layer = clipboard.layers[clip_layer_i];
2747 bke::greasepencil::TreeNode *node = grease_pencil.find_node_by_name(layer.name);
2748 const bool found_layer = node && node->is_layer() && node->as_layer().is_editable();
2749 if (found_layer) {
2750 layers_to_paste_into[clip_layer_i] = &node->as_layer();
2751 continue;
2752 }
2753 if (active_layer && active_layer->is_editable()) {
2754 /* Fall back to active layer. */
2755 BKE_report(
2756 op->reports, RPT_WARNING, "Couldn't find matching layer, pasting into active layer");
2757 layers_to_paste_into[clip_layer_i] = active_layer;
2758 continue;
2759 }
2760
2761 if (!active_layer) {
2762 BKE_report(op->reports, RPT_ERROR, "No active Grease Pencil layer to paste into");
2763 }
2764 if (!active_layer->is_editable()) {
2765 BKE_report(op->reports, RPT_ERROR, "Active layer is not editable");
2766 }
2767 return OPERATOR_CANCELLED;
2768 }
2769
2770 /* Deselect everything from editable drawings. The pasted strokes are the only ones then after
2771 * the paste. That's convenient for the user. */
2772 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
2773 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
2775 info.drawing.strokes_for_write(), selection_domain, CD_PROP_BOOL);
2776 ed::curves::fill_selection_false(selection_in_target.span);
2777 selection_in_target.finish();
2778 });
2779
2780 for (const int clip_layer_i : clipboard.layers.index_range()) {
2781 const Clipboard::ClipboardLayer &clip_layer = clipboard.layers[clip_layer_i];
2782 const bke::CurvesGeometry &curves_to_paste = clip_layer.curves;
2783
2784 BLI_assert(layers_to_paste_into[clip_layer_i] != nullptr);
2785 Layer &paste_layer = *layers_to_paste_into[clip_layer_i];
2786 const float4x4 object_to_paste_layer = math::invert(paste_layer.to_object_space(*object));
2787
2788 /* Ensure active keyframe. */
2789 bool inserted_keyframe = false;
2790 if (!ensure_active_keyframe(scene, grease_pencil, paste_layer, false, inserted_keyframe)) {
2791 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil frame to draw on");
2792 return OPERATOR_CANCELLED;
2793 }
2794
2795 Vector<MutableDrawingInfo> drawing_infos =
2797 scene, grease_pencil, paste_layer);
2798 for (const MutableDrawingInfo info : drawing_infos) {
2800 *object,
2801 curves_to_paste,
2802 object_to_paste_layer,
2803 clipboard.object_to_world,
2804 keep_world_transform,
2805 paste_on_back,
2806 info.drawing);
2807 }
2808
2809 if (inserted_keyframe) {
2811 }
2812 }
2813 }
2814 else {
2816 }
2817
2818 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
2819 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
2820
2821 return OPERATOR_FINISHED;
2822}
2823
2825{
2827 return false;
2828 }
2829
2830 std::scoped_lock lock(grease_pencil_clipboard_lock);
2831 /* Check for curves in the Grease Pencil clipboard. */
2832 return (grease_pencil_clipboard && grease_pencil_clipboard->layers.size() > 0);
2833}
2834
2836{
2837 PropertyRNA *prop;
2838
2839 static const EnumPropertyItem rna_paste_items[] = {
2840 {int(PasteType::Active), "ACTIVE", 0, "Paste to Active", ""},
2841 {int(PasteType::ByLayer), "LAYER", 0, "Paste by Layer", ""},
2842 {0, nullptr, 0, nullptr, nullptr},
2843 };
2844
2845 ot->name = "Paste Strokes";
2846 ot->idname = "GREASE_PENCIL_OT_paste";
2847 ot->description =
2848 "Paste Grease Pencil points or strokes from the internal clipboard to the active layer";
2849
2852
2853 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2854
2855 ot->prop = RNA_def_enum(ot->srna, "type", rna_paste_items, int(PasteType::Active), "Type", "");
2856
2857 prop = RNA_def_boolean(
2858 ot->srna, "paste_back", false, "Paste on Back", "Add pasted strokes behind all strokes");
2860
2861 prop = RNA_def_boolean(ot->srna,
2862 "keep_world_transform",
2863 false,
2864 "Keep World Transform",
2865 "Keep the world transform of strokes from the clipboard unchanged");
2867}
2868
2870
2872 Object &object,
2873 const float4x4 &object_to_paste_layer,
2874 const bool keep_world_transform,
2875 const bool paste_back,
2877{
2879 if (clipboard.layers.is_empty()) {
2880 return {};
2881 }
2882
2883 Vector<bke::GeometrySet> geometries_to_join;
2884 for (Clipboard::ClipboardLayer &layer : clipboard.layers) {
2886 }
2887 bke::GeometrySet joined_clipboard_set = geometry::join_geometries(geometries_to_join.as_span(),
2888 {});
2889 BLI_assert(joined_clipboard_set.has_curves());
2890 const bke::CurvesGeometry &joined_clipboard_curves =
2891 joined_clipboard_set.get_curves()->geometry.wrap();
2892
2893 return clipboard_paste_strokes_ex(bmain,
2894 object,
2895 joined_clipboard_curves,
2896 object_to_paste_layer,
2897 clipboard.object_to_world,
2898 keep_world_transform,
2899 paste_back,
2900 drawing);
2901}
2902
2903/* -------------------------------------------------------------------- */
2907{
2908 const Scene *scene = CTX_data_scene(C);
2909 Object *object = CTX_data_active_object(C);
2910 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
2911
2912 const float threshold = RNA_float_get(op->ptr, "threshold");
2913 const bool use_unselected = RNA_boolean_get(op->ptr, "use_unselected");
2914
2915 std::atomic<bool> changed = false;
2916
2917 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
2918 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
2919 bke::greasepencil::Drawing &drawing = info.drawing;
2920 IndexMaskMemory memory;
2921 const IndexMask points = use_unselected ?
2923 *object, drawing, info.layer_index, memory) :
2925 *object, info.drawing, info.layer_index, memory);
2926 if (points.is_empty()) {
2927 return;
2928 }
2930 drawing.strokes(), threshold, points, {});
2931 drawing.tag_topology_changed();
2932 changed.store(true, std::memory_order_relaxed);
2933 });
2934 if (changed) {
2935 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
2936 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
2937 }
2938 return OPERATOR_FINISHED;
2939}
2940
2942{
2943 PropertyRNA *prop;
2944
2945 ot->name = "Merge by Distance";
2946 ot->idname = "GREASE_PENCIL_OT_stroke_merge_by_distance";
2947 ot->description = "Merge points by distance";
2948
2951
2952 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2953
2954 prop = RNA_def_float(ot->srna, "threshold", 0.001f, 0.0f, 100.0f, "Threshold", "", 0.0f, 100.0f);
2955 /* Avoid re-using last var. */
2957
2958 prop = RNA_def_boolean(ot->srna,
2959 "use_unselected",
2960 false,
2961 "Unselected",
2962 "Use whole stroke, not only selected points");
2964}
2965
2967
2968/* -------------------------------------------------------------------- */
2971
2973 const IndexMask &points_to_extrude)
2974{
2975 const OffsetIndices<int> points_by_curve = src.points_by_curve();
2976
2977 const int old_curves_num = src.curves_num();
2978 const int old_points_num = src.points_num();
2979
2980 Vector<int> dst_to_src_points(old_points_num);
2982
2983 Vector<int> dst_to_src_curves(old_curves_num);
2985
2986 Vector<bool> dst_selected(old_points_num, false);
2987
2988 Vector<int> dst_curve_counts(old_curves_num);
2990 points_by_curve, src.curves_range(), dst_curve_counts.as_mutable_span());
2991
2992 const VArray<bool> &src_cyclic = src.cyclic();
2993
2994 /* Point offset keeps track of the points inserted. */
2995 int point_offset = 0;
2996 for (const int curve_index : src.curves_range()) {
2997 const IndexRange curve_points = points_by_curve[curve_index];
2998 const IndexMask curve_points_to_extrude = points_to_extrude.slice_content(curve_points);
2999 const bool curve_cyclic = src_cyclic[curve_index];
3000
3001 curve_points_to_extrude.foreach_index([&](const int src_point_index) {
3002 if (!curve_cyclic && (src_point_index == curve_points.first())) {
3003 /* Start-point extruded, we insert a new point at the beginning of the curve.
3004 * NOTE: all points of a cyclic curve behave like an inner-point. */
3005 dst_to_src_points.insert(src_point_index + point_offset, src_point_index);
3006 dst_selected.insert(src_point_index + point_offset, true);
3007 ++dst_curve_counts[curve_index];
3008 ++point_offset;
3009 return;
3010 }
3011 if (!curve_cyclic && (src_point_index == curve_points.last())) {
3012 /* End-point extruded, we insert a new point at the end of the curve.
3013 * NOTE: all points of a cyclic curve behave like an inner-point. */
3014 dst_to_src_points.insert(src_point_index + point_offset + 1, src_point_index);
3015 dst_selected.insert(src_point_index + point_offset + 1, true);
3016 ++dst_curve_counts[curve_index];
3017 ++point_offset;
3018 return;
3019 }
3020
3021 /* Inner-point extruded: we create a new curve made of two points located at the same
3022 * position. Only one of them is selected so that the other one remains stuck to the curve.
3023 */
3024 dst_to_src_points.append(src_point_index);
3025 dst_selected.append(false);
3026 dst_to_src_points.append(src_point_index);
3027 dst_selected.append(true);
3028 dst_to_src_curves.append(curve_index);
3029 dst_curve_counts.append(2);
3030 });
3031 }
3032
3033 const int new_points_num = dst_to_src_points.size();
3034 const int new_curves_num = dst_to_src_curves.size();
3035
3036 bke::CurvesGeometry dst(new_points_num, new_curves_num);
3038
3039 /* Setup curve offsets, based on the number of points in each curve. */
3040 MutableSpan<int> new_curve_offsets = dst.offsets_for_write();
3041 array_utils::copy(dst_curve_counts.as_span(), new_curve_offsets.drop_back(1));
3043
3044 /* Attributes. */
3045 const bke::AttributeAccessor src_attributes = src.attributes();
3047
3048 /* Selection attribute. */
3049 /* Copy the value of control point selections to all selection attributes.
3050 *
3051 * This will lead to the extruded control point always having both handles selected, if it's a
3052 * bezier type stroke. This is to circumvent the issue of source curves handles not being
3053 * deselected when the user extrudes a bezier control point with both handles selected*/
3054 for (const StringRef selection_attribute_name :
3056 {
3058 dst, bke::AttrDomain::Point, CD_PROP_BOOL, selection_attribute_name);
3059 selection.span.copy_from(dst_selected.as_span());
3060 selection.finish();
3061 }
3062
3063 bke::gather_attributes(src_attributes,
3066 {},
3067 dst_to_src_curves,
3068 dst_attributes);
3069
3070 /* Cyclic attribute : newly created curves cannot be cyclic. */
3071 dst.cyclic_for_write().drop_front(old_curves_num).fill(false);
3072
3073 bke::gather_attributes(src_attributes,
3077 {".selection", ".selection_handle_left", ".selection_handle_right"}),
3078 dst_to_src_points,
3079 dst_attributes);
3080
3081 dst.update_curve_types();
3082 if (src.nurbs_has_custom_knots()) {
3083 IndexMaskMemory memory;
3084 const VArray<int8_t> curve_types = src.curve_types();
3085 const VArray<int8_t> knot_modes = dst.nurbs_knots_modes();
3086 const OffsetIndices<int> dst_points_by_curve = dst.points_by_curve();
3087 const IndexMask include_curves = IndexMask::from_predicate(
3088 src.curves_range(), GrainSize(512), memory, [&](const int64_t curve_index) {
3089 return curve_types[curve_index] == CURVE_TYPE_NURBS &&
3090 knot_modes[curve_index] == NURBS_KNOT_MODE_CUSTOM &&
3091 points_by_curve[curve_index].size() == dst_points_by_curve[curve_index].size();
3092 });
3094 include_curves.complement(dst.curves_range(), memory),
3097 dst);
3098 bke::curves::nurbs::gather_custom_knots(src, include_curves, 0, dst);
3099 }
3100 return dst;
3101}
3102
3104{
3105 const Scene *scene = CTX_data_scene(C);
3106 Object *object = CTX_data_active_object(C);
3107 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
3108
3109 std::atomic<bool> changed = false;
3110 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
3111 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3112 IndexMaskMemory memory;
3113 const IndexMask points_to_extrude = retrieve_editable_and_selected_points(
3114 *object, info.drawing, info.layer_index, memory);
3115 if (points_to_extrude.is_empty()) {
3116 return;
3117 }
3118
3119 const bke::CurvesGeometry &curves = info.drawing.strokes();
3120 info.drawing.strokes_for_write() = extrude_grease_pencil_curves(curves, points_to_extrude);
3121
3123 changed.store(true, std::memory_order_relaxed);
3124 });
3125
3126 if (changed) {
3127 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
3128 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
3129 }
3130
3131 return OPERATOR_FINISHED;
3132}
3133
3135{
3136 ot->name = "Extrude Stroke Points";
3137 ot->idname = "GREASE_PENCIL_OT_extrude";
3138 ot->description = "Extrude the selected points";
3139
3142
3143 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3144}
3145
3147
3148/* -------------------------------------------------------------------- */
3151
3152/* Determine how much the radius needs to be scaled to look the same from the view. */
3154 const float3 &old_pos,
3155 const float3 &new_pos)
3156{
3157 /* Don't scale the radius when the view is orthographic. */
3158 if (!rv3d->is_persp) {
3159 return 1.0f;
3160 }
3161
3162 const float3 view_center = float3(rv3d->viewinv[3]);
3163 return math::length(new_pos - view_center) / math::length(old_pos - view_center);
3164}
3165
3167{
3168 Scene &scene = *CTX_data_scene(C);
3170
3171 View3D *v3d = CTX_wm_view3d(C);
3172 ARegion *region = CTX_wm_region(C);
3173
3174 RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
3175
3176 const ReprojectMode mode = ReprojectMode(RNA_enum_get(op->ptr, "type"));
3177 const bool keep_original = RNA_boolean_get(op->ptr, "keep_original");
3178
3179 Object *object = CTX_data_active_object(C);
3180 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
3181 const float offset = RNA_float_get(op->ptr, "offset");
3182
3183 /* Init snap context for geometry projection. */
3185 [&]() -> transform::SnapObjectContext * {
3186 if (mode == ReprojectMode::Surface) {
3187 return transform::snap_object_context_create(&scene, 0);
3188 }
3189 return nullptr;
3190 });
3191
3193 scene.toolsettings);
3194
3195 const int oldframe = int(DEG_get_ctime(depsgraph));
3196 if (keep_original) {
3197 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
3198 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3199 IndexMaskMemory memory;
3201 *object, info.drawing, info.layer_index, selection_domain, memory);
3202 if (elements.is_empty()) {
3203 return;
3204 }
3205
3207 if (selection_domain == bke::AttrDomain::Curve) {
3209 }
3210 else if (selection_domain == bke::AttrDomain::Point) {
3212 }
3214 });
3215 }
3216
3217 /* TODO: This can probably be optimized further for the non-Surface projection use case by
3218 * considering all drawings for the parallel loop instead of having to partition by frame number.
3219 */
3220 std::atomic<bool> changed = false;
3221 Array<Vector<MutableDrawingInfo>> drawings_per_frame =
3223 for (const Span<MutableDrawingInfo> drawings : drawings_per_frame) {
3224 if (drawings.is_empty()) {
3225 continue;
3226 }
3227 const int current_frame_number = drawings.first().frame_number;
3228
3229 if (mode == ReprojectMode::Surface) {
3230 scene.r.cfra = current_frame_number;
3232 }
3233
3234 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3236 MutableSpan<float> radii = curves.radius_for_write();
3237
3238 IndexMaskMemory memory;
3239 const IndexMask editable_points = retrieve_editable_points(
3240 *object, info.drawing, info.layer_index, memory);
3241
3242 for (const StringRef selection_name :
3244 {
3245 const IndexMask selected_points = ed::curves::retrieve_selected_points(
3246 curves, selection_name, memory);
3247 const IndexMask points_to_reproject = IndexMask::from_intersection(
3248 editable_points, selected_points, memory);
3249
3250 if (points_to_reproject.is_empty()) {
3251 return;
3252 }
3253
3254 MutableSpan<float3> positions = curves.positions_for_write();
3255 if (selection_name == ".selection_handle_left") {
3256 positions = curves.handle_positions_left_for_write();
3257 }
3258 else if (selection_name == ".selection_handle_right") {
3259 positions = curves.handle_positions_right_for_write();
3260 }
3261
3262 const bke::greasepencil::Layer &layer = grease_pencil.layer(info.layer_index);
3263 if (mode == ReprojectMode::Surface) {
3264 const float4x4 layer_space_to_world_space = layer.to_world_space(*object);
3265 const float4x4 world_space_to_layer_space = math::invert(layer_space_to_world_space);
3266 points_to_reproject.foreach_index(GrainSize(4096), [&](const int point_i) {
3267 float3 &position = positions[point_i];
3268 const float3 world_pos = math::transform_point(layer_space_to_world_space, position);
3269 float2 screen_co;
3270 if (ED_view3d_project_float_global(region, world_pos, screen_co, V3D_PROJ_TEST_NOP) !=
3272 {
3273 return;
3274 }
3275
3276 float3 ray_start, ray_direction;
3278 depsgraph, region, v3d, screen_co, ray_start, ray_direction, true))
3279 {
3280 return;
3281 }
3282
3283 float hit_depth = std::numeric_limits<float>::max();
3284 float3 hit_position(0.0f);
3285 float3 hit_normal(0.0f);
3286
3288 params.snap_target_select = SCE_SNAP_TARGET_ALL;
3289 transform::SnapObjectContext *snap_context = thread_snap_contexts.local();
3290 if (transform::snap_object_project_ray(snap_context,
3291 depsgraph,
3292 v3d,
3293 &params,
3294 ray_start,
3295 ray_direction,
3296 &hit_depth,
3297 hit_position,
3298 hit_normal))
3299 {
3300 /* Apply offset over surface. */
3301 const float3 new_pos = math::transform_point(
3302 world_space_to_layer_space,
3303 hit_position + math::normalize(ray_start - hit_position) * offset);
3304
3305 if (selection_name == ".selection") {
3306 radii[point_i] *= calculate_radius_projection_factor(rv3d, position, new_pos);
3307 }
3308 position = new_pos;
3309 }
3310 });
3311 }
3312 else {
3313 const DrawingPlacement drawing_placement(
3314 scene, *region, *v3d, *object, &layer, mode, offset, nullptr);
3315 points_to_reproject.foreach_index(GrainSize(4096), [&](const int point_i) {
3316 const float3 new_pos = drawing_placement.reproject(positions[point_i]);
3317 if (selection_name == ".selection") {
3318 radii[point_i] *= calculate_radius_projection_factor(
3319 rv3d, positions[point_i], new_pos);
3320 }
3321 positions[point_i] = new_pos;
3322 });
3323 }
3324
3326 changed.store(true, std::memory_order_relaxed);
3327 }
3328 });
3329 }
3330
3331 for (transform::SnapObjectContext *snap_context : thread_snap_contexts) {
3332 if (snap_context != nullptr) {
3334 }
3335 }
3336
3337 if (mode == ReprojectMode::Surface) {
3338 scene.r.cfra = oldframe;
3340 }
3341
3342 if (changed) {
3343 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
3344 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
3345 }
3346
3347 return OPERATOR_FINISHED;
3348}
3349
3351{
3352 uiLayout *layout = op->layout;
3353 uiLayout *row;
3354
3355 const ReprojectMode type = ReprojectMode(RNA_enum_get(op->ptr, "type"));
3356
3357 uiLayoutSetPropSep(layout, true);
3358 uiLayoutSetPropDecorate(layout, false);
3359 row = &layout->row(true);
3360 row->prop(op->ptr, "type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
3361
3362 if (type == ReprojectMode::Surface) {
3363 row = &layout->row(true);
3364 row->prop(op->ptr, "offset", UI_ITEM_NONE, std::nullopt, ICON_NONE);
3365 }
3366 row = &layout->row(true);
3367 row->prop(op->ptr, "keep_original", UI_ITEM_NONE, std::nullopt, ICON_NONE);
3368}
3369
3371{
3372 static const EnumPropertyItem reproject_type[] = {
3374 "FRONT",
3375 0,
3376 "Front",
3377 "Reproject the strokes using the X-Z plane"},
3378 {int(ReprojectMode::Side), "SIDE", 0, "Side", "Reproject the strokes using the Y-Z plane"},
3379 {int(ReprojectMode::Top), "TOP", 0, "Top", "Reproject the strokes using the X-Y plane"},
3380 {int(ReprojectMode::View),
3381 "VIEW",
3382 0,
3383 "View",
3384 "Reproject the strokes to end up on the same plane, as if drawn from the current "
3385 "viewpoint "
3386 "using 'Cursor' Stroke Placement"},
3388 "SURFACE",
3389 0,
3390 "Surface",
3391 "Reproject the strokes on to the scene geometry, as if drawn using 'Surface' placement"},
3393 "CURSOR",
3394 0,
3395 "Cursor",
3396 "Reproject the strokes using the orientation of 3D cursor"},
3397 {0, nullptr, 0, nullptr, nullptr},
3398 };
3399
3400 /* identifiers */
3401 ot->name = "Reproject Strokes";
3402 ot->idname = "GREASE_PENCIL_OT_reproject";
3403 ot->description =
3404 "Reproject the selected strokes from the current viewpoint as if they had been newly "
3405 "drawn "
3406 "(e.g. to fix problems from accidental 3D cursor movement or accidental viewport changes, "
3407 "or for matching deforming geometry)";
3408
3409 /* callbacks */
3410 ot->invoke = WM_menu_invoke;
3414
3415 /* flags */
3416 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3417
3418 /* properties */
3419 ot->prop = RNA_def_enum(
3420 ot->srna, "type", reproject_type, int(ReprojectMode::View), "Projection Type", "");
3421
3423 ot->srna,
3424 "keep_original",
3425 false,
3426 "Keep Original",
3427 "Keep original strokes and create a copy before reprojecting");
3429
3430 RNA_def_float(ot->srna, "offset", 0.0f, 0.0f, 10.0f, "Surface Offset", "", 0.0f, 10.0f);
3431}
3432
3434/* -------------------------------------------------------------------- */
3437
3438/* Poll callback for snap operators */
3439/* NOTE: For now, we only allow these in the 3D view, as other editors do not
3440 * define a cursor or grid-step which can be used.
3441 */
3443{
3445 return false;
3446 }
3447
3448 const ScrArea *area = CTX_wm_area(C);
3449 if (!(area && area->spacetype == SPACE_VIEW3D)) {
3450 return false;
3451 }
3452 const ARegion *region = CTX_wm_region(C);
3453 if (!(region && region->regiontype == RGN_TYPE_WINDOW)) {
3454 return false;
3455 }
3456
3457 return true;
3458}
3459
3461{
3463
3464 const Scene &scene = *CTX_data_scene(C);
3465 Object &object = *CTX_data_active_object(C);
3466 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
3467 const View3D &v3d = *CTX_wm_view3d(C);
3468 const ARegion &region = *CTX_wm_region(C);
3469 const float grid_size = ED_view3d_grid_view_scale(&scene, &v3d, &region, nullptr);
3470
3471 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
3472 for (const MutableDrawingInfo &drawing_info : drawings) {
3473 bke::CurvesGeometry &curves = drawing_info.drawing.strokes_for_write();
3474 if (curves.is_empty()) {
3475 continue;
3476 }
3478 continue;
3479 }
3480
3482 {
3483 IndexMaskMemory memory;
3484 const IndexMask selected_points = ed::curves::retrieve_selected_points(
3485 curves, selection_name, memory);
3486
3487 const Layer &layer = grease_pencil.layer(drawing_info.layer_index);
3488 const float4x4 layer_to_world = layer.to_world_space(object);
3489 const float4x4 world_to_layer = math::invert(layer_to_world);
3490
3491 MutableSpan<float3> positions = curves.positions_for_write();
3492 if (selection_name == ".selection_handle_left") {
3493 positions = curves.handle_positions_left_for_write();
3494 }
3495 else if (selection_name == ".selection_handle_right") {
3496 positions = curves.handle_positions_right_for_write();
3497 }
3498 selected_points.foreach_index(GrainSize(4096), [&](const int point_i) {
3499 const float3 pos_world = math::transform_point(layer_to_world, positions[point_i]);
3500 const float3 pos_snapped = grid_size * math::floor(pos_world / grid_size + 0.5f);
3501 positions[point_i] = math::transform_point(world_to_layer, pos_snapped);
3502 });
3503 }
3504
3505 drawing_info.drawing.tag_positions_changed();
3509 WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, &grease_pencil);
3510 }
3511
3512 return OPERATOR_FINISHED;
3513}
3514
3516{
3517 ot->name = "Snap Selection to Grid";
3518 ot->idname = "GREASE_PENCIL_OT_snap_to_grid";
3519 ot->description = "Snap selected points to the nearest grid points";
3520
3523
3524 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3525}
3526
3528
3529/* -------------------------------------------------------------------- */
3532
3534{
3536
3537 const Scene &scene = *CTX_data_scene(C);
3538 Object &object = *CTX_data_active_object(C);
3539 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
3540 const bool use_offset = RNA_boolean_get(op->ptr, "use_offset");
3541 const float3 cursor_world = scene.cursor.location;
3542
3543 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
3544 for (const MutableDrawingInfo &drawing_info : drawings) {
3545 bke::CurvesGeometry &curves = drawing_info.drawing.strokes_for_write();
3546 if (curves.is_empty()) {
3547 continue;
3548 }
3550 continue;
3551 }
3552
3553 IndexMaskMemory selected_points_memory;
3555 selected_points_memory);
3556
3557 const Layer &layer = grease_pencil.layer(drawing_info.layer_index);
3558 const float4x4 layer_to_world = layer.to_world_space(object);
3559 const float4x4 world_to_layer = math::invert(layer_to_world);
3560 const float3 cursor_layer = math::transform_point(world_to_layer, cursor_world);
3561
3562 MutableSpan<float3> positions = curves.positions_for_write();
3563 if (use_offset) {
3564 const OffsetIndices points_by_curve = curves.points_by_curve();
3565 IndexMaskMemory selected_curves_memory;
3566 const IndexMask selected_curves = ed::curves::retrieve_selected_curves(
3567 curves, selected_curves_memory);
3568
3569 selected_curves.foreach_index(GrainSize(512), [&](const int curve_i) {
3570 const IndexRange points = points_by_curve[curve_i];
3571
3572 /* Offset from first point of the curve. */
3573 const float3 offset = cursor_layer - positions[points.first()];
3574 selected_points.slice_content(points).foreach_index(
3575 GrainSize(4096), [&](const int point_i) { positions[point_i] += offset; });
3576 });
3577 }
3578 else {
3579 /* Set all selected positions to the cursor location. */
3580 index_mask::masked_fill(positions, cursor_layer, selected_points);
3581 }
3582
3583 curves.calculate_bezier_auto_handles();
3584 drawing_info.drawing.tag_positions_changed();
3588 WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, &grease_pencil);
3589 }
3590
3591 return OPERATOR_FINISHED;
3592}
3593
3595{
3596 /* identifiers */
3597 ot->name = "Snap Selection to Cursor";
3598 ot->idname = "GREASE_PENCIL_OT_snap_to_cursor";
3599 ot->description = "Snap selected points/strokes to the cursor";
3600
3601 /* callbacks */
3604
3605 /* flags */
3606 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3607
3608 /* props */
3609 ot->prop = RNA_def_boolean(ot->srna,
3610 "use_offset",
3611 true,
3612 "With Offset",
3613 "Offset the entire stroke instead of selected points only");
3614}
3615
3617
3618/* -------------------------------------------------------------------- */
3621
3623 const Object &object,
3624 const GreasePencil &grease_pencil,
3625 float3 &r_centroid,
3626 float3 &r_min,
3627 float3 &r_max)
3628{
3630
3631 int num_selected = 0;
3632 r_centroid = float3(0.0f);
3633 r_min = float3(std::numeric_limits<float>::max());
3634 r_max = float3(std::numeric_limits<float>::lowest());
3635
3636 const Vector<DrawingInfo> drawings = retrieve_visible_drawings(scene, grease_pencil, false);
3637 for (const DrawingInfo &drawing_info : drawings) {
3638 const Layer &layer = grease_pencil.layer(drawing_info.layer_index);
3639 if (layer.is_locked()) {
3640 continue;
3641 }
3642 const bke::CurvesGeometry &curves = drawing_info.drawing.strokes();
3643 if (curves.is_empty()) {
3644 continue;
3645 }
3647 continue;
3648 }
3649
3650 IndexMaskMemory selected_points_memory;
3652 selected_points_memory);
3653 const float4x4 layer_to_world = layer.to_world_space(object);
3654
3655 Span<float3> positions = curves.positions();
3656 selected_points.foreach_index(GrainSize(4096), [&](const int point_i) {
3657 const float3 pos_world = math::transform_point(layer_to_world, positions[point_i]);
3658 r_centroid += pos_world;
3659 math::min_max(pos_world, r_min, r_max);
3660 });
3661 num_selected += selected_points.size();
3662 }
3663 if (num_selected == 0) {
3664 r_min = r_max = float3(0.0f);
3665 return false;
3666 }
3667
3668 r_centroid /= num_selected;
3669 return true;
3670}
3671
3673{
3674 Scene &scene = *CTX_data_scene(C);
3675 const Object &object = *CTX_data_active_object(C);
3676 const GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
3677 float3 &cursor = reinterpret_cast<float3 &>(scene.cursor.location);
3678
3679 float3 centroid, points_min, points_max;
3681 scene, object, grease_pencil, centroid, points_min, points_max))
3682 {
3683 return OPERATOR_FINISHED;
3684 }
3685
3686 switch (scene.toolsettings->transform_pivot_point) {
3688 cursor = math::midpoint(points_min, points_max);
3689 break;
3691 case V3D_AROUND_CURSOR:
3693 case V3D_AROUND_ACTIVE:
3694 cursor = centroid;
3695 break;
3696 default:
3698 }
3699
3702
3703 return OPERATOR_FINISHED;
3704}
3705
3707{
3708 /* identifiers */
3709 ot->name = "Snap Cursor to Selected Points";
3710 ot->idname = "GREASE_PENCIL_OT_snap_cursor_to_selected";
3711 ot->description = "Snap cursor to center of selected points";
3712
3713 /* callbacks */
3716
3717 /* flags */
3718 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3719}
3720
3722{
3723 float4x3 strokemat4x3 = float4x3(strokemat);
3724
3725 /*
3726 * We need the diagonal of ones to start from the bottom right instead top left to properly
3727 * apply the two matrices.
3728 *
3729 * i.e.
3730 * # # # # # # # #
3731 * We need # # # # Instead of # # # #
3732 * 0 0 0 1 0 0 1 0
3733 *
3734 */
3735 strokemat4x3[2][2] = 0.0f;
3736 strokemat4x3[3][2] = 1.0f;
3737
3738 return strokemat4x3;
3739}
3740
3742{
3743 const Scene *scene = CTX_data_scene(C);
3744 Object *object = CTX_data_active_object(C);
3745 ARegion *region = CTX_wm_region(C);
3746 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
3747
3748 std::atomic<bool> changed = false;
3749 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
3750 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3751 IndexMaskMemory memory;
3753 *object, info.drawing, info.layer_index, memory);
3754 if (strokes.is_empty()) {
3755 return;
3756 }
3757
3758 const bke::greasepencil::Layer &layer = grease_pencil.layer(info.layer_index);
3759 const float4x4 layer_space_to_world_space = layer.to_world_space(*object);
3760
3761 /* Calculate screen space points. */
3762 const float2 screen_start(RNA_int_get(op->ptr, "xstart"), RNA_int_get(op->ptr, "ystart"));
3763 const float2 screen_end(RNA_int_get(op->ptr, "xend"), RNA_int_get(op->ptr, "yend"));
3764 const float2 screen_direction = screen_end - screen_start;
3765 const float2 screen_tangent = screen_start + float2(-screen_direction[1], screen_direction[0]);
3766
3767 const bke::CurvesGeometry &curves = info.drawing.strokes();
3768 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
3769 const Span<float3> positions = curves.positions();
3771 const VArray<int> materials = *curves.attributes().lookup_or_default<int>(
3772 "material_index", bke::AttrDomain::Curve, 0);
3773
3774 Array<float4x2> texture_matrices(strokes.size());
3775
3776 strokes.foreach_index([&](const int curve_i, const int pos) {
3777 const int material_index = materials[curve_i];
3778
3779 const MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(object,
3780 material_index + 1);
3781 const bool is_radial = gp_style->gradient_type == GP_MATERIAL_GRADIENT_RADIAL;
3782
3783 const float texture_angle = gp_style->texture_angle;
3784 const float2 texture_scale = float2(gp_style->texture_scale);
3785 const float2 texture_offset = float2(gp_style->texture_offset);
3786
3787 const float2x2 texture_rotation = math::from_rotation<float2x2>(
3788 math::AngleRadian(texture_angle));
3789
3790 const float3 point = math::transform_point(layer_space_to_world_space,
3791 positions[points_by_curve[curve_i].first()]);
3792 const float3 normal = math::transform_direction(layer_space_to_world_space,
3793 normals[curve_i]);
3794
3795 const float4 plane = float4(normal, -math::dot(normal, point));
3796
3797 float3 start;
3798 float3 tangent;
3799 float3 end;
3800 ED_view3d_win_to_3d_on_plane(region, plane, screen_start, false, start);
3801 ED_view3d_win_to_3d_on_plane(region, plane, screen_tangent, false, tangent);
3802 ED_view3d_win_to_3d_on_plane(region, plane, screen_end, false, end);
3803
3804 const float3 origin = start;
3805 /* Invert the length by dividing by the length squared. */
3806 const float3 u_dir = (end - origin) / math::length_squared(end - origin);
3807 float3 v_dir = math::cross(u_dir, normal);
3808
3809 /* Flip the texture if need so that it is not mirrored. */
3810 if (math::dot(tangent - start, v_dir) < 0.0f) {
3811 v_dir = -v_dir;
3812 }
3813
3814 /* Calculate the texture space before the texture offset transformation. */
3815 const float4x2 base_texture_space = math::transpose(float2x4(
3816 float4(u_dir, -math::dot(u_dir, origin)), float4(v_dir, -math::dot(v_dir, origin))));
3817
3818 float3x2 offset_matrix = float3x2::identity();
3819
3820 if (is_radial) {
3821 /* Radial gradients are scaled down by a factor of 2 and have the center at 0.5 */
3822 offset_matrix *= 0.5f;
3823 offset_matrix[2] += float2(0.5f, 0.5f);
3824 }
3825
3826 /* For some reason 0.5 is added to the offset before being rendered, so remove it here. */
3827 offset_matrix[2] -= float2(0.5f, 0.5f);
3828
3829 offset_matrix = math::from_scale<float2x2>(texture_scale) * offset_matrix;
3830 offset_matrix = texture_rotation * offset_matrix;
3831 offset_matrix[2] -= texture_offset;
3832
3833 texture_matrices[pos] = (offset_matrix * expand_4x2_mat(base_texture_space)) *
3834 layer_space_to_world_space;
3835 });
3836
3837 info.drawing.set_texture_matrices(texture_matrices, strokes);
3838
3839 changed.store(true, std::memory_order_relaxed);
3840 });
3841
3842 if (changed) {
3843 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
3844 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
3845 }
3846
3848}
3849
3851 wmOperator *op,
3852 const wmEvent *event)
3853{
3855
3856 /* Check for mouse release. */
3857 if ((ret & OPERATOR_RUNNING_MODAL) != 0 && event->type == LEFTMOUSE && event->val == KM_RELEASE)
3858 {
3862 }
3863
3864 return ret;
3865}
3866
3868 wmOperator *op,
3869 const wmEvent *event)
3870{
3871 /* Invoke interactive line drawing (representing the gradient) in viewport. */
3873
3874 if ((ret & OPERATOR_RUNNING_MODAL) != 0) {
3875 ARegion *region = CTX_wm_region(C);
3876 if (region->regiontype == RGN_TYPE_WINDOW && event->type == LEFTMOUSE &&
3877 event->val == KM_PRESS)
3878 {
3879 wmGesture *gesture = static_cast<wmGesture *>(op->customdata);
3880 gesture->is_active = true;
3881 }
3882 }
3883
3884 return ret;
3885}
3886
3888{
3889 /* Identifiers. */
3890 ot->name = "Texture Gradient";
3891 ot->idname = "GREASE_PENCIL_OT_texture_gradient";
3892 ot->description = "Draw a line to set the fill material gradient for the selected strokes";
3893
3894 /* API callbacks. */
3900
3901 /* Flags. */
3902 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3903
3905}
3906
3908
3909/* -------------------------------------------------------------------- */
3912
3914{
3915 const Scene *scene = CTX_data_scene(C);
3916 Object *object = CTX_data_active_object(C);
3917 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
3918
3919 const CurveType dst_type = CurveType(RNA_enum_get(op->ptr, "type"));
3920 const bool use_handles = RNA_boolean_get(op->ptr, "use_handles");
3921
3922 bool changed = false;
3923 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
3924 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3926 IndexMaskMemory memory;
3928 *object, info.drawing, info.layer_index, memory);
3929 if (strokes.is_empty()) {
3930 return;
3931 }
3932
3934 options.convert_bezier_handles_to_poly_points = use_handles;
3935 options.convert_bezier_handles_to_catmull_rom_points = use_handles;
3936 options.keep_bezier_shape_as_nurbs = use_handles;
3937 options.keep_catmull_rom_shape_as_nurbs = use_handles;
3938
3939 curves = geometry::convert_curves(curves, strokes, dst_type, {}, options);
3941
3942 changed = true;
3943 });
3944
3945 if (changed) {
3946 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
3947 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
3948 }
3949
3950 return OPERATOR_FINISHED;
3951}
3952
3954{
3955 ot->name = "Set Curve Type";
3956 ot->idname = "GREASE_PENCIL_OT_set_curve_type";
3957 ot->description = "Set type of selected curves";
3958
3959 ot->invoke = WM_menu_invoke;
3962
3963 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3964
3965 ot->prop = RNA_def_enum(
3966 ot->srna, "type", rna_enum_curves_type_items, CURVE_TYPE_POLY, "Type", "Curve type");
3967
3968 RNA_def_boolean(ot->srna,
3969 "use_handles",
3970 false,
3971 "Handles",
3972 "Take handle information into account in the conversion");
3973}
3974
3976
3977/* -------------------------------------------------------------------- */
3980
3982{
3983 const Scene *scene = CTX_data_scene(C);
3984 Object *object = CTX_data_active_object(C);
3985 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
3986
3987 const HandleType dst_handle_type = HandleType(RNA_enum_get(op->ptr, "type"));
3988
3989 bool changed = false;
3990 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
3991 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3993 if (!curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
3994 return;
3995 }
3996 IndexMaskMemory memory;
3998 *object, info.drawing, info.layer_index, memory);
3999 const IndexMask bezier_curves = curves.indices_for_curve_type(
4000 CURVE_TYPE_BEZIER, editable_strokes, memory);
4001
4002 const bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
4003 const VArraySpan<bool> selection = *attributes.lookup_or_default<bool>(
4004 ".selection", bke::AttrDomain::Point, true);
4005 const VArraySpan<bool> selection_left = *attributes.lookup_or_default<bool>(
4006 ".selection_handle_left", bke::AttrDomain::Point, true);
4007 const VArraySpan<bool> selection_right = *attributes.lookup_or_default<bool>(
4008 ".selection_handle_right", bke::AttrDomain::Point, true);
4009
4010 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
4011 MutableSpan<int8_t> handle_types_left = curves.handle_types_left_for_write();
4012 MutableSpan<int8_t> handle_types_right = curves.handle_types_right_for_write();
4013 bezier_curves.foreach_index(GrainSize(256), [&](const int curve_i) {
4014 const IndexRange points = points_by_curve[curve_i];
4015 for (const int point_i : points) {
4016 if (selection_left[point_i] || selection[point_i]) {
4017 handle_types_left[point_i] = int8_t(dst_handle_type);
4018 }
4019 if (selection_right[point_i] || selection[point_i]) {
4020 handle_types_right[point_i] = int8_t(dst_handle_type);
4021 }
4022 }
4023 });
4024
4025 curves.calculate_bezier_auto_handles();
4026 curves.tag_topology_changed();
4028
4029 changed = true;
4030 });
4031
4032 if (changed) {
4033 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4034 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4035 }
4036
4037 return OPERATOR_FINISHED;
4038}
4039
4041{
4042 ot->name = "Set Handle Type";
4043 ot->idname = "GREASE_PENCIL_OT_set_handle_type";
4044 ot->description = "Set the handle type for bezier curves";
4045
4046 ot->invoke = WM_menu_invoke;
4049
4050 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4051
4052 ot->prop = RNA_def_enum(
4053 ot->srna, "type", rna_enum_curves_handle_type_items, CURVE_TYPE_POLY, "Type", nullptr);
4054}
4055
4057
4058/* -------------------------------------------------------------------- */
4061
4063{
4064 const Scene *scene = CTX_data_scene(C);
4065 Object *object = CTX_data_active_object(C);
4066 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
4067
4068 const int resolution = RNA_int_get(op->ptr, "resolution");
4069
4070 bool changed = false;
4071 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
4072 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4074 IndexMaskMemory memory;
4076 *object, info.drawing, info.layer_index, memory);
4077 if (editable_strokes.is_empty()) {
4078 return;
4079 }
4080
4081 if (curves.is_single_type(CURVE_TYPE_POLY)) {
4082 return;
4083 }
4084
4085 index_mask::masked_fill(curves.resolution_for_write(), resolution, editable_strokes);
4087 changed = true;
4088 });
4089
4090 if (changed) {
4091 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4092 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4093 }
4094
4095 return OPERATOR_FINISHED;
4096}
4097
4099{
4100 ot->name = "Set Curve Resolution";
4101 ot->idname = "GREASE_PENCIL_OT_set_curve_resolution";
4102 ot->description = "Set resolution of selected curves";
4103
4106
4107 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4108
4109 RNA_def_int(ot->srna,
4110 "resolution",
4111 12,
4112 0,
4113 10000,
4114 "Resolution",
4115 "The resolution to use for each curve segment",
4116 1,
4117 64);
4118}
4119
4121
4122/* -------------------------------------------------------------------- */
4125
4127{
4128 const Scene *scene = CTX_data_scene(C);
4129 Object *object = CTX_data_active_object(C);
4130 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
4131
4132 bool changed = false;
4133 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
4134 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4136 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
4137 IndexMaskMemory memory;
4139 *object, info.drawing, info.layer_index, memory);
4140 if (editable_strokes.is_empty()) {
4141 return;
4142 }
4143
4144 if (attributes.contains("uv_rotation")) {
4145 if (editable_strokes.size() == curves.curves_num()) {
4146 attributes.remove("uv_rotation");
4147 }
4148 else {
4149 bke::SpanAttributeWriter<float> uv_rotations = attributes.lookup_for_write_span<float>(
4150 "uv_rotation");
4151 index_mask::masked_fill(uv_rotations.span, 0.0f, editable_strokes);
4152 uv_rotations.finish();
4153 }
4154 }
4155
4156 if (attributes.contains("uv_translation")) {
4157 if (editable_strokes.size() == curves.curves_num()) {
4158 attributes.remove("uv_translation");
4159 }
4160 else {
4161 bke::SpanAttributeWriter<float2> uv_translations =
4162 attributes.lookup_for_write_span<float2>("uv_translation");
4163 index_mask::masked_fill(uv_translations.span, float2(0.0f, 0.0f), editable_strokes);
4164 uv_translations.finish();
4165 }
4166 }
4167
4168 if (attributes.contains("uv_scale")) {
4169 if (editable_strokes.size() == curves.curves_num()) {
4170 attributes.remove("uv_scale");
4171 }
4172 else {
4174 "uv_scale");
4175 index_mask::masked_fill(uv_scales.span, float2(1.0f, 1.0f), editable_strokes);
4176 uv_scales.finish();
4177 }
4178 }
4179
4180 if (attributes.contains("uv_shear")) {
4181 if (editable_strokes.size() == curves.curves_num()) {
4182 attributes.remove("uv_shear");
4183 }
4184 else {
4185 bke::SpanAttributeWriter<float> uv_shears = attributes.lookup_for_write_span<float>(
4186 "uv_shear");
4187 index_mask::masked_fill(uv_shears.span, 0.0f, editable_strokes);
4188 uv_shears.finish();
4189 }
4190 }
4191
4193 changed = true;
4194 });
4195
4196 if (changed) {
4197 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4198 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4199 }
4200
4201 return OPERATOR_FINISHED;
4202}
4203
4205{
4206 /* Identifiers. */
4207 ot->name = "Reset UVs";
4208 ot->idname = "GREASE_PENCIL_OT_reset_uvs";
4209 ot->description = "Reset UV transformation to default values";
4210
4211 /* Callbacks. */
4214
4215 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4216}
4217
4219{
4220 const Scene &scene = *CTX_data_scene(C);
4221 Object &object = *CTX_data_active_object(C);
4222 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
4223 std::atomic<bool> changed = false;
4224
4225 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
4226 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4227 IndexMaskMemory memory;
4228 const IndexMask selected_points =
4230 object, info.drawing, info.layer_index, memory);
4231
4232 if (selected_points.is_empty()) {
4233 return;
4234 }
4235
4237 selected_points);
4239 changed.store(true, std::memory_order_relaxed);
4240 });
4241
4242 if (changed) {
4243 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4244 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4245 return OPERATOR_FINISHED;
4246 }
4247
4248 return OPERATOR_CANCELLED;
4249}
4250
4252{
4253 /* Identifiers. */
4254 ot->name = "Split stroke";
4255 ot->idname = "GREASE_PENCIL_OT_stroke_split";
4256 ot->description = "Split selected points to a new stroke";
4257
4258 /* Callbacks. */
4261
4262 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4263}
4264
4266
4267/* -------------------------------------------------------------------- */
4270
4271enum class RemoveFillGuidesMode : int8_t { ActiveFrame = 0, AllFrames = 1 };
4272
4274{
4275 using namespace blender::bke::greasepencil;
4276 const Scene &scene = *CTX_data_scene(C);
4277 Object &object = *CTX_data_active_object(C);
4278 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
4279
4280 const RemoveFillGuidesMode mode = RemoveFillGuidesMode(RNA_enum_get(op->ptr, "mode"));
4281
4282 std::atomic<bool> changed = false;
4285 for (const int layer_i : grease_pencil.layers().index_range()) {
4286 const Layer &layer = grease_pencil.layer(layer_i);
4287 if (!layer.is_editable()) {
4288 continue;
4289 }
4290 if (Drawing *drawing = grease_pencil.get_editable_drawing_at(layer, scene.r.cfra)) {
4291 drawings.append({*drawing, layer_i, scene.r.cfra, 1.0f});
4292 }
4293 }
4294 }
4295 else if (mode == RemoveFillGuidesMode::AllFrames) {
4296 for (const int layer_i : grease_pencil.layers().index_range()) {
4297 const Layer &layer = grease_pencil.layer(layer_i);
4298 if (!layer.is_editable()) {
4299 continue;
4300 }
4301 for (const auto [frame_number, frame] : layer.frames().items()) {
4302 if (Drawing *drawing = grease_pencil.get_editable_drawing_at(layer, frame_number)) {
4303 drawings.append({*drawing, layer_i, frame_number, 1.0f});
4304 }
4305 }
4306 }
4307 }
4308 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4311 changed.store(true, std::memory_order_relaxed);
4312 }
4313 });
4314
4315 if (changed) {
4316 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4317 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4318 return OPERATOR_FINISHED;
4319 }
4320
4321 return OPERATOR_CANCELLED;
4322}
4323
4325{
4326 static const EnumPropertyItem rna_mode_items[] = {
4327 {int(RemoveFillGuidesMode::ActiveFrame), "ACTIVE_FRAME", 0, "Active Frame", ""},
4328 {int(RemoveFillGuidesMode::AllFrames), "ALL_FRAMES", 0, "All Frames", ""},
4329 {0, nullptr, 0, nullptr, nullptr},
4330 };
4331
4332 /* Identifiers. */
4333 ot->name = "Remove Fill Guides";
4334 ot->idname = "GREASE_PENCIL_OT_remove_fill_guides";
4335 ot->description = "Remove all the strokes that were created from the fill tool as guides";
4336
4337 /* Callbacks. */
4340
4341 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4342
4343 ot->prop = RNA_def_enum(
4344 ot->srna, "mode", rna_mode_items, int(RemoveFillGuidesMode::AllFrames), "Mode", "");
4345}
4346
4347/* -------------------------------------------------------------------- */
4350
4351enum class OutlineMode : int8_t {
4352 View = 0,
4354 Side = 2,
4355 Top = 3,
4358};
4359
4361 {int(OutlineMode::View), "VIEW", 0, "View", ""},
4362 {int(OutlineMode::Front), "FRONT", 0, "Front", ""},
4363 {int(OutlineMode::Side), "SIDE", 0, "Side", ""},
4364 {int(OutlineMode::Top), "TOP", 0, "Top", ""},
4365 {int(OutlineMode::Cursor), "CURSOR", 0, "Cursor", ""},
4366 {int(OutlineMode::Camera), "CAMERA", 0, "Camera", ""},
4367 {0, nullptr, 0, nullptr, nullptr},
4368};
4369
4371{
4373
4374 const Scene *scene = CTX_data_scene(C);
4375 Object *object = CTX_data_active_object(C);
4376 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
4377
4378 const float radius = RNA_float_get(op->ptr, "radius");
4379 const float offset_factor = RNA_float_get(op->ptr, "offset_factor");
4380 const int corner_subdivisions = RNA_int_get(op->ptr, "corner_subdivisions");
4381 const float outline_offset = radius * offset_factor;
4382 const int mat_nr = -1;
4383
4384 const OutlineMode mode = OutlineMode(RNA_enum_get(op->ptr, "type"));
4385
4386 float4x4 viewinv = float4x4::identity();
4387 switch (mode) {
4388 case OutlineMode::View: {
4390 viewinv = float4x4(rv3d->viewmat);
4391 break;
4392 }
4393 case OutlineMode::Front:
4394 viewinv = float4x4({1.0f, 0.0f, 0.0f, 0.0f},
4395 {0.0f, 0.0f, 1.0f, 0.0f},
4396 {0.0f, 1.0f, 0.0f, 0.0f},
4397 {0.0f, 0.0f, 0.0f, 1.0f});
4398 break;
4399 case OutlineMode::Side:
4400 viewinv = float4x4({0.0f, 0.0f, 1.0f, 0.0f},
4401 {0.0f, 1.0f, 0.0f, 0.0f},
4402 {1.0f, 0.0f, 0.0f, 0.0f},
4403 {0.0f, 0.0f, 0.0f, 1.0f});
4404 break;
4405 case OutlineMode::Top:
4406 viewinv = float4x4::identity();
4407 break;
4408 case OutlineMode::Cursor: {
4409 viewinv = scene->cursor.matrix<float4x4>();
4410 break;
4411 }
4413 viewinv = scene->camera->world_to_object();
4414 break;
4415 default:
4417 break;
4418 }
4419
4420 bool changed = false;
4421 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
4422 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4423 IndexMaskMemory memory;
4425 *object, info.drawing, info.layer_index, memory);
4426 if (editable_strokes.is_empty()) {
4427 return;
4428 }
4429
4430 const Layer &layer = grease_pencil.layer(info.layer_index);
4431 const float4x4 viewmat = viewinv * layer.to_world_space(*object);
4432
4434 editable_strokes,
4435 viewmat,
4436 corner_subdivisions,
4437 radius,
4438 outline_offset,
4439 mat_nr);
4440
4441 info.drawing.strokes_for_write().remove_curves(editable_strokes, {});
4442
4443 /* Join the outline stroke into the drawing. */
4444 Curves *strokes = bke::curves_new_nomain(std::move(outline));
4445
4446 Curves *other_curves = bke::curves_new_nomain(std::move(info.drawing.strokes_for_write()));
4447 const std::array<bke::GeometrySet, 2> geometry_sets = {
4449
4450 info.drawing.strokes_for_write() = std::move(
4451 geometry::join_geometries(geometry_sets, {}).get_curves_for_write()->geometry.wrap());
4452
4454 changed = true;
4455 });
4456
4457 if (changed) {
4458 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4459 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4460 }
4461
4462 return OPERATOR_FINISHED;
4463}
4464
4466{
4467 /* Identifiers. */
4468 ot->name = "Outline";
4469 ot->idname = "GREASE_PENCIL_OT_outline";
4470 ot->description = "Convert selected strokes to perimeter";
4471
4472 /* Callbacks. */
4475
4476 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4477
4478 /* Properties */
4479 ot->prop = RNA_def_enum(
4480 ot->srna, "type", prop_outline_modes, int(OutlineMode::View), "Projection Mode", "");
4481 RNA_def_float_distance(ot->srna, "radius", 0.01f, 0.0f, 10.0f, "Radius", "", 0.0f, 10.0f);
4483 ot->srna, "offset_factor", -1.0f, -1.0f, 1.0f, "Offset Factor", "", -1.0f, 1.0f);
4484 RNA_def_int(ot->srna, "corner_subdivisions", 2, 0, 10, "Corner Subdivisions", "", 0, 5);
4485}
4486
4488
4489/* -------------------------------------------------------------------- */
4492
4494 const IndexMask &selection,
4495 const float threshold)
4496{
4497 const VArray<float> thresholds = VArray<float>::ForSingle(threshold, curves.curves_num());
4498 /* TODO: Detect or manually provide corners. */
4499 const VArray<bool> corners = VArray<bool>::ForSingle(false, curves.points_num());
4501 curves, selection, thresholds, corners, geometry::FitMethod::Refit, {});
4502}
4503
4505 const IndexMask &selection,
4506 const float threshold)
4507{
4508 if (curves.is_single_type(CURVE_TYPE_CATMULL_ROM)) {
4509 return;
4510 }
4511 IndexMaskMemory memory;
4512 const IndexMask non_catmull_rom_curves_selection =
4513 curves.indices_for_curve_type(CURVE_TYPE_CATMULL_ROM, selection, memory)
4514 .complement(selection, memory);
4515 BLI_assert(!non_catmull_rom_curves_selection.is_empty());
4516 curves = geometry::resample_to_evaluated(curves, non_catmull_rom_curves_selection);
4517
4518 /* To avoid having too many control points, simplify the position attribute based on the
4519 * threshold. This doesn't replace an actual curve fitting (which would be better), but
4520 * is a decent approximation for the meantime. */
4521 const IndexMask points_to_remove = geometry::simplify_curve_attribute(
4522 curves.positions(),
4523 non_catmull_rom_curves_selection,
4524 curves.points_by_curve(),
4525 curves.cyclic(),
4526 threshold,
4527 curves.positions(),
4528 memory);
4529 curves.remove_points(points_to_remove, {});
4530
4532 options.convert_bezier_handles_to_poly_points = false;
4533 options.convert_bezier_handles_to_catmull_rom_points = false;
4534 options.keep_bezier_shape_as_nurbs = true;
4535 options.keep_catmull_rom_shape_as_nurbs = true;
4537 curves, non_catmull_rom_curves_selection, CURVE_TYPE_CATMULL_ROM, {}, options);
4538}
4539
4541{
4542 if (curves.is_single_type(CURVE_TYPE_POLY)) {
4543 return;
4544 }
4545 IndexMaskMemory memory;
4546 const IndexMask non_poly_curves_selection = curves
4547 .indices_for_curve_type(
4548 CURVE_TYPE_POLY, selection, memory)
4549 .complement(selection, memory);
4550 BLI_assert(!non_poly_curves_selection.is_empty());
4551 curves = geometry::resample_to_evaluated(curves, non_poly_curves_selection);
4552}
4553
4555 const IndexMask &selection,
4556 const float threshold)
4557{
4558 if (curves.is_single_type(CURVE_TYPE_BEZIER)) {
4559 return;
4560 }
4561 IndexMaskMemory memory;
4562 const IndexMask poly_curves_selection = curves.indices_for_curve_type(
4563 CURVE_TYPE_POLY, selection, memory);
4564 if (!poly_curves_selection.is_empty()) {
4565 curves = fit_poly_curves(curves, poly_curves_selection, threshold);
4566 }
4567
4569 options.convert_bezier_handles_to_poly_points = false;
4570 options.convert_bezier_handles_to_catmull_rom_points = false;
4571 options.keep_bezier_shape_as_nurbs = true;
4572 options.keep_catmull_rom_shape_as_nurbs = true;
4574}
4575
4577 const IndexMask &selection,
4578 const float threshold)
4579{
4580 if (curves.is_single_type(CURVE_TYPE_NURBS)) {
4581 return;
4582 }
4583
4584 IndexMaskMemory memory;
4585 const IndexMask poly_curves_selection = curves.indices_for_curve_type(
4586 CURVE_TYPE_POLY, selection, memory);
4587 if (!poly_curves_selection.is_empty()) {
4588 curves = fit_poly_curves(curves, poly_curves_selection, threshold);
4589 }
4590
4592 options.convert_bezier_handles_to_poly_points = false;
4593 options.convert_bezier_handles_to_catmull_rom_points = false;
4594 options.keep_bezier_shape_as_nurbs = true;
4595 options.keep_catmull_rom_shape_as_nurbs = true;
4597}
4598
4600{
4601 const Scene *scene = CTX_data_scene(C);
4602 Object *object = CTX_data_active_object(C);
4603 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
4604
4605 const CurveType dst_type = CurveType(RNA_enum_get(op->ptr, "type"));
4606 const float threshold = RNA_float_get(op->ptr, "threshold");
4607
4608 std::atomic<bool> changed = false;
4609 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
4610 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4612 IndexMaskMemory memory;
4614 *object, info.drawing, info.layer_index, memory);
4615 if (strokes.is_empty()) {
4616 return;
4617 }
4618
4619 switch (dst_type) {
4621 convert_to_catmull_rom(curves, strokes, threshold);
4622 break;
4623 case CURVE_TYPE_POLY:
4624 convert_to_poly(curves, strokes);
4625 break;
4626 case CURVE_TYPE_BEZIER:
4627 convert_to_bezier(curves, strokes, threshold);
4628 break;
4629 case CURVE_TYPE_NURBS:
4630 convert_to_nurbs(curves, strokes, threshold);
4631 break;
4632 }
4633
4635 changed.store(true, std::memory_order_relaxed);
4636 });
4637
4638 if (changed) {
4639 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4640 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4641 }
4642
4643 return OPERATOR_FINISHED;
4644}
4645
4647{
4648 uiLayout *layout = op->layout;
4650
4652
4653 uiLayoutSetPropSep(layout, true);
4654 uiLayoutSetPropDecorate(layout, false);
4655
4656 layout->prop(&ptr, "type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
4657
4658 const CurveType dst_type = CurveType(RNA_enum_get(op->ptr, "type"));
4659
4660 if (dst_type == CURVE_TYPE_POLY) {
4661 return;
4662 }
4663
4664 layout->prop(&ptr, "threshold", UI_ITEM_NONE, std::nullopt, ICON_NONE);
4665}
4666
4668{
4669 ot->name = "Convert Curve Type";
4670 ot->idname = "GREASE_PENCIL_OT_convert_curve_type";
4671 ot->description = "Convert type of selected curves";
4672
4673 ot->invoke = WM_menu_invoke;
4677
4678 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4679
4680 ot->prop = RNA_def_enum(
4681 ot->srna, "type", rna_enum_curves_type_items, CURVE_TYPE_POLY, "Type", "");
4683
4684 PropertyRNA *prop = RNA_def_float(
4685 ot->srna,
4686 "threshold",
4687 0.01f,
4688 0.0f,
4689 100.0f,
4690 "Threshold",
4691 "The distance that the resulting points are allowed to be within",
4692 0.0f,
4693 100.0f);
4695}
4696
4698
4699} // namespace blender::ed::greasepencil
4700
4702{
4703 using namespace blender::ed::greasepencil;
4742}
4743
4744/* -------------------------------------------------------------------- */
4747
4748namespace blender::ed::greasepencil {
4749
4750/* Note: the `duplicate_layer` API would be nicer, but only supports duplicating groups from the
4751 * same datablock. */
4754 const bke::greasepencil::Layer &layer_src)
4755{
4756 using namespace blender::bke::greasepencil;
4757
4758 Layer &layer_dst = grease_pencil_dst.add_layer(group_dst, layer_src.name());
4759 BKE_grease_pencil_copy_layer_parameters(layer_src, layer_dst);
4760
4761 layer_dst.frames_for_write() = layer_src.frames();
4762 layer_dst.tag_frames_map_changed();
4763
4764 return layer_dst;
4765}
4766
4768 GreasePencil &grease_pencil_dst,
4770 const bke::greasepencil::LayerGroup &group_src,
4771 Map<StringRefNull, StringRefNull> &layer_name_map);
4772
4773static void copy_layer_group_content(GreasePencil &grease_pencil_dst,
4775 const bke::greasepencil::LayerGroup &group_src,
4776 Map<StringRefNull, StringRefNull> &layer_name_map)
4777{
4778 using namespace blender::bke::greasepencil;
4779
4780 LISTBASE_FOREACH (GreasePencilLayerTreeNode *, child, &group_src.children) {
4781 switch (child->type) {
4782 case GP_LAYER_TREE_LEAF: {
4783 Layer &layer_src = reinterpret_cast<GreasePencilLayer *>(child)->wrap();
4784 Layer &layer_dst = copy_layer(grease_pencil_dst, group_dst, layer_src);
4785 layer_name_map.add_new(layer_src.name(), layer_dst.name());
4786 break;
4787 }
4788 case GP_LAYER_TREE_GROUP: {
4789 LayerGroup &group_src = reinterpret_cast<GreasePencilLayerTreeGroup *>(child)->wrap();
4790 copy_layer_group_recursive(grease_pencil_dst, group_dst, group_src, layer_name_map);
4791 break;
4792 }
4793 }
4794 }
4795}
4796
4798 GreasePencil &grease_pencil_dst,
4800 const bke::greasepencil::LayerGroup &group_src,
4801 Map<StringRefNull, StringRefNull> &layer_name_map)
4802{
4803 bke::greasepencil::LayerGroup &group_dst = grease_pencil_dst.add_layer_group(
4804 parent_dst, group_src.base.name);
4806
4807 copy_layer_group_content(grease_pencil_dst, group_dst, group_src, layer_name_map);
4808 return group_dst;
4809}
4810
4812{
4813 BLI_assert(object.type == OB_GREASE_PENCIL);
4814 Array<int> material_index_map(*BKE_object_material_len_p(&object));
4815 for (const int i : material_index_map.index_range()) {
4816 Material *material = BKE_object_material_get(&object, i + 1);
4817 if (material != nullptr) {
4818 material_index_map[i] = materials.index_of_or_add(material);
4819 }
4820 else {
4821 material_index_map[i] = 0;
4822 }
4823 }
4824 return material_index_map;
4825}
4826
4828 const Span<int> material_index_map)
4829{
4831 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
4832 /* Validate material indices and add missing materials. */
4833 bke::SpanAttributeWriter<int> material_writer = attributes.lookup_or_add_for_write_span<int>(
4834 "material_index", bke::AttrDomain::Curve);
4835 threading::parallel_for(curves.curves_range(), 1024, [&](const IndexRange range) {
4836 for (const int curve_i : range) {
4837 material_writer.span[curve_i] = material_index_map[material_writer.span[curve_i]];
4838 }
4839 });
4840 material_writer.finish();
4841}
4842
4844 GreasePencil &grease_pencil,
4845 const ListBase &vertex_group_names)
4846{
4847 Map<StringRefNull, StringRefNull> vertex_group_map;
4848 LISTBASE_FOREACH (bDeformGroup *, dg, &vertex_group_names) {
4849 bDeformGroup *vgroup = static_cast<bDeformGroup *>(MEM_dupallocN(dg));
4850 BKE_object_defgroup_unique_name(vgroup, &object);
4851 BLI_addtail(&grease_pencil.vertex_group_names, vgroup);
4852 vertex_group_map.add_new(dg->name, vgroup->name);
4853 }
4854 return vertex_group_map;
4855}
4856
4858 const Map<StringRefNull, StringRefNull> &vertex_group_map)
4859{
4861 STRNCPY(dg->name, vertex_group_map.lookup(dg->name).c_str());
4862 }
4863
4864 /* Indices in vertex weights remain valid, they are local to the drawing's vertex groups.
4865 * Only the names of the groups change. */
4866}
4867
4869 Object &ob_src,
4870 Object &ob_dst,
4871 VectorSet<Material *> &materials)
4872{
4873 using namespace blender::bke::greasepencil;
4874
4875 /* Skip if the datablock is already used by the active object. */
4876 if (ob_src.data == ob_dst.data) {
4877 return;
4878 }
4879
4880 BLI_assert(ob_src.type == OB_GREASE_PENCIL);
4881 BLI_assert(ob_dst.type == OB_GREASE_PENCIL);
4882 GreasePencil &grease_pencil_src = *static_cast<GreasePencil *>(ob_src.data);
4883 GreasePencil &grease_pencil_dst = *static_cast<GreasePencil *>(ob_dst.data);
4884 /* Number of existing layers that don't need to be updated. */
4885 const int orig_layers_num = grease_pencil_dst.layers().size();
4886
4888 ob_dst, grease_pencil_dst, grease_pencil_src.vertex_group_names);
4889 const Array<int> material_index_map = add_materials_to_map(ob_src, materials);
4890
4891 /* Concatenate drawing arrays. Existing drawings in dst keep their position, new drawings are
4892 * mapped to the new index range. */
4893 const int new_drawing_array_num = grease_pencil_dst.drawing_array_num +
4894 grease_pencil_src.drawing_array_num;
4895 GreasePencilDrawingBase **new_drawing_array = static_cast<GreasePencilDrawingBase **>(
4896 MEM_malloc_arrayN(new_drawing_array_num, sizeof(GreasePencilDrawingBase *), __func__));
4897 MutableSpan<GreasePencilDrawingBase *> new_drawings = {new_drawing_array, new_drawing_array_num};
4898 const IndexRange new_drawings_dst = IndexRange::from_begin_size(
4899 0, grease_pencil_dst.drawing_array_num);
4900 const IndexRange new_drawings_src = IndexRange::from_begin_size(
4901 grease_pencil_dst.drawing_array_num, grease_pencil_src.drawing_array_num);
4902
4903 copy_drawing_array(grease_pencil_dst.drawings(), new_drawings.slice(new_drawings_dst));
4904 copy_drawing_array(grease_pencil_src.drawings(), new_drawings.slice(new_drawings_src));
4905
4906 /* Free existing drawings array. */
4907 grease_pencil_dst.resize_drawings(0);
4908 grease_pencil_dst.drawing_array = new_drawing_array;
4909 grease_pencil_dst.drawing_array_num = new_drawing_array_num;
4910
4911 /* Maps original names of source layers to new unique layer names. */
4912 Map<StringRefNull, StringRefNull> layer_name_map;
4913 /* Only copy the content of the root group, not the root node itself. */
4914 copy_layer_group_content(grease_pencil_dst,
4915 grease_pencil_dst.root_group(),
4916 grease_pencil_src.root_group(),
4917 layer_name_map);
4918
4919 /* Copy custom attributes for new layers. */
4920 CustomData_merge_layout(&grease_pencil_src.layers_data,
4921 &grease_pencil_dst.layers_data,
4924 grease_pencil_dst.layers().size());
4925 CustomData_copy_data(&grease_pencil_src.layers_data,
4926 &grease_pencil_dst.layers_data,
4927 0,
4928 orig_layers_num,
4929 grease_pencil_src.layers().size());
4930
4931 /* Fix names, indices and transforms to keep relationships valid. */
4932 for (const int layer_index : grease_pencil_dst.layers().index_range()) {
4933 Layer &layer = *grease_pencil_dst.layers_for_write()[layer_index];
4934 const bool is_orig_layer = (layer_index < orig_layers_num);
4935 const float4x4 old_layer_to_world = (is_orig_layer ? layer.to_world_space(ob_dst) :
4936 layer.to_world_space(ob_src));
4937
4938 /* Update newly added layers. */
4939 if (!is_orig_layer) {
4940 /* Update name references for masks. */
4941 LISTBASE_FOREACH (GreasePencilLayerMask *, dst_mask, &layer.masks) {
4942 const StringRefNull *new_mask_name = layer_name_map.lookup_ptr(dst_mask->layer_name);
4943 if (new_mask_name) {
4944 MEM_SAFE_FREE(dst_mask->layer_name);
4945 dst_mask->layer_name = BLI_strdup(new_mask_name->c_str());
4946 }
4947 }
4948 /* Shift drawing indices to match the new drawings array. */
4949 for (const int key : layer.frames_for_write().keys()) {
4950 int &drawing_index = layer.frames_for_write().lookup(key).drawing_index;
4951 drawing_index = new_drawings_src[drawing_index];
4952 }
4953 }
4954
4955 /* Layer parent object may become invalid. This can be an original layer pointing at the joined
4956 * object which gets destroyed, or a new layer that points at the target object which is now
4957 * its owner. */
4958 if (ELEM(layer.parent, &ob_dst, &ob_src)) {
4959 layer.parent = nullptr;
4960 }
4961
4962 /* Apply relative object transform to new drawings to keep world-space positions unchanged.
4963 * Be careful where the matrix is computed: changing the parent pointer (above) can affect
4964 * this! */
4965 const float4x4 new_layer_to_world = layer.to_world_space(ob_dst);
4966 for (const int key : layer.frames_for_write().keys()) {
4967 const int drawing_index = layer.frames_for_write().lookup(key).drawing_index;
4968 GreasePencilDrawingBase *drawing_base = grease_pencil_dst.drawings()[drawing_index];
4969 if (drawing_base->type != GP_DRAWING) {
4970 continue;
4971 }
4972 Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base)->wrap();
4974 curves.transform(math::invert(new_layer_to_world) * old_layer_to_world);
4975
4976 if (!is_orig_layer) {
4977 remap_vertex_groups(drawing, vertex_group_map);
4978 remap_material_indices(drawing, material_index_map);
4979 }
4980 }
4981 }
4982
4983 /* Rename animation paths to layers. */
4984 BKE_fcurves_main_cb(&bmain, [&](ID *id, FCurve *fcu) {
4985 if (id == &grease_pencil_src.id && fcu->rna_path && strstr(fcu->rna_path, "layers[")) {
4986 /* Have to use linear search, the layer name map only contains sub-strings of RNA paths. */
4987 for (auto [name_src, name_dst] : layer_name_map.items()) {
4988 if (name_dst != name_src) {
4989 const char *old_path = fcu->rna_path;
4991 id, fcu->rna_path, "layers", name_src.c_str(), name_dst.c_str(), 0, 0, false);
4992 if (old_path != fcu->rna_path) {
4993 /* Stop after first match. */
4994 break;
4995 }
4996 }
4997 }
4998 }
4999 /* Fix driver targets. */
5000 if (fcu->driver) {
5001 LISTBASE_FOREACH (DriverVar *, dvar, &fcu->driver->variables) {
5002 /* Only change the used targets, since the others will need fixing manually anyway. */
5004 if (dtar->id != &grease_pencil_src.id) {
5005 continue;
5006 }
5007 dtar->id = &grease_pencil_dst.id;
5008
5009 if (dtar->rna_path && strstr(dtar->rna_path, "layers[")) {
5010 for (auto [name_src, name_dst] : layer_name_map.items()) {
5011 if (name_dst != name_src) {
5012 const char *old_path = fcu->rna_path;
5013 dtar->rna_path = BKE_animsys_fix_rna_path_rename(
5014 id, dtar->rna_path, "layers", name_src.c_str(), name_dst.c_str(), 0, 0, false);
5015 if (old_path != dtar->rna_path) {
5016 break;
5017 }
5018 }
5019 }
5020 }
5021 }
5023 }
5024 }
5025 });
5026
5027 /* Merge animation data of objects and grease pencil datablocks. */
5028 if (ob_src.adt) {
5029 if (ob_dst.adt == nullptr) {
5030 ob_dst.adt = BKE_animdata_copy(&bmain, ob_src.adt, 0);
5031 }
5032 else {
5033 BKE_animdata_merge_copy(&bmain, &ob_dst.id, &ob_src.id, ADT_MERGECOPY_KEEP_DST, false);
5034 }
5035
5036 if (ob_dst.adt->action) {
5038 }
5039 }
5040 if (grease_pencil_src.adt) {
5041 if (grease_pencil_dst.adt == nullptr) {
5042 grease_pencil_dst.adt = BKE_animdata_copy(&bmain, grease_pencil_src.adt, 0);
5043 }
5044 else {
5046 &bmain, &grease_pencil_dst.id, &grease_pencil_src.id, ADT_MERGECOPY_KEEP_DST, false);
5047 }
5048
5049 if (grease_pencil_dst.adt->action) {
5051 }
5052 }
5053}
5054
5055} // namespace blender::ed::greasepencil
5056
5058{
5059 Main *bmain = CTX_data_main(C);
5060 Scene *scene = CTX_data_scene(C);
5061 Object *ob_active = CTX_data_active_object(C);
5062
5063 /* Ensure we're in right mode and that the active object is correct. */
5064 if (!ob_active || ob_active->type != OB_GREASE_PENCIL) {
5065 return OPERATOR_CANCELLED;
5066 }
5067
5068 bool ok = false;
5069 CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) {
5070 if (ob_iter == ob_active) {
5071 ok = true;
5072 break;
5073 }
5074 }
5076 /* Active object must always selected. */
5077 if (ok == false) {
5078 BKE_report(op->reports, RPT_WARNING, "Active object is not a selected Grease Pencil");
5079 return OPERATOR_CANCELLED;
5080 }
5081
5082 Object *ob_dst = ob_active;
5083 GreasePencil *grease_pencil_dst = static_cast<GreasePencil *>(ob_dst->data);
5084
5087 *ob_dst, materials);
5088 /* Reassign material indices in the original layers, in case materials are deduplicated. */
5089 for (GreasePencilDrawingBase *drawing_base : grease_pencil_dst->drawings()) {
5090 if (drawing_base->type != GP_DRAWING) {
5091 continue;
5092 }
5094 reinterpret_cast<GreasePencilDrawing *>(drawing_base)->wrap();
5095 blender::ed::greasepencil::remap_material_indices(drawing, material_index_map);
5096 }
5097
5098 /* Loop and join all data. */
5099 CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) {
5100 if (ob_iter->type != OB_GREASE_PENCIL || ob_iter == ob_active) {
5101 continue;
5102 }
5103
5104 blender::ed::greasepencil::join_object_with_active(*bmain, *ob_iter, *ob_dst, materials);
5105
5106 /* Free the old object. */
5107 blender::ed::object::base_free_and_unlink(bmain, scene, ob_iter);
5108 }
5110
5111 /* Transfer material pointers. The material indices are updated for each drawing separately. */
5112 if (!materials.is_empty()) {
5113 /* Old C API, needs a const_cast but doesn't actually change anything. */
5114 Material **materials_ptr = const_cast<Material **>(materials.data());
5116 bmain, DEG_get_original(ob_dst), &materials_ptr, materials.size(), false);
5117 }
5118
5119 DEG_id_tag_update(&grease_pencil_dst->id, ID_RECALC_GEOMETRY);
5121
5124
5125 return OPERATOR_FINISHED;
5126}
5127
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:446
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:894
#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
@ OPTYPE_UNDO
Definition WM_types.hh:182
@ OPTYPE_REGISTER
Definition WM_types.hh:180
#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
@ KM_PRESS
Definition WM_types.hh:308
@ KM_RELEASE
Definition WM_types.hh:309
#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
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:4226
wmOperatorType * ot
Definition wm_files.cc:4225
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 *)