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"
12#include "BLI_index_mask.hh"
13#include "BLI_index_range.hh"
14#include "BLI_listbase.h"
15#include "BLI_math_base.hh"
16#include "BLI_math_matrix.hh"
17#include "BLI_math_vector.hh"
19#include "BLI_offset_indices.hh"
20#include "BLI_span.hh"
21#include "BLI_string.h"
22#include "BLI_utildefines.h"
23#include "BLI_vector.hh"
24#include "BLT_translation.hh"
25
26#include "DNA_anim_types.h"
27#include "DNA_array_utils.hh"
29#include "DNA_material_types.h"
30#include "DNA_object_types.h"
31#include "DNA_scene_types.h"
32#include "DNA_space_types.h"
33#include "DNA_view3d_types.h"
35
36#include "BKE_anim_data.hh"
37#include "BKE_animsys.h"
38#include "BKE_attribute.hh"
39#include "BKE_context.hh"
40#include "BKE_curves_utils.hh"
41#include "BKE_customdata.hh"
42#include "BKE_deform.hh"
43#include "BKE_fcurve_driver.h"
44#include "BKE_grease_pencil.hh"
45#include "BKE_instances.hh"
46#include "BKE_lib_id.hh"
47#include "BKE_main.hh"
48#include "BKE_material.hh"
49#include "BKE_preview_image.hh"
50#include "BKE_report.hh"
51#include "BKE_scene.hh"
52
53#include "RNA_access.hh"
54#include "RNA_define.hh"
55#include "RNA_enum_types.hh"
56
57#include "DEG_depsgraph.hh"
59
60#include "ED_curves.hh"
61#include "ED_grease_pencil.hh"
62#include "ED_object.hh"
64#include "ED_view3d.hh"
65
67#include "GEO_fit_curves.hh"
70#include "GEO_reorder.hh"
72#include "GEO_set_curve_type.hh"
74#include "GEO_smooth_curves.hh"
76
77#include "UI_interface_c.hh"
78
79#include "UI_resources.hh"
80#include <limits>
81
83
84/* -------------------------------------------------------------------- */
87
89{
90 const Scene *scene = CTX_data_scene(C);
92 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
93
94 const int iterations = RNA_int_get(op->ptr, "iterations");
95 const float influence = RNA_float_get(op->ptr, "factor");
96 const bool keep_shape = RNA_boolean_get(op->ptr, "keep_shape");
97 const bool smooth_ends = RNA_boolean_get(op->ptr, "smooth_ends");
98
99 const bool smooth_position = RNA_boolean_get(op->ptr, "smooth_position");
100 const bool smooth_radius = RNA_boolean_get(op->ptr, "smooth_radius");
101 const bool smooth_opacity = RNA_boolean_get(op->ptr, "smooth_opacity");
102
103 if (!(smooth_position || smooth_radius || smooth_opacity)) {
104 /* There's nothing to be smoothed, return. */
105 return OPERATOR_FINISHED;
106 }
107
108 bool changed = false;
109 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
110 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
112 if (curves.is_empty()) {
113 return;
114 }
115
116 IndexMaskMemory memory;
118 *object, info.drawing, info.layer_index, memory);
119 if (strokes.is_empty()) {
120 return;
121 }
122
123 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
124 const OffsetIndices points_by_curve = curves.points_by_curve();
125 const VArray<bool> cyclic = curves.cyclic();
126 const VArray<bool> point_selection = *curves.attributes().lookup_or_default<bool>(
127 ".selection", bke::AttrDomain::Point, true);
128
129 if (smooth_position) {
130 bke::GSpanAttributeWriter positions = attributes.lookup_for_write_span("position");
132 points_by_curve,
133 point_selection,
134 cyclic,
135 iterations,
136 influence,
137 smooth_ends,
138 keep_shape,
139 positions.span);
140 positions.finish();
141 changed = true;
142 }
143 if (smooth_opacity && info.drawing.opacities().is_span()) {
144 bke::GSpanAttributeWriter opacities = attributes.lookup_for_write_span("opacity");
146 points_by_curve,
147 point_selection,
148 cyclic,
149 iterations,
150 influence,
151 smooth_ends,
152 false,
153 opacities.span);
154 opacities.finish();
155 changed = true;
156 }
157 if (smooth_radius && info.drawing.radii().is_span()) {
158 bke::GSpanAttributeWriter radii = attributes.lookup_for_write_span("radius");
160 points_by_curve,
161 point_selection,
162 cyclic,
163 iterations,
164 influence,
165 smooth_ends,
166 false,
167 radii.span);
168 radii.finish();
169 changed = true;
170 }
171 });
172
173 if (changed) {
174 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
175 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
176 }
177
178 return OPERATOR_FINISHED;
179}
180
182{
183 PropertyRNA *prop;
184
185 ot->name = "Smooth Stroke";
186 ot->idname = "GREASE_PENCIL_OT_stroke_smooth";
187 ot->description = "Smooth selected strokes";
188
191
193
194 prop = RNA_def_int(ot->srna, "iterations", 10, 1, 100, "Iterations", "", 1, 30);
196 RNA_def_float(ot->srna, "factor", 1.0f, 0.0f, 1.0f, "Factor", "", 0.0f, 1.0f);
197 RNA_def_boolean(ot->srna, "smooth_ends", false, "Smooth Endpoints", "");
198 RNA_def_boolean(ot->srna, "keep_shape", false, "Keep Shape", "");
199
200 RNA_def_boolean(ot->srna, "smooth_position", true, "Position", "");
201 RNA_def_boolean(ot->srna, "smooth_radius", true, "Radius", "");
202 RNA_def_boolean(ot->srna, "smooth_opacity", false, "Opacity", "");
203}
204
206
207/* -------------------------------------------------------------------- */
210
211enum class SimplifyMode {
212 FIXED = 0,
215 MERGE = 3,
216};
217
220 "FIXED",
221 0,
222 "Fixed",
223 "Delete alternating vertices in the stroke, except extremes"},
225 "ADAPTIVE",
226 0,
227 "Adaptive",
228 "Use a Ramer-Douglas-Peucker algorithm to simplify the stroke preserving main shape"},
230 "SAMPLE",
231 0,
232 "Sample",
233 "Re-sample the stroke with segments of the specified length"},
235 "MERGE",
236 0,
237 "Merge",
238 "Simplify the stroke by merging vertices closer than a given distance"},
239 {0, nullptr, 0, nullptr, nullptr},
240};
241
243 const int step,
244 const IndexMask &stroke_selection,
245 IndexMaskMemory &memory)
246{
247 const OffsetIndices points_by_curve = curves.points_by_curve();
248 const Array<int> point_to_curve_map = curves.point_to_curve_map();
249
250 const IndexMask selected_points = IndexMask::from_ranges(
251 points_by_curve, stroke_selection, memory);
252
253 /* Find points to keep among selected points. */
254 const IndexMask selected_to_keep = IndexMask::from_predicate(
255 selected_points, GrainSize(2048), memory, [&](const int64_t i) {
256 const int curve_i = point_to_curve_map[i];
257 const IndexRange points = points_by_curve[curve_i];
258 if (points.size() <= 2) {
259 return true;
260 }
261 const int local_i = i - points.start();
262 return (local_i % int(math::pow(2.0f, float(step))) == 0) || points.last() == i;
263 });
264
265 /* All the points that are not selected are also kept. */
267 {selected_to_keep, selected_points.complement(curves.points_range(), memory)}, memory);
268}
269
271{
272 const Scene *scene = CTX_data_scene(C);
274 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
275
276 const SimplifyMode mode = SimplifyMode(RNA_enum_get(op->ptr, "mode"));
277
278 bool changed = false;
279 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
280 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
282 if (curves.is_empty()) {
283 return;
284 }
285
286 IndexMaskMemory memory;
288 *object, info.drawing, info.layer_index, memory);
289 if (strokes.is_empty()) {
290 return;
291 }
292
293 switch (mode) {
294 case SimplifyMode::FIXED: {
295 const int steps = RNA_int_get(op->ptr, "steps");
296 const IndexMask points_to_keep = simplify_fixed(curves, steps, strokes, memory);
297 if (points_to_keep.is_empty()) {
298 info.drawing.strokes_for_write() = {};
299 break;
300 }
301 if (points_to_keep.size() == curves.points_num()) {
302 break;
303 }
305 curves, points_to_keep, {});
307 changed = true;
308 break;
309 }
311 const float simplify_factor = RNA_float_get(op->ptr, "factor");
312 const IndexMask points_to_delete = geometry::simplify_curve_attribute(
313 curves.positions(),
314 strokes,
315 curves.points_by_curve(),
316 curves.cyclic(),
317 simplify_factor,
318 curves.positions(),
319 memory);
320 info.drawing.strokes_for_write().remove_points(points_to_delete, {});
322 changed = true;
323 break;
324 }
326 const float resample_length = RNA_float_get(op->ptr, "length");
328 curves, strokes, VArray<float>::ForSingle(resample_length, curves.curves_num()), {});
330 changed = true;
331 break;
332 }
333 case SimplifyMode::MERGE: {
334 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
335 const Array<int> point_to_curve_map = curves.point_to_curve_map();
336 const float merge_distance = RNA_float_get(op->ptr, "distance");
337 const IndexMask selected_points = IndexMask::from_ranges(points_by_curve, strokes, memory);
338 const IndexMask filtered_points = IndexMask::from_predicate(
339 selected_points, GrainSize(2048), memory, [&](const int64_t i) {
340 const int curve_i = point_to_curve_map[i];
341 const IndexRange points = points_by_curve[curve_i];
342 if (points.drop_front(1).drop_back(1).contains(i)) {
343 return true;
344 }
345 return false;
346 });
348 curves, merge_distance, filtered_points, {});
350 changed = true;
351 break;
352 }
353 default:
354 break;
355 }
356 });
357
358 if (changed) {
359 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
360 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
361 }
362 return OPERATOR_FINISHED;
363}
364
366{
367 uiLayout *layout = op->layout;
369
371
372 uiLayoutSetPropSep(layout, true);
373 uiLayoutSetPropDecorate(layout, false);
374
375 layout->prop(&ptr, "mode", UI_ITEM_NONE, std::nullopt, ICON_NONE);
376
377 const SimplifyMode mode = SimplifyMode(RNA_enum_get(op->ptr, "mode"));
378
379 switch (mode) {
381 layout->prop(&ptr, "steps", UI_ITEM_NONE, std::nullopt, ICON_NONE);
382 break;
384 layout->prop(&ptr, "factor", UI_ITEM_NONE, std::nullopt, ICON_NONE);
385 break;
387 layout->prop(&ptr, "length", UI_ITEM_NONE, std::nullopt, ICON_NONE);
388 break;
390 layout->prop(&ptr, "distance", UI_ITEM_NONE, std::nullopt, ICON_NONE);
391 break;
392 default:
393 break;
394 }
395}
396
398{
399 PropertyRNA *prop;
400
401 ot->name = "Simplify Stroke";
402 ot->idname = "GREASE_PENCIL_OT_stroke_simplify";
403 ot->description = "Simplify selected strokes";
404
407
409
411
412 prop = RNA_def_float(ot->srna, "factor", 0.01f, 0.0f, 100.0f, "Factor", "", 0.0f, 100.0f);
414 prop = RNA_def_float(ot->srna, "length", 0.05f, 0.01f, 100.0f, "Length", "", 0.01f, 1.0f);
416 prop = RNA_def_float(ot->srna, "distance", 0.01f, 0.0f, 100.0f, "Distance", "", 0.0f, 1.0f);
418 prop = RNA_def_int(ot->srna, "steps", 1, 0, 50, "Steps", "", 0.0f, 10);
420 prop = RNA_def_enum(ot->srna,
421 "mode",
423 0,
424 "Mode",
425 "Method used for simplifying stroke points");
427}
428
430
431/* -------------------------------------------------------------------- */
434
436{
437 const Scene *scene = CTX_data_scene(C);
439 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
440
442 scene->toolsettings);
443
444 bool changed = false;
445 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
446 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
447 IndexMaskMemory memory;
449 *object, info.drawing, info.layer_index, selection_domain, memory);
450 if (elements.is_empty()) {
451 return;
452 }
453
455 if (selection_domain == bke::AttrDomain::Curve) {
456 curves.remove_curves(elements, {});
457 }
458 else if (selection_domain == bke::AttrDomain::Point) {
460 }
462 changed = true;
463 });
464
465 if (changed) {
466 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
467 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
468 }
469 return OPERATOR_FINISHED;
470}
471
473{
474 ot->name = "Delete";
475 ot->idname = "GREASE_PENCIL_OT_delete";
476 ot->description = "Delete selected strokes or points";
477
480
482}
483
485
486/* -------------------------------------------------------------------- */
489
490enum class DissolveMode : int8_t {
497};
498
500 {int(DissolveMode::POINTS), "POINTS", 0, "Dissolve", "Dissolve selected points"},
502 "BETWEEN",
503 0,
504 "Dissolve Between",
505 "Dissolve points between selected points"},
507 "UNSELECT",
508 0,
509 "Dissolve Unselect",
510 "Dissolve all unselected points"},
511 {0, nullptr, 0, nullptr, nullptr},
512};
513
515 const IndexMask &mask,
516 const DissolveMode mode)
517{
518 const VArray<bool> selection = *curves.attributes().lookup_or_default<bool>(
519 ".selection", bke::AttrDomain::Point, true);
520
521 Array<bool> points_to_dissolve(curves.points_num(), false);
522 selection.materialize(mask, points_to_dissolve);
523
524 if (mode == DissolveMode::POINTS) {
525 return points_to_dissolve;
526 }
527
528 /* Both `between` and `unselect` have the unselected point being the ones dissolved so we need
529 * to invert. */
531
532 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
533 /* Because we are going to invert, these become the points to keep. */
534 MutableSpan<bool> points_to_keep = points_to_dissolve.as_mutable_span();
535
536 threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) {
537 for (const int64_t curve_i : range) {
538 const IndexRange points = points_by_curve[curve_i];
539 const Span<bool> curve_selection = points_to_dissolve.as_span().slice(points);
540 /* The unselected curves should not be dissolved. */
541 if (!curve_selection.contains(true)) {
542 points_to_keep.slice(points).fill(true);
543 continue;
544 }
545
546 /* `between` is just `unselect` but with the first and last segments not getting
547 * dissolved. */
548 if (mode != DissolveMode::BETWEEN) {
549 continue;
550 }
551
552 const Vector<IndexRange> deselection_ranges = array_utils::find_all_ranges(curve_selection,
553 false);
554
555 if (deselection_ranges.size() != 0) {
556 const IndexRange first_range = deselection_ranges.first().shift(points.first());
557 const IndexRange last_range = deselection_ranges.last().shift(points.first());
558
559 /* Ranges should only be fill if the first/last point matches the start/end point
560 * of the segment. */
561 if (first_range.first() == points.first()) {
562 points_to_keep.slice(first_range).fill(true);
563 }
564 if (last_range.last() == points.last()) {
565 points_to_keep.slice(last_range).fill(true);
566 }
567 }
568 }
569 });
570
571 array_utils::invert_booleans(points_to_dissolve);
572
573 return points_to_dissolve;
574}
575
577{
578 const Scene *scene = CTX_data_scene(C);
580 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
581
582 const DissolveMode mode = DissolveMode(RNA_enum_get(op->ptr, "type"));
583
584 bool changed = false;
585 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
586 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
588 if (curves.is_empty()) {
589 return;
590 }
591
592 IndexMaskMemory memory;
594 *object, info.drawing, info.layer_index, memory);
595 if (points.is_empty()) {
596 return;
597 }
598
599 const Array<bool> points_to_dissolve = get_points_to_dissolve(curves, points, mode);
600 if (points_to_dissolve.as_span().contains(true)) {
601 curves.remove_points(IndexMask::from_bools(points_to_dissolve, memory), {});
603 changed = true;
604 }
605 });
606
607 if (changed) {
608 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
609 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
610 }
611 return OPERATOR_FINISHED;
612}
613
615{
616 PropertyRNA *prop;
617
618 ot->name = "Dissolve";
619 ot->idname = "GREASE_PENCIL_OT_dissolve";
620 ot->description = "Delete selected points without splitting strokes";
621
622 ot->invoke = WM_menu_invoke;
625
627
628 ot->prop = prop = RNA_def_enum(ot->srna,
629 "type",
631 0,
632 "Type",
633 "Method used for dissolving stroke points");
636}
637
639
640/* -------------------------------------------------------------------- */
643
644enum class DeleteFrameMode : int8_t {
649};
650
653 "ACTIVE_FRAME",
654 0,
655 "Active Frame",
656 "Deletes current frame in the active layer"},
658 "ALL_FRAMES",
659 0,
660 "All Active Frames",
661 "Delete active frames for all layers"},
662 {0, nullptr, 0, nullptr, nullptr},
663};
664
666{
667 const Scene *scene = CTX_data_scene(C);
669 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
670 const int current_frame = scene->r.cfra;
671
672 const DeleteFrameMode mode = DeleteFrameMode(RNA_enum_get(op->ptr, "type"));
673
674 bool changed = false;
675 if (mode == DeleteFrameMode::ACTIVE_FRAME && grease_pencil.has_active_layer()) {
676 bke::greasepencil::Layer &layer = *grease_pencil.get_active_layer();
677 if (layer.is_editable() && layer.start_frame_at(current_frame)) {
678 changed |= grease_pencil.remove_frames(layer, {*layer.start_frame_at(current_frame)});
679 }
680 }
681 else if (mode == DeleteFrameMode::ALL_FRAMES) {
682 for (bke::greasepencil::Layer *layer : grease_pencil.layers_for_write()) {
683 if (layer->is_editable() && layer->start_frame_at(current_frame)) {
684 changed |= grease_pencil.remove_frames(*layer, {*layer->start_frame_at(current_frame)});
685 }
686 }
687 }
688
689 if (changed) {
690 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
691 WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_EDITED, &grease_pencil);
693 }
694
695 return OPERATOR_FINISHED;
696}
697
699{
700 PropertyRNA *prop;
701
702 ot->name = "Delete Frame";
703 ot->idname = "GREASE_PENCIL_OT_delete_frame";
704 ot->description = "Delete Grease Pencil Frame(s)";
705
706 ot->invoke = WM_menu_invoke;
709
711
712 ot->prop = prop = RNA_def_enum(ot->srna,
713 "type",
715 0,
716 "Type",
717 "Method used for deleting Grease Pencil frames");
719}
720
721
722/* -------------------------------------------------------------------- */
725
727{
728 using namespace blender;
729 Main *bmain = CTX_data_main(C);
730 const Scene *scene = CTX_data_scene(C);
732 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
733 Material *ma = nullptr;
734 char name[MAX_ID_NAME - 2];
735 RNA_string_get(op->ptr, "material", name);
736
737 int material_index = object->actcol - 1;
738
739 if (name[0] != '\0') {
740 ma = reinterpret_cast<Material *>(BKE_libblock_find_name(bmain, ID_MA, name));
741 if (ma == nullptr) {
742 BKE_reportf(op->reports, RPT_WARNING, TIP_("Material '%s' could not be found"), name);
743 return OPERATOR_CANCELLED;
744 }
745
746 /* Find slot index. */
747 material_index = BKE_object_material_index_get(object, ma);
748 }
749
750 if (material_index == -1) {
751 return OPERATOR_CANCELLED;
752 }
753
754 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
755 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
756 IndexMaskMemory memory;
758 *object, info.drawing, info.layer_index, memory);
759 if (strokes.is_empty()) {
760 return;
761 }
762
765 curves.attributes_for_write().lookup_or_add_for_write_span<int>("material_index",
767 index_mask::masked_fill(materials.span, material_index, strokes);
768 materials.finish();
769 });
770
771 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
772 WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_EDITED, &grease_pencil);
773
774 return OPERATOR_FINISHED;
775}
776
778{
779 ot->name = "Assign Material";
780 ot->idname = "GREASE_PENCIL_OT_stroke_material_set";
781 ot->description = "Assign the active material slot to the selected strokes";
782
785
787
788 ot->prop = RNA_def_string(
789 ot->srna, "material", nullptr, MAX_ID_NAME - 2, "Material", "Name of the material");
791}
792
793
794/* -------------------------------------------------------------------- */
797
798enum class CyclicalMode : int8_t {
800 CLOSE = 0,
802 OPEN = 1,
805};
806
808 {int(CyclicalMode::CLOSE), "CLOSE", 0, "Close All", ""},
809 {int(CyclicalMode::OPEN), "OPEN", 0, "Open All", ""},
810 {int(CyclicalMode::TOGGLE), "TOGGLE", 0, "Toggle", ""},
811 {0, nullptr, 0, nullptr, nullptr},
812};
813
815 const IndexMask &strokes)
816{
817 const VArray<bool> cyclic = curves.cyclic();
818 const Span<float3> positions = curves.positions();
819 curves.ensure_evaluated_lengths();
820
821 Array<int> use_cuts(curves.points_num(), 0);
822 const OffsetIndices points_by_curve = curves.points_by_curve();
823
824 strokes.foreach_index(GrainSize(4096), [&](const int curve_i) {
825 if (cyclic[curve_i]) {
826 const IndexRange points = points_by_curve[curve_i];
827 const float end_distance = math::distance(positions[points.first()],
828 positions[points.last()]);
829
830 /* Because the curve is already cyclical the last segment has to be subtracted. */
831 const float curve_length = curves.evaluated_length_total_for_curve(curve_i, true) -
832 end_distance;
833
834 /* Calculate cuts to match the average density. */
835 const float point_density = float(points.size()) / curve_length;
836 use_cuts[points.last()] = int(point_density * end_distance);
837 }
838 });
839
840 const VArray<int> cuts = VArray<int>::ForSpan(use_cuts.as_span());
841
842 return geometry::subdivide_curves(curves, strokes, cuts);
843}
844
846{
847 const Scene *scene = CTX_data_scene(C);
849 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
850
851 const CyclicalMode mode = CyclicalMode(RNA_enum_get(op->ptr, "type"));
852 const bool subdivide_cyclic_segment = RNA_boolean_get(op->ptr, "subdivide_cyclic_segment");
853
854 bool changed = false;
855 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
856 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
858 if (mode == CyclicalMode::OPEN && !curves.attributes().contains("cyclic")) {
859 /* Avoid creating unneeded attribute. */
860 return;
861 }
862
863 IndexMaskMemory memory;
865 *object, info.drawing, info.layer_index, memory);
866 if (strokes.is_empty()) {
867 return;
868 }
869
870 MutableSpan<bool> cyclic = curves.cyclic_for_write();
871 switch (mode) {
873 index_mask::masked_fill(cyclic, true, strokes);
874 break;
876 index_mask::masked_fill(cyclic, false, strokes);
877 break;
879 array_utils::invert_booleans(cyclic, strokes);
880 break;
881 }
882
883 /* Remove the attribute if it is empty. */
884 if (mode != CyclicalMode::CLOSE) {
886 curves.attributes_for_write().remove("cyclic");
887 }
888 }
889
890 if (subdivide_cyclic_segment) {
891 /* Update to properly calculate the lengths. */
892 curves.tag_topology_changed();
893
895 }
896
898 changed = true;
899 });
900
901 if (changed) {
902 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
903 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
904 }
905
906 return OPERATOR_FINISHED;
907}
908
910{
911 ot->name = "Set Cyclical State";
912 ot->idname = "GREASE_PENCIL_OT_cyclical_set";
913 ot->description = "Close or open the selected stroke adding a segment from last to first point";
914
915 ot->invoke = WM_menu_invoke;
918
920
921 ot->prop = RNA_def_enum(
922 ot->srna, "type", prop_cyclical_types, int(CyclicalMode::TOGGLE), "Type", "");
923
924 RNA_def_boolean(ot->srna,
925 "subdivide_cyclic_segment",
926 true,
927 "Match Point Density",
928 "Add point in the new segment to keep the same density");
929}
930
932
933/* -------------------------------------------------------------------- */
936
938{
939 const Scene *scene = CTX_data_scene(C);
941 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
942
943 if (object->totcol == 0) {
944 return OPERATOR_CANCELLED;
945 }
946
947 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
948 for (const MutableDrawingInfo &info : drawings) {
949 IndexMaskMemory memory;
951 *object, info.drawing, info.layer_index, memory);
952 if (strokes.is_empty()) {
953 continue;
954 }
955 bke::CurvesGeometry &curves = info.drawing.strokes_for_write();
956
957 const VArray<int> materials = *curves.attributes().lookup_or_default<int>(
958 "material_index", bke::AttrDomain::Curve, 0);
959 object->actcol = materials[strokes.first()] + 1;
960 break;
961 };
962
963 WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_EDITED, &grease_pencil);
964
965 return OPERATOR_FINISHED;
966}
967
969{
970 ot->name = "Set Active Material";
971 ot->idname = "GREASE_PENCIL_OT_set_active_material";
972 ot->description = "Set the selected stroke material as the active material";
973
976
978}
979
980
981/* -------------------------------------------------------------------- */
984
986{
987 const Scene *scene = CTX_data_scene(C);
989 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
990
991 /* Radius is half of the thickness. */
992 const float radius = RNA_float_get(op->ptr, "thickness") * 0.5f;
993
994 bool changed = false;
995 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
996 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
997 IndexMaskMemory memory;
999 *object, info.drawing, info.layer_index, memory);
1000 if (strokes.is_empty()) {
1001 return;
1002 }
1004
1005 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
1007 bke::curves::fill_points<float>(points_by_curve, strokes, radius, radii);
1008 changed = true;
1009 });
1010
1011 if (changed) {
1012 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1013 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1014 }
1015
1016 return OPERATOR_FINISHED;
1017}
1018
1020{
1021 ot->name = "Set Uniform Thickness";
1022 ot->idname = "GREASE_PENCIL_OT_set_uniform_thickness";
1023 ot->description = "Set all stroke points to same thickness";
1024
1027
1028 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1029
1030 ot->prop = RNA_def_float(
1031 ot->srna, "thickness", 0.1f, 0.0f, 1000.0f, "Thickness", "Thickness", 0.0f, 1000.0f);
1032}
1033
1035/* -------------------------------------------------------------------- */
1038
1040{
1041 using namespace blender::bke;
1042
1043 const Scene *scene = CTX_data_scene(C);
1044 Object *object = CTX_data_active_object(C);
1045 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1046
1047 const float opacity_stroke = RNA_float_get(op->ptr, "opacity_stroke");
1048 const float opacity_fill = RNA_float_get(op->ptr, "opacity_fill");
1049
1050 bool changed = false;
1051 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1052 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1053 IndexMaskMemory memory;
1055 *object, info.drawing, info.layer_index, memory);
1056 if (strokes.is_empty()) {
1057 return;
1058 }
1060 MutableAttributeAccessor attributes = curves.attributes_for_write();
1061 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
1062
1064 bke::curves::fill_points<float>(points_by_curve, strokes, opacity_stroke, opacities);
1065
1066 if (SpanAttributeWriter<float> fill_opacities = attributes.lookup_or_add_for_write_span<float>(
1067 "fill_opacity", AttrDomain::Curve))
1068 {
1069 strokes.foreach_index(GrainSize(2048), [&](const int64_t curve) {
1070 fill_opacities.span[curve] = opacity_fill;
1071 });
1072 }
1073
1074 changed = true;
1075 });
1076
1077 if (changed) {
1078 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1079 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1080 }
1081
1082 return OPERATOR_FINISHED;
1083}
1084
1086{
1087 ot->name = "Set Uniform Opacity";
1088 ot->idname = "GREASE_PENCIL_OT_set_uniform_opacity";
1089 ot->description = "Set all stroke points to same opacity";
1090
1093
1094 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1095
1096 /* Differentiate default opacities for stroke & fills so shapes with same stroke+fill colors will
1097 * be more readable. */
1098 RNA_def_float(ot->srna, "opacity_stroke", 1.0f, 0.0f, 1.0f, "Stroke Opacity", "", 0.0f, 1.0f);
1099 RNA_def_float(ot->srna, "opacity_fill", 0.5f, 0.0f, 1.0f, "Fill Opacity", "", 0.0f, 1.0f);
1100}
1101
1103
1104/* -------------------------------------------------------------------- */
1107
1109 wmOperator * /*op*/)
1110{
1111 const Scene *scene = CTX_data_scene(C);
1112 Object *object = CTX_data_active_object(C);
1113 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1114
1115 bool changed = false;
1116 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1117 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1118 IndexMaskMemory memory;
1120 *object, info.drawing, info.layer_index, memory);
1121 if (strokes.is_empty()) {
1122 return;
1123 }
1125
1126 /* Switch stroke direction. */
1127 curves.reverse_curves(strokes);
1128
1129 changed = true;
1130 });
1131
1132 if (changed) {
1133 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1134 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1135 }
1136
1137 return OPERATOR_FINISHED;
1138}
1139
1141{
1142 /* identifiers */
1143 ot->name = "Switch Direction";
1144 ot->idname = "GREASE_PENCIL_OT_stroke_switch_direction";
1145 ot->description = "Change direction of the points of the selected strokes";
1146
1147 /* Callbacks. */
1150
1151 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1152}
1153
1155/* -------------------------------------------------------------------- */
1159 const IndexMask &mask)
1160{
1161
1162 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
1163 const VArray<bool> src_cyclic = curves.cyclic();
1164
1165 /* Early-return if no cyclic curves. */
1167 return curves;
1168 }
1169
1170 Array<bool> start_set_points(curves.points_num());
1171 mask.to_bools(start_set_points.as_mutable_span());
1172
1173 Array<int> dst_to_src_point(curves.points_num());
1174
1175 threading::parallel_for(curves.curves_range(), 1024, [&](const IndexRange range) {
1176 for (const int curve_i : range) {
1177 const IndexRange points = points_by_curve[curve_i];
1178 const Span<bool> curve_i_selected_points = start_set_points.as_span().slice(points);
1179 const int first_selected = curve_i_selected_points.first_index_try(true);
1180
1181 MutableSpan<int> dst_to_src_slice = dst_to_src_point.as_mutable_span().slice(points);
1182
1183 array_utils::fill_index_range<int>(dst_to_src_slice, points.start());
1184
1185 if (first_selected == -1 || src_cyclic[curve_i] == false) {
1186 continue;
1187 }
1188
1189 std::rotate(dst_to_src_slice.begin(),
1190 dst_to_src_slice.begin() + first_selected,
1191 dst_to_src_slice.end());
1192 }
1193 });
1194
1195 /* New CurvesGeometry to copy to. */
1196 bke::CurvesGeometry dst_curves(curves.points_num(), curves.curves_num());
1197 BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &curves.vertex_group_names);
1198
1199 /* Copy offsets. */
1200 array_utils::copy(curves.offsets(), dst_curves.offsets_for_write());
1201
1202 /* Attribute accessors for copying. */
1203 bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
1204 const bke::AttributeAccessor src_attributes = curves.attributes();
1205
1206 /* Copy curve attrs. */
1208 src_attributes, bke::AttrDomain::Curve, bke::AttrDomain::Curve, {}, dst_attributes);
1209 array_utils::copy(src_cyclic, dst_curves.cyclic_for_write());
1210
1211 /* Copy point attrs */
1212 gather_attributes(src_attributes,
1215 {},
1216 dst_to_src_point,
1217 dst_attributes);
1218
1219 dst_curves.update_curve_types();
1220 /* TODO: change to copying knots by point. */
1221 if (curves.nurbs_has_custom_knots()) {
1223 dst_curves.curves_range(), NURBS_KNOT_MODE_NORMAL, NURBS_KNOT_MODE_NORMAL, dst_curves);
1224 }
1225 return dst_curves;
1226}
1227
1229{
1230 using namespace bke::greasepencil;
1231 const Scene *scene = CTX_data_scene(C);
1232 Object *object = CTX_data_active_object(C);
1233 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1234
1235 std::atomic<bool> changed = false;
1236 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1237 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1238 IndexMaskMemory memory;
1240 *object, info.drawing, info.layer_index, memory);
1241 if (selection.is_empty()) {
1242 return;
1243 }
1244
1245 info.drawing.strokes_for_write() = set_start_point(info.drawing.strokes(), selection);
1246
1248 changed = true;
1249 });
1250
1251 if (changed) {
1252 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1253 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1254 }
1255 return OPERATOR_FINISHED;
1256}
1258{
1259 /* Identifiers */
1260 ot->name = "Set Start Point";
1261 ot->idname = "GREASE_PENCIL_OT_set_start_point";
1262 ot->description = "Select which point is the beginning of the curve";
1263
1264 /* Callbacks */
1267
1268 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1269}
1270
1271
1272/* -------------------------------------------------------------------- */
1275
1276enum class CapsMode : int8_t {
1278 FLAT = 0,
1282 END = 2,
1285};
1286
1287static void toggle_caps(MutableSpan<int8_t> caps, const IndexMask &strokes)
1288{
1289 strokes.foreach_index([&](const int stroke_i) {
1290 if (caps[stroke_i] == GP_STROKE_CAP_FLAT) {
1291 caps[stroke_i] = GP_STROKE_CAP_ROUND;
1292 }
1293 else {
1294 caps[stroke_i] = GP_STROKE_CAP_FLAT;
1295 }
1296 });
1297}
1298
1300{
1301 const Scene *scene = CTX_data_scene(C);
1302 Object *object = CTX_data_active_object(C);
1303 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1304
1305 const CapsMode mode = CapsMode(RNA_enum_get(op->ptr, "type"));
1306
1307 bool changed = false;
1308 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1309 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1311 IndexMaskMemory memory;
1313 *object, info.drawing, info.layer_index, memory);
1314 if (strokes.is_empty()) {
1315 return;
1316 }
1317
1318 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
1319
1320 if (ELEM(mode, CapsMode::ROUND, CapsMode::FLAT)) {
1321 const int8_t flag_set = (mode == CapsMode::ROUND) ? int8_t(GP_STROKE_CAP_TYPE_ROUND) :
1323 if (bke::SpanAttributeWriter<int8_t> start_caps =
1324 attributes.lookup_or_add_for_write_span<int8_t>("start_cap", bke::AttrDomain::Curve))
1325 {
1326 index_mask::masked_fill(start_caps.span, flag_set, strokes);
1327 start_caps.finish();
1328 }
1330 attributes.lookup_or_add_for_write_span<int8_t>("end_cap", bke::AttrDomain::Curve))
1331 {
1332 index_mask::masked_fill(end_caps.span, flag_set, strokes);
1333 end_caps.finish();
1334 }
1335 }
1336 else {
1337 switch (mode) {
1338 case CapsMode::START: {
1340 attributes.lookup_or_add_for_write_span<int8_t>("start_cap",
1342 {
1343 toggle_caps(caps.span, strokes);
1344 caps.finish();
1345 }
1346 break;
1347 }
1348 case CapsMode::END: {
1350 attributes.lookup_or_add_for_write_span<int8_t>("end_cap",
1352 {
1353 toggle_caps(caps.span, strokes);
1354 caps.finish();
1355 }
1356 break;
1357 }
1358 case CapsMode::ROUND:
1359 case CapsMode::FLAT:
1360 break;
1361 }
1362 }
1363
1364 changed = true;
1365 });
1366
1367 if (changed) {
1368 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1369 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1370 }
1371
1372 return OPERATOR_FINISHED;
1373}
1374
1376{
1377 static const EnumPropertyItem prop_caps_types[] = {
1378 {int(CapsMode::ROUND), "ROUND", 0, "Rounded", "Set as default rounded"},
1379 {int(CapsMode::FLAT), "FLAT", 0, "Flat", ""},
1381 {int(CapsMode::START), "START", 0, "Toggle Start", ""},
1382 {int(CapsMode::END), "END", 0, "Toggle End", ""},
1383 {0, nullptr, 0, nullptr, nullptr},
1384 };
1385
1386 ot->name = "Set Curve Caps";
1387 ot->idname = "GREASE_PENCIL_OT_caps_set";
1388 ot->description = "Change curve caps mode (rounded or flat)";
1389
1390 ot->invoke = WM_menu_invoke;
1393
1394 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1395
1396 ot->prop = RNA_def_enum(ot->srna, "type", prop_caps_types, int(CapsMode::ROUND), "Type", "");
1397}
1398
1400
1401/* -------------------------------------------------------------------- */
1404
1405/* Retry enum items with object materials. */
1407 PointerRNA * /*ptr*/,
1408 PropertyRNA * /*prop*/,
1409 bool *r_free)
1410{
1412 EnumPropertyItem *item = nullptr, item_tmp = {0};
1413 int totitem = 0;
1414
1415 if (ob == nullptr) {
1417 }
1418
1419 /* Existing materials */
1420 for (const int i : IndexRange(ob->totcol)) {
1421 if (Material *ma = BKE_object_material_get(ob, i + 1)) {
1422 item_tmp.identifier = ma->id.name + 2;
1423 item_tmp.name = ma->id.name + 2;
1424 item_tmp.value = i + 1;
1425 item_tmp.icon = ma->preview ? ma->preview->runtime->icon_id : ICON_NONE;
1426
1427 RNA_enum_item_add(&item, &totitem, &item_tmp);
1428 }
1429 }
1430 RNA_enum_item_end(&item, &totitem);
1431 *r_free = true;
1432
1433 return item;
1434}
1435
1437{
1438 Object *object = CTX_data_active_object(C);
1439 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1440 const int slot = RNA_enum_get(op->ptr, "slot");
1441
1442 /* Try to get material slot. */
1443 if ((slot < 1) || (slot > object->totcol)) {
1444 return OPERATOR_CANCELLED;
1445 }
1446
1447 /* Set active material. */
1448 object->actcol = slot;
1449
1450 WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_EDITED, &grease_pencil);
1451
1452 return OPERATOR_FINISHED;
1453}
1454
1456{
1457 ot->name = "Set Active Material";
1458 ot->idname = "GREASE_PENCIL_OT_set_material";
1459 ot->description = "Set active material";
1460
1463
1464 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1465
1466 /* Material to use (dynamic enum) */
1467 ot->prop = RNA_def_enum(ot->srna, "slot", rna_enum_dummy_DEFAULT_items, 0, "Material Slot", "");
1469}
1470
1471
1472/* -------------------------------------------------------------------- */
1475
1477{
1478 const Scene *scene = CTX_data_scene(C);
1479 Object *object = CTX_data_active_object(C);
1480 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1481
1483 scene->toolsettings);
1484
1485 std::atomic<bool> changed = false;
1486 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1487 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1488 IndexMaskMemory memory;
1490 *object, info.drawing, info.layer_index, selection_domain, memory);
1491 if (elements.is_empty()) {
1492 return;
1493 }
1494
1496 if (selection_domain == bke::AttrDomain::Curve) {
1498 }
1499 else if (selection_domain == bke::AttrDomain::Point) {
1501 }
1503 changed.store(true, std::memory_order_relaxed);
1504 });
1505
1506 if (changed) {
1507 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1508 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1509 }
1510 return OPERATOR_FINISHED;
1511}
1512
1514{
1515 ot->name = "Duplicate";
1516 ot->idname = "GREASE_PENCIL_OT_duplicate";
1517 ot->description = "Duplicate the selected points";
1518
1521
1522 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1523}
1524
1526{
1527 Object *object = CTX_data_active_object(C);
1528 Scene &scene = *CTX_data_scene(C);
1529 const int limit = RNA_int_get(op->ptr, "limit");
1530
1531 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1532 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
1533
1534 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1536 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
1537
1538 IndexMaskMemory memory;
1540 *object, info.drawing, info.layer_index, memory);
1541
1542 const IndexMask curves_to_delete = IndexMask::from_predicate(
1543 editable_strokes, GrainSize(4096), memory, [&](const int i) {
1544 return points_by_curve[i].size() <= limit;
1545 });
1546
1547 curves.remove_curves(curves_to_delete, {});
1548 });
1549
1550 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1551 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1552
1553 return OPERATOR_FINISHED;
1554}
1555
1557 wmOperator *op,
1558 const wmEvent *event)
1559{
1561 C, op, event, IFACE_("Remove Loose Points"), IFACE_("Delete"));
1562}
1563
1565{
1566 ot->name = "Clean Loose Points";
1567 ot->idname = "GREASE_PENCIL_OT_clean_loose";
1568 ot->description = "Remove loose points";
1569
1573
1574 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1575
1576 RNA_def_int(ot->srna,
1577 "limit",
1578 1,
1579 1,
1580 INT_MAX,
1581 "Limit",
1582 "Number of points to consider stroke as loose",
1583 1,
1584 INT_MAX);
1585}
1586
1588
1589/* -------------------------------------------------------------------- */
1592
1594{
1595 const int cuts = RNA_int_get(op->ptr, "number_cuts");
1596 const bool only_selected = RNA_boolean_get(op->ptr, "only_selected");
1597
1598 std::atomic<bool> changed = false;
1599
1600 const Scene *scene = CTX_data_scene(C);
1601 Object *object = CTX_data_active_object(C);
1602 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1604 scene->toolsettings);
1605
1606 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1607
1608 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1609 IndexMaskMemory memory;
1611 *object, info.drawing, info.layer_index, memory);
1612 if (strokes.is_empty()) {
1613 return;
1614 }
1616
1617 VArray<int> vcuts = {};
1618
1619 if (selection_domain == bke::AttrDomain::Curve || !only_selected) {
1620 /* Subdivide entire selected curve, every stroke subdivides to the same cut. */
1621 vcuts = VArray<int>::ForSingle(cuts, curves.points_num());
1622 }
1623 else if (selection_domain == bke::AttrDomain::Point) {
1624 /* Subdivide between selected points. Only cut between selected points.
1625 * Make the cut array the same length as point count for specifying
1626 * cut/uncut for each segment. */
1627 const VArray<bool> selection = *curves.attributes().lookup_or_default<bool>(
1628 ".selection", bke::AttrDomain::Point, true);
1629
1630 const OffsetIndices points_by_curve = curves.points_by_curve();
1631 const VArray<bool> cyclic = curves.cyclic();
1632
1633 Array<int> use_cuts(curves.points_num(), 0);
1634
1635 /* The cut is after each point, so the last point selected wouldn't need to be registered. */
1636 for (const int curve : curves.curves_range()) {
1637 /* No need to loop to the last point since the cut is registered on the point before the
1638 * segment. */
1639 for (const int point : points_by_curve[curve].drop_back(1)) {
1640 /* The point itself should be selected. */
1641 if (!selection[point]) {
1642 continue;
1643 }
1644 /* If the next point in the curve is selected, then cut this segment. */
1645 if (selection[point + 1]) {
1646 use_cuts[point] = cuts;
1647 }
1648 }
1649 /* Check for cyclic and selection. */
1650 if (cyclic[curve]) {
1651 const int first_point = points_by_curve[curve].first();
1652 const int last_point = points_by_curve[curve].last();
1653 if (selection[first_point] && selection[last_point]) {
1654 use_cuts[last_point] = cuts;
1655 }
1656 }
1657 }
1658 vcuts = VArray<int>::ForContainer(std::move(use_cuts));
1659 }
1660
1661 curves = geometry::subdivide_curves(curves, strokes, vcuts);
1663 changed.store(true, std::memory_order_relaxed);
1664 });
1665
1666 if (changed) {
1667 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1669 }
1670
1671 return OPERATOR_FINISHED;
1672}
1673
1675{
1676 PropertyRNA *prop;
1677
1678 ot->name = "Subdivide Stroke";
1679 ot->idname = "GREASE_PENCIL_OT_stroke_subdivide";
1680 ot->description =
1681 "Subdivide between continuous selected points of the stroke adding a point half way "
1682 "between "
1683 "them";
1684
1687
1688 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1689
1690 prop = RNA_def_int(ot->srna, "number_cuts", 1, 1, 32, "Number of Cuts", "", 1, 5);
1691 /* Avoid re-using last var because it can cause _very_ high value and annoy users. */
1693
1694 RNA_def_boolean(ot->srna,
1695 "only_selected",
1696 true,
1697 "Selected Points",
1698 "Smooth only selected points in the stroke");
1699}
1700
1702
1703/* -------------------------------------------------------------------- */
1706
1707enum class ReorderDirection : int8_t {
1709 TOP = 0,
1711 UP = 1,
1713 DOWN = 2,
1716};
1717
1719 const IndexMask &selected,
1720 const ReorderDirection direction)
1721{
1722 Array<int> indices(universe.size());
1723
1725 /* Initialize the indices. */
1727 }
1728
1730 /*
1731 * Take the selected indices and move them to the start for `Bottom` or the end for `Top`
1732 * And fill the reset with the unselected indices.
1733 *
1734 * Here's a diagram:
1735 *
1736 * Input
1737 * 0 1 2 3 4 5 6 7 8 9
1738 * ^ ^ ^
1739 *
1740 * Top
1741 * |-----A-----| |-B-|
1742 * 0 1 3 6 7 8 9 2 4 5
1743 * ^ ^ ^
1744 *
1745 * Bottom
1746 * |-A-| |-----B-----|
1747 * 2 4 5 0 1 3 6 7 8 9
1748 * ^ ^ ^
1749 */
1750
1751 IndexMaskMemory memory;
1752 const IndexMask unselected = selected.complement(universe, memory);
1753
1754 const IndexMask &A = (direction == ReorderDirection::BOTTOM) ? selected : unselected;
1755 const IndexMask &B = (direction == ReorderDirection::BOTTOM) ? unselected : selected;
1756
1757 A.to_indices(indices.as_mutable_span().take_front(A.size()));
1758 B.to_indices(indices.as_mutable_span().take_back(B.size()));
1759 }
1760 else if (direction == ReorderDirection::DOWN) {
1761 selected.foreach_index_optimized<int>([&](const int curve_i, const int pos) {
1762 /* Check if the curve index is touching the beginning without any gaps. */
1763 if (curve_i != pos) {
1764 /* Move a index down by flipping it with the one below it. */
1765 std::swap(indices[curve_i], indices[curve_i - 1]);
1766 }
1767 });
1768 }
1769 else if (direction == ReorderDirection::UP) {
1770 Array<int> selected_indices(selected.size());
1771 selected.to_indices(selected_indices.as_mutable_span());
1772
1773 /* Because each index is moving up we need to loop through the indices backwards,
1774 * starting at the largest. */
1775 for (const int i : selected_indices.index_range()) {
1776 const int pos = selected_indices.index_range().last(i);
1777 const int curve_i = selected_indices[pos];
1778
1779 /* Check if the curve index is touching the end without any gaps. */
1780 if (curve_i != universe.last(i)) {
1781 /* Move a index up by flipping it with the one above it. */
1782 std::swap(indices[curve_i], indices[curve_i + 1]);
1783 }
1784 }
1785 }
1786
1787 return indices;
1788}
1789
1791{
1792 const Scene *scene = CTX_data_scene(C);
1793 Object *object = CTX_data_active_object(C);
1794 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1795
1796 const ReorderDirection direction = ReorderDirection(RNA_enum_get(op->ptr, "direction"));
1797
1798 std::atomic<bool> changed = false;
1799 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
1800 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
1801 IndexMaskMemory memory;
1803 *object, info.drawing, info.layer_index, memory);
1804 if (strokes.is_empty()) {
1805 return;
1806 }
1808
1809 /* Return if everything is selected. */
1810 if (strokes.size() == curves.curves_num()) {
1811 return;
1812 }
1813
1814 const Array<int> indices = get_reordered_indices(curves.curves_range(), strokes, direction);
1815
1818 changed.store(true, std::memory_order_relaxed);
1819 });
1820
1821 if (changed) {
1822 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1823 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
1824 }
1825
1826 return OPERATOR_FINISHED;
1827}
1828
1830{
1831 static const EnumPropertyItem prop_reorder_direction[] = {
1832 {int(ReorderDirection::TOP), "TOP", 0, "Bring to Front", ""},
1833 {int(ReorderDirection::UP), "UP", 0, "Bring Forward", ""},
1835 {int(ReorderDirection::DOWN), "DOWN", 0, "Send Backward", ""},
1836 {int(ReorderDirection::BOTTOM), "BOTTOM", 0, "Send to Back", ""},
1837 {0, nullptr, 0, nullptr, nullptr},
1838 };
1839
1840 ot->name = "Reorder";
1841 ot->idname = "GREASE_PENCIL_OT_reorder";
1842 ot->description = "Change the display order of the selected strokes";
1843
1846
1847 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1848
1849 ot->prop = RNA_def_enum(
1850 ot->srna, "direction", prop_reorder_direction, int(ReorderDirection::TOP), "Direction", "");
1851}
1852
1854
1855/* -------------------------------------------------------------------- */
1858
1860{
1861 using namespace bke::greasepencil;
1862 const Scene *scene = CTX_data_scene(C);
1863 bool changed = false;
1864
1865 Object *object = CTX_data_active_object(C);
1866 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1867
1868 int target_layer_name_length;
1869 char *target_layer_name = RNA_string_get_alloc(
1870 op->ptr, "target_layer_name", nullptr, 0, &target_layer_name_length);
1871 BLI_SCOPED_DEFER([&] { MEM_SAFE_FREE(target_layer_name); });
1872 const bool add_new_layer = RNA_boolean_get(op->ptr, "add_new_layer");
1873 TreeNode *target_node = nullptr;
1874
1875 if (add_new_layer) {
1876 target_node = &grease_pencil.add_layer(target_layer_name).as_node();
1877 }
1878 else {
1879 target_node = grease_pencil.find_node_by_name(target_layer_name);
1880 }
1881
1882 if (target_node == nullptr || !target_node->is_layer()) {
1883 BKE_reportf(op->reports, RPT_ERROR, "There is no layer '%s'", target_layer_name);
1884 return OPERATOR_CANCELLED;
1885 }
1886
1887 Layer &layer_dst = target_node->as_layer();
1888 if (layer_dst.is_locked()) {
1889 BKE_reportf(op->reports, RPT_ERROR, "'%s' Layer is locked", target_layer_name);
1890 return OPERATOR_CANCELLED;
1891 }
1892
1893 /* Iterate through all the drawings at current scene frame. */
1894 const Vector<MutableDrawingInfo> drawings_src = retrieve_editable_drawings(*scene,
1895 grease_pencil);
1896 for (const MutableDrawingInfo &info : drawings_src) {
1897 bke::CurvesGeometry &curves_src = info.drawing.strokes_for_write();
1898 IndexMaskMemory memory;
1899 const IndexMask selected_strokes = ed::curves::retrieve_selected_curves(curves_src, memory);
1900 if (selected_strokes.is_empty()) {
1901 continue;
1902 }
1903
1904 if (!layer_dst.frames().lookup_ptr(info.frame_number)) {
1905 /* Move geometry to a new drawing in target layer. */
1906 Drawing &drawing_dst = *grease_pencil.insert_frame(layer_dst, info.frame_number);
1908 curves_src, selected_strokes, {});
1909
1910 curves_src.remove_curves(selected_strokes, {});
1911 drawing_dst.tag_topology_changed();
1912 }
1913 else if (Drawing *drawing_dst = grease_pencil.get_drawing_at(layer_dst, info.frame_number)) {
1914 /* Append geometry to drawing in target layer. */
1916 curves_src, selected_strokes, {});
1917 Curves *selected_curves = bke::curves_new_nomain(std::move(selected_elems));
1918 Curves *layer_curves = bke::curves_new_nomain(std::move(drawing_dst->strokes_for_write()));
1919 std::array<bke::GeometrySet, 2> geometry_sets{
1920 bke::GeometrySet::from_curves(layer_curves),
1921 bke::GeometrySet::from_curves(selected_curves)};
1922 bke::GeometrySet joined = geometry::join_geometries(geometry_sets, {});
1923 drawing_dst->strokes_for_write() = std::move(joined.get_curves_for_write()->geometry.wrap());
1924
1925 curves_src.remove_curves(selected_strokes, {});
1926
1927 drawing_dst->tag_topology_changed();
1928 }
1929
1930 info.drawing.tag_topology_changed();
1931 changed = true;
1932 }
1933
1934 if (changed) {
1935 /* updates */
1938 }
1939
1940 return OPERATOR_FINISHED;
1941}
1942
1944 wmOperator *op,
1945 const wmEvent *event)
1946{
1947 const bool add_new_layer = RNA_boolean_get(op->ptr, "add_new_layer");
1948 if (add_new_layer) {
1949 Object *object = CTX_data_active_object(C);
1950 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
1951
1952 const std::string unique_name = grease_pencil.unique_layer_name("Layer");
1953 RNA_string_set(op->ptr, "target_layer_name", unique_name.c_str());
1954
1956 C, op, event, IFACE_("Move to New Layer"), IFACE_("Create"));
1957 }
1958
1959 /* Show the move menu if this operator is invoked from operator search without any property
1960 * pre-set. */
1961 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "target_layer_name");
1962 if (!RNA_property_is_set(op->ptr, prop)) {
1963 WM_menu_name_call(C, "GREASE_PENCIL_MT_move_to_layer", 0);
1964 return OPERATOR_FINISHED;
1965 }
1966
1968}
1969
1971{
1972 PropertyRNA *prop;
1973
1974 ot->name = "Move to Layer";
1975 ot->idname = "GREASE_PENCIL_OT_move_to_layer";
1976 ot->description = "Move selected strokes to another layer";
1977
1981
1982 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1983
1984 prop = RNA_def_string(
1985 ot->srna, "target_layer_name", nullptr, INT16_MAX, "Name", "Target Grease Pencil Layer");
1987 prop = RNA_def_boolean(
1988 ot->srna, "add_new_layer", false, "New Layer", "Move selection to a new layer");
1990}
1991
1993
1994/* -------------------------------------------------------------------- */
1997
1998enum class SeparateMode : int8_t {
1999 /* Selected Points/Strokes. */
2001 /* By Material. */
2002 MATERIAL = 1,
2003 /* By each Layer. */
2005};
2006
2008 {int(SeparateMode::SELECTED), "SELECTED", 0, "Selection", "Separate selected geometry"},
2009 {int(SeparateMode::MATERIAL), "MATERIAL", 0, "By Material", "Separate by material"},
2010 {int(SeparateMode::LAYER), "LAYER", 0, "By Layer", "Separate by layer"},
2011 {0, nullptr, 0, nullptr, nullptr},
2012};
2013
2014static void remove_unused_materials(Main *bmain, Object *object)
2015{
2016 int actcol = object->actcol;
2017 for (int slot = 1; slot <= object->totcol; slot++) {
2018 while (slot <= object->totcol && !BKE_object_material_slot_used(object, slot)) {
2019 object->actcol = slot;
2020 if (!BKE_object_material_slot_remove(bmain, object)) {
2021 break;
2022 }
2023
2024 if (actcol >= slot) {
2025 actcol--;
2026 }
2027 }
2028 }
2029 object->actcol = actcol;
2030}
2031
2033 Scene *scene,
2034 ViewLayer *view_layer,
2035 Base *base_prev,
2036 const GreasePencil &grease_pencil_src)
2037{
2038 const eDupli_ID_Flags dupflag = eDupli_ID_Flags(U.dupflag & USER_DUP_GPENCIL);
2039 Base *base_new = object::add_duplicate(bmain, scene, view_layer, base_prev, dupflag);
2040 Object *object_dst = base_new->object;
2041 object_dst->mode = OB_MODE_OBJECT;
2042 GreasePencil *grease_pencil_dst = BKE_grease_pencil_add(bmain, grease_pencil_src.id.name + 2);
2043 BKE_grease_pencil_copy_parameters(grease_pencil_src, *grease_pencil_dst);
2044 object_dst->data = grease_pencil_dst;
2045
2046 return object_dst;
2047}
2048
2050 const int layer_index,
2051 const GreasePencil &grease_pencil_src,
2052 GreasePencil &grease_pencil_dst,
2053 Vector<int> &src_to_dst_layer_indices)
2054{
2055 using namespace bke::greasepencil;
2056
2057 /* This assumes that the index is valid. Will cause an assert if it is not. */
2058 const Layer &layer_src = grease_pencil_src.layer(layer_index);
2059 if (TreeNode *node = grease_pencil_dst.find_node_by_name(layer_src.name())) {
2060 return node->as_layer();
2061 }
2062
2063 /* If the layer can't be found in `grease_pencil_dst` by name add a new layer. */
2064 Layer &new_layer = grease_pencil_dst.add_layer(layer_src.name());
2065 BKE_grease_pencil_copy_layer_parameters(layer_src, new_layer);
2066 src_to_dst_layer_indices.append(layer_index);
2067
2068 return new_layer;
2069}
2070
2072 Main &bmain,
2073 Scene &scene,
2074 ViewLayer &view_layer,
2075 Base &base_prev,
2076 Object &object_src)
2077{
2078 using namespace bke::greasepencil;
2079 bool changed = false;
2080
2081 GreasePencil &grease_pencil_src = *static_cast<GreasePencil *>(object_src.data);
2083 &bmain, &scene, &view_layer, &base_prev, grease_pencil_src);
2084 GreasePencil &grease_pencil_dst = *static_cast<GreasePencil *>(object_dst->data);
2085
2086 /* Iterate through all the drawings at current scene frame. */
2087 const Vector<MutableDrawingInfo> drawings_src = retrieve_editable_drawings(scene,
2088 grease_pencil_src);
2089 Vector<int> src_to_dst_layer_indices;
2090 for (const MutableDrawingInfo &info : drawings_src) {
2091 bke::CurvesGeometry &curves_src = info.drawing.strokes_for_write();
2092 IndexMaskMemory memory;
2093 const IndexMask selected_points = ed::curves::retrieve_selected_points(curves_src, memory);
2094 if (selected_points.is_empty()) {
2095 continue;
2096 }
2097
2098 /* Insert Keyframe at current frame/layer. */
2100 info.layer_index, grease_pencil_src, grease_pencil_dst, src_to_dst_layer_indices);
2101
2102 Drawing *drawing_dst = grease_pencil_dst.insert_frame(layer_dst, info.frame_number);
2103 BLI_assert(drawing_dst != nullptr);
2104
2105 /* Copy strokes to new CurvesGeometry. */
2107 curves_src, selected_points, {});
2108 curves_src = geometry::remove_points_and_split(curves_src, selected_points);
2109
2110 info.drawing.tag_topology_changed();
2111 drawing_dst->tag_topology_changed();
2112
2113 changed = true;
2114 }
2115
2116 if (changed) {
2117 /* Transfer layer attributes. */
2118 bke::gather_attributes(grease_pencil_src.attributes(),
2121 {},
2122 src_to_dst_layer_indices.as_span(),
2123 grease_pencil_dst.attributes_for_write());
2124
2125 /* Set the active layer in the target object. */
2126 if (grease_pencil_src.has_active_layer()) {
2127 const Layer &active_layer_src = *grease_pencil_src.get_active_layer();
2128 TreeNode *active_layer_dst = grease_pencil_dst.find_node_by_name(active_layer_src.name());
2129 if (active_layer_dst && active_layer_dst->is_layer()) {
2130 grease_pencil_dst.set_active_layer(&active_layer_dst->as_layer());
2131 }
2132 }
2133
2134 /* Add object materials to target object. */
2136 object_dst,
2137 BKE_object_material_array_p(&object_src),
2138 *BKE_object_material_len_p(&object_src),
2139 false);
2140
2141 remove_unused_materials(&bmain, object_dst);
2142 DEG_id_tag_update(&grease_pencil_dst.id, ID_RECALC_GEOMETRY);
2143 WM_event_add_notifier(&C, NC_OBJECT | ND_DRAW, &grease_pencil_dst);
2144 }
2145 return changed;
2146}
2147
2149 Main &bmain,
2150 Scene &scene,
2151 ViewLayer &view_layer,
2152 Base &base_prev,
2153 Object &object_src)
2154{
2155 using namespace bke::greasepencil;
2156 bool changed = false;
2157
2158 GreasePencil &grease_pencil_src = *static_cast<GreasePencil *>(object_src.data);
2159
2160 /* Create a new object for each layer. */
2161 for (const int layer_i : grease_pencil_src.layers().index_range()) {
2162 Layer &layer_src = grease_pencil_src.layer(layer_i);
2163 if (layer_src.is_locked()) {
2164 continue;
2165 }
2166
2168 &bmain, &scene, &view_layer, &base_prev, grease_pencil_src);
2169 GreasePencil &grease_pencil_dst = *static_cast<GreasePencil *>(object_dst->data);
2170 Vector<int> src_to_dst_layer_indices;
2172 layer_i, grease_pencil_src, grease_pencil_dst, src_to_dst_layer_indices);
2173
2174 /* Iterate through all the drawings at current frame. */
2176 scene, grease_pencil_src, layer_src);
2177 for (const MutableDrawingInfo &info : drawings_src) {
2178 bke::CurvesGeometry &curves_src = info.drawing.strokes_for_write();
2179 IndexMaskMemory memory;
2180 const IndexMask strokes = retrieve_editable_strokes(
2181 object_src, info.drawing, info.layer_index, memory);
2182 if (strokes.is_empty()) {
2183 continue;
2184 }
2185
2186 /* Add object materials. */
2188 object_dst,
2189 BKE_object_material_array_p(&object_src),
2190 *BKE_object_material_len_p(&object_src),
2191 false);
2192
2193 /* Insert Keyframe at current frame/layer. */
2194 Drawing *drawing_dst = grease_pencil_dst.insert_frame(layer_dst, info.frame_number);
2195 /* TODO: Can we assume the insert never fails? */
2196 BLI_assert(drawing_dst != nullptr);
2197
2198 /* Copy strokes to new CurvesGeometry. */
2200 info.drawing.strokes(), strokes, {});
2201 curves_src.remove_curves(strokes, {});
2202
2203 info.drawing.tag_topology_changed();
2204 drawing_dst->tag_topology_changed();
2205
2206 changed = true;
2207 }
2208
2209 /* Transfer layer attributes. */
2210 bke::gather_attributes(grease_pencil_src.attributes(),
2213 {},
2214 src_to_dst_layer_indices.as_span(),
2215 grease_pencil_dst.attributes_for_write());
2216
2217 remove_unused_materials(&bmain, object_dst);
2218
2219 DEG_id_tag_update(&grease_pencil_dst.id, ID_RECALC_GEOMETRY);
2220 WM_event_add_notifier(&C, NC_OBJECT | ND_DRAW, &grease_pencil_dst);
2221 }
2222
2223 return changed;
2224}
2225
2227 Main &bmain,
2228 Scene &scene,
2229 ViewLayer &view_layer,
2230 Base &base_prev,
2231 Object &object_src)
2232{
2233 using namespace blender::bke;
2234 using namespace bke::greasepencil;
2235 bool changed = false;
2236
2237 GreasePencil &grease_pencil_src = *static_cast<GreasePencil *>(object_src.data);
2238
2239 /* Create a new object for each material. */
2240 for (const int mat_i : IndexRange(object_src.totcol).drop_front(1)) {
2241 if (!BKE_object_material_slot_used(&object_src, mat_i + 1)) {
2242 continue;
2243 }
2244
2246 &bmain, &scene, &view_layer, &base_prev, grease_pencil_src);
2247 GreasePencil &grease_pencil_dst = *static_cast<GreasePencil *>(object_dst->data);
2248
2249 /* Add object materials. */
2251 object_dst,
2252 BKE_object_material_array_p(&object_src),
2253 *BKE_object_material_len_p(&object_src),
2254 false);
2255
2256 /* Iterate through all the drawings at current scene frame. */
2257 const Vector<MutableDrawingInfo> drawings_src = retrieve_editable_drawings(scene,
2258 grease_pencil_src);
2259 Vector<int> src_to_dst_layer_indices;
2260 for (const MutableDrawingInfo &info : drawings_src) {
2261 bke::CurvesGeometry &curves_src = info.drawing.strokes_for_write();
2262 IndexMaskMemory memory;
2264 object_src, info.drawing, mat_i, memory);
2265 if (strokes.is_empty()) {
2266 continue;
2267 }
2268
2269 /* Insert Keyframe at current frame/layer. */
2271 info.layer_index, grease_pencil_src, grease_pencil_dst, src_to_dst_layer_indices);
2272
2273 Drawing *drawing_dst = grease_pencil_dst.insert_frame(layer_dst, info.frame_number);
2274 /* TODO: Can we assume the insert never fails? */
2275 BLI_assert(drawing_dst != nullptr);
2276
2277 /* Copy strokes to new CurvesGeometry. */
2278 drawing_dst->strokes_for_write() = bke::curves_copy_curve_selection(curves_src, strokes, {});
2279 curves_src.remove_curves(strokes, {});
2280
2281 info.drawing.tag_topology_changed();
2282 drawing_dst->tag_topology_changed();
2283
2284 changed = true;
2285 }
2286
2287 /* Transfer layer attributes. */
2288 bke::gather_attributes(grease_pencil_src.attributes(),
2291 {},
2292 src_to_dst_layer_indices.as_span(),
2293 grease_pencil_dst.attributes_for_write());
2294
2295 remove_unused_materials(&bmain, object_dst);
2296
2297 DEG_id_tag_update(&grease_pencil_dst.id, ID_RECALC_GEOMETRY);
2298 WM_event_add_notifier(&C, NC_OBJECT | ND_DRAW, &grease_pencil_dst);
2299 }
2300
2301 if (changed) {
2302 remove_unused_materials(&bmain, &object_src);
2303 }
2304
2305 return changed;
2306}
2307
2309{
2310 using namespace bke::greasepencil;
2311 Main *bmain = CTX_data_main(C);
2312 Scene *scene = CTX_data_scene(C);
2313 ViewLayer *view_layer = CTX_data_view_layer(C);
2314 Base *base_prev = CTX_data_active_base(C);
2315 Object *object_src = CTX_data_active_object(C);
2316 GreasePencil &grease_pencil_src = *static_cast<GreasePencil *>(object_src->data);
2317
2318 const SeparateMode mode = SeparateMode(RNA_enum_get(op->ptr, "mode"));
2319 bool changed = false;
2320
2321 WM_cursor_wait(true);
2322
2323 switch (mode) {
2325 /* Cancel if nothing selected. */
2327 grease_pencil_src);
2328 const bool has_selection = std::any_of(
2329 drawings.begin(), drawings.end(), [&](const MutableDrawingInfo &info) {
2330 return ed::curves::has_anything_selected(info.drawing.strokes());
2331 });
2332 if (!has_selection) {
2333 BKE_report(op->reports, RPT_ERROR, "Nothing selected");
2334 WM_cursor_wait(false);
2335 return OPERATOR_CANCELLED;
2336 }
2337
2339 *C, *bmain, *scene, *view_layer, *base_prev, *object_src);
2340 break;
2341 }
2343 /* Cancel if the object only has one material. */
2344 if (object_src->totcol == 1) {
2345 BKE_report(op->reports, RPT_ERROR, "The object has only one material");
2346 WM_cursor_wait(false);
2347 return OPERATOR_CANCELLED;
2348 }
2349
2351 *C, *bmain, *scene, *view_layer, *base_prev, *object_src);
2352 break;
2353 }
2354 case SeparateMode::LAYER: {
2355 /* Cancel if the object only has one layer. */
2356 if (grease_pencil_src.layers().size() == 1) {
2357 BKE_report(op->reports, RPT_ERROR, "The object has only one layer");
2358 WM_cursor_wait(false);
2359 return OPERATOR_CANCELLED;
2360 }
2362 *C, *bmain, *scene, *view_layer, *base_prev, *object_src);
2363 break;
2364 }
2365 }
2366
2367 WM_cursor_wait(false);
2368
2369 if (changed) {
2370 DEG_id_tag_update(&grease_pencil_src.id, ID_RECALC_GEOMETRY);
2371 WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_EDITED, &grease_pencil_src);
2372 }
2373
2374 return OPERATOR_FINISHED;
2375}
2376
2378{
2379 ot->name = "Separate";
2380 ot->idname = "GREASE_PENCIL_OT_separate";
2381 ot->description = "Separate the selected geometry into a new Grease Pencil object";
2382
2383 ot->invoke = WM_menu_invoke;
2386
2387 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2388
2389 ot->prop = RNA_def_enum(
2390 ot->srna, "mode", prop_separate_modes, int(SeparateMode::SELECTED), "Mode", "");
2391}
2392
2394
2395/* -------------------------------------------------------------------- */
2398
2399/* Global clipboard for Grease Pencil curves. */
2400static struct Clipboard {
2402 /* Name of the layer. */
2403 std::string name;
2404 /* Curves for this layer. */
2406 };
2408 /* Object transform of stored curves. */
2410 /* We store the material uid's of the copied curves, so we can match those when pasting the
2411 * clipboard into another object. */
2414} *grease_pencil_clipboard = nullptr;
2415
2418
2420{
2421 std::scoped_lock lock(grease_pencil_clipboard_lock);
2422
2423 if (grease_pencil_clipboard == nullptr) {
2424 grease_pencil_clipboard = MEM_new<Clipboard>(__func__);
2425 }
2427}
2428
2430{
2431 std::scoped_lock lock(grease_pencil_clipboard_lock);
2432
2434 MEM_delete(grease_pencil_clipboard);
2435 grease_pencil_clipboard = nullptr;
2436 }
2437}
2438
2440{
2441 using namespace blender::ed::greasepencil;
2442
2443 /* Get a list of all materials in the scene. */
2444 Map<uint, Material *> scene_materials;
2445 LISTBASE_FOREACH (Material *, material, &bmain.materials) {
2446 scene_materials.add(material->id.session_uid, material);
2447 }
2448
2449 const Clipboard &clipboard = ensure_grease_pencil_clipboard();
2450 Array<int> clipboard_material_remap(clipboard.materials_in_source_num, 0);
2451 for (const int i : clipboard.materials.index_range()) {
2452 /* Check if the material name exists in the scene. */
2453 int target_index;
2454 uint material_id = clipboard.materials[i].first;
2455 Material *material = scene_materials.lookup_default(material_id, nullptr);
2456 if (!material) {
2457 /* Material is removed, so create a new material. */
2458 BKE_grease_pencil_object_material_new(&bmain, &object, nullptr, &target_index);
2459 clipboard_material_remap[clipboard.materials[i].second] = target_index;
2460 continue;
2461 }
2462
2463 /* Find or add the material to the target object. */
2464 target_index = BKE_object_material_ensure(&bmain, &object, material);
2465 clipboard_material_remap[clipboard.materials[i].second] = target_index;
2466 }
2467
2468 return clipboard_material_remap;
2469}
2470
2472 const VArray<float4x4> &transforms)
2473{
2474 BLI_assert(geometries.size() == transforms.size());
2475
2476 std::unique_ptr<bke::Instances> instances = std::make_unique<bke::Instances>();
2477 instances->resize(geometries.size());
2478 transforms.materialize(instances->transforms_for_write());
2479 MutableSpan<int> handles = instances->reference_handles_for_write();
2480 for (const int i : geometries.index_range()) {
2481 handles[i] = instances->add_new_reference(bke::InstanceReference{geometries[i]});
2482 }
2483
2485 options.keep_original_ids = true;
2486 options.realize_instance_attributes = false;
2487 return realize_instances(bke::GeometrySet::from_instances(instances.release()), options);
2488}
2495
2497{
2499
2500 const Scene *scene = CTX_data_scene(C);
2501 Object *object = CTX_data_active_object(C);
2502 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
2504 scene->toolsettings);
2505
2507
2508 int num_elements_copied = 0;
2509 Map<const Layer *, Vector<bke::GeometrySet>> copied_curves_per_layer;
2510
2511 /* Collect all selected strokes/points on all editable layers. */
2512 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
2513 for (const MutableDrawingInfo &drawing_info : drawings) {
2514 const bke::CurvesGeometry &curves = drawing_info.drawing.strokes();
2515 const Layer &layer = grease_pencil.layer(drawing_info.layer_index);
2516
2517 if (curves.is_empty()) {
2518 continue;
2519 }
2521 continue;
2522 }
2523
2524 /* Get a copy of the selected geometry on this layer. */
2525 IndexMaskMemory memory;
2526 bke::CurvesGeometry copied_curves;
2527
2528 if (selection_domain == bke::AttrDomain::Curve) {
2529 const IndexMask selected_curves = ed::curves::retrieve_selected_curves(curves, memory);
2530 copied_curves = curves_copy_curve_selection(curves, selected_curves, {});
2531 num_elements_copied += copied_curves.curves_num();
2532 }
2533 else if (selection_domain == bke::AttrDomain::Point) {
2534 const IndexMask selected_points = ed::curves::retrieve_selected_points(curves, memory);
2535 copied_curves = geometry::remove_points_and_split(
2536 curves, selected_points.complement(curves.points_range(), memory));
2537 num_elements_copied += copied_curves.points_num();
2538 }
2539
2540 /* Add the layer selection to the set of copied curves. */
2541 copied_curves_per_layer.lookup_or_add_default(&layer).append(
2542 bke::GeometrySet::from_curves(curves_new_nomain(std::move(copied_curves))));
2543 }
2544
2545 if (copied_curves_per_layer.is_empty()) {
2546 clipboard.layers.reinitialize(0);
2547 return OPERATOR_CANCELLED;
2548 }
2549
2550 clipboard.layers.reinitialize(copied_curves_per_layer.size());
2551
2552 int i = 0;
2553 for (auto const &[layer, geometries] : copied_curves_per_layer.items()) {
2554 const float4x4 layer_to_object = layer->to_object_space(*object);
2555 Clipboard::ClipboardLayer &cliplayer = clipboard.layers[i];
2556
2557 bke::GeometrySet joined_copied_curves = join_geometries_with_transform(geometries.as_span(),
2558 layer_to_object);
2559 cliplayer.curves = joined_copied_curves.get_curves()->geometry.wrap();
2560 cliplayer.name = layer->name();
2561 i++;
2562 }
2563 clipboard.object_to_world = object->object_to_world();
2564
2565 /* Store the session uid of the materials used by the curves in the clipboard. We use the uid to
2566 * remap the material indices when pasting. */
2567 clipboard.materials.clear();
2568 clipboard.materials_in_source_num = grease_pencil.material_array_num;
2569
2570 const auto is_material_index_used = [&](const int material_index) -> bool {
2571 for (const Clipboard::ClipboardLayer &layer : clipboard.layers) {
2572 const bke::AttributeAccessor attributes = layer.curves.attributes();
2573 const VArraySpan<int> material_indices = *attributes.lookup_or_default<int>(
2574 "material_index", bke::AttrDomain::Curve, 0);
2575 if (material_indices.contains(material_index)) {
2576 return true;
2577 }
2578 }
2579 return false;
2580 };
2581
2582 for (const int material_index : IndexRange(grease_pencil.material_array_num)) {
2583 if (!is_material_index_used(material_index)) {
2584 continue;
2585 }
2586 const Material *material = BKE_object_material_get(object, material_index + 1);
2587 clipboard.materials.append({material ? material->id.session_uid : 0, material_index});
2588 }
2589
2590 /* Report the numbers. */
2591 if (selection_domain == bke::AttrDomain::Curve) {
2592 BKE_reportf(op->reports, RPT_INFO, "Copied %d selected curve(s)", num_elements_copied);
2593 }
2594 else if (selection_domain == bke::AttrDomain::Point) {
2595 BKE_reportf(op->reports, RPT_INFO, "Copied %d selected point(s)", num_elements_copied);
2596 }
2597
2598 return OPERATOR_FINISHED;
2599}
2600
2602{
2603 ot->name = "Copy Strokes";
2604 ot->idname = "GREASE_PENCIL_OT_copy";
2605 ot->description = "Copy the selected Grease Pencil points or strokes to the internal clipboard";
2606
2609
2610 ot->flag = OPTYPE_REGISTER;
2611}
2612
2614 Object &object,
2615 const bke::CurvesGeometry &curves_to_paste,
2616 const float4x4 &object_to_paste_layer,
2617 const float4x4 &clipboard_to_world,
2618 const bool keep_world_transform,
2619 const bool paste_back,
2621{
2622 /* Get a list of all materials in the scene. */
2623 const Array<int> clipboard_material_remap = ed::greasepencil::clipboard_materials_remap(bmain,
2624 object);
2625
2626 /* Get the index range of the pasted curves in the target layer. */
2627 const IndexRange pasted_curves_range = paste_back ? IndexRange(0, curves_to_paste.curves_num()) :
2628 IndexRange(drawing.strokes().curves_num(),
2629 curves_to_paste.curves_num());
2630
2631 /* Append the geometry from the clipboard to the target layer. */
2632 Curves *clipboard_id = bke::curves_new_nomain(curves_to_paste);
2633 Curves *target_id = curves_new_nomain(std::move(drawing.strokes_for_write()));
2634
2635 const Array<bke::GeometrySet> geometry_sets = {
2636 bke::GeometrySet::from_curves(paste_back ? clipboard_id : target_id),
2637 bke::GeometrySet::from_curves(paste_back ? target_id : clipboard_id)};
2638
2639 const float4x4 transform = object_to_paste_layer *
2640 (keep_world_transform ?
2641 object.world_to_object() * clipboard_to_world :
2643 const Array<float4x4> transforms = paste_back ? Span<float4x4>{transform, float4x4::identity()} :
2646 geometry_sets, VArray<float4x4>::ForContainer(transforms));
2647
2648 drawing.strokes_for_write() = std::move(joined_curves.get_curves_for_write()->geometry.wrap());
2649
2650 /* Remap the material indices of the pasted curves to the target object material indices. */
2652 bke::SpanAttributeWriter<int> material_indices = attributes.lookup_or_add_for_write_span<int>(
2653 "material_index", bke::AttrDomain::Curve);
2654 if (material_indices) {
2655 for (const int i : pasted_curves_range) {
2656 material_indices.span[i] = clipboard_material_remap[material_indices.span[i]];
2657 }
2658 material_indices.finish();
2659 }
2660
2661 drawing.tag_topology_changed();
2662
2663 return pasted_curves_range;
2664}
2665
2666enum class PasteType {
2669};
2670
2672{
2673 using namespace bke::greasepencil;
2674 Main *bmain = CTX_data_main(C);
2675 const Scene &scene = *CTX_data_scene(C);
2676 Object *object = CTX_data_active_object(C);
2678 scene.toolsettings);
2679 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
2680
2681 const PasteType type = PasteType(RNA_enum_get(op->ptr, "type"));
2682
2683 const bool keep_world_transform = RNA_boolean_get(op->ptr, "keep_world_transform");
2684 const bool paste_on_back = RNA_boolean_get(op->ptr, "paste_back");
2685
2687 if (clipboard.layers.is_empty()) {
2688 return OPERATOR_CANCELLED;
2689 }
2690
2691 /* Make sure everything on the clipboard is selected, in the correct selection domain. */
2693 bke::GSpanAttributeWriter selection = ed::curves::ensure_selection_attribute(
2694 layer.curves, selection_domain, CD_PROP_BOOL);
2695 selection.finish();
2696 });
2697
2698 if (type == PasteType::Active) {
2699 Layer *active_layer = grease_pencil.get_active_layer();
2700 if (!active_layer) {
2701 BKE_report(op->reports, RPT_ERROR, "No active Grease Pencil layer to paste into");
2702 return OPERATOR_CANCELLED;
2703 }
2704 if (!active_layer->is_editable()) {
2705 BKE_report(op->reports, RPT_ERROR, "Active layer is not editable");
2706 return OPERATOR_CANCELLED;
2707 }
2708
2709 /* Deselect everything from editable drawings. The pasted strokes are the only ones then after
2710 * the paste. That's convenient for the user. */
2711 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
2712 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
2714 info.drawing.strokes_for_write(), selection_domain, CD_PROP_BOOL);
2715 ed::curves::fill_selection_false(selection_in_target.span);
2716 selection_in_target.finish();
2717 });
2718
2719 const float4x4 object_to_layer = math::invert(active_layer->to_object_space(*object));
2720
2721 /* Ensure active keyframe. */
2722 bool inserted_keyframe = false;
2723 if (!ensure_active_keyframe(scene, grease_pencil, *active_layer, false, inserted_keyframe)) {
2724 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil frame to draw on");
2725 return OPERATOR_CANCELLED;
2726 }
2727
2728 Vector<MutableDrawingInfo> drawing_infos =
2730 scene, grease_pencil, *active_layer);
2731 for (const MutableDrawingInfo info : drawing_infos) {
2733 *bmain, *object, object_to_layer, keep_world_transform, paste_on_back, info.drawing);
2734 }
2735
2736 if (inserted_keyframe) {
2738 }
2739 }
2740 else if (type == PasteType::ByLayer) {
2741 Layer *active_layer = grease_pencil.get_active_layer();
2742 /* Find layers to paste strokes into. */
2743 Array<Layer *> layers_to_paste_into(clipboard.layers.size());
2744 for (const int clip_layer_i : clipboard.layers.index_range()) {
2745 const Clipboard::ClipboardLayer &layer = clipboard.layers[clip_layer_i];
2746 bke::greasepencil::TreeNode *node = grease_pencil.find_node_by_name(layer.name);
2747 const bool found_layer = node && node->is_layer() && node->as_layer().is_editable();
2748 if (found_layer) {
2749 layers_to_paste_into[clip_layer_i] = &node->as_layer();
2750 continue;
2751 }
2752 if (active_layer && active_layer->is_editable()) {
2753 /* Fall back to active layer. */
2754 BKE_report(
2755 op->reports, RPT_WARNING, "Couldn't find matching layer, pasting into active layer");
2756 layers_to_paste_into[clip_layer_i] = active_layer;
2757 continue;
2758 }
2759
2760 if (!active_layer) {
2761 BKE_report(op->reports, RPT_ERROR, "No active Grease Pencil layer to paste into");
2762 }
2763 if (!active_layer->is_editable()) {
2764 BKE_report(op->reports, RPT_ERROR, "Active layer is not editable");
2765 }
2766 return OPERATOR_CANCELLED;
2767 }
2768
2769 /* Deselect everything from editable drawings. The pasted strokes are the only ones then after
2770 * the paste. That's convenient for the user. */
2771 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
2772 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
2774 info.drawing.strokes_for_write(), selection_domain, CD_PROP_BOOL);
2775 ed::curves::fill_selection_false(selection_in_target.span);
2776 selection_in_target.finish();
2777 });
2778
2779 for (const int clip_layer_i : clipboard.layers.index_range()) {
2780 const Clipboard::ClipboardLayer &clip_layer = clipboard.layers[clip_layer_i];
2781 const bke::CurvesGeometry &curves_to_paste = clip_layer.curves;
2782
2783 BLI_assert(layers_to_paste_into[clip_layer_i] != nullptr);
2784 Layer &paste_layer = *layers_to_paste_into[clip_layer_i];
2785 const float4x4 object_to_paste_layer = math::invert(paste_layer.to_object_space(*object));
2786
2787 /* Ensure active keyframe. */
2788 bool inserted_keyframe = false;
2789 if (!ensure_active_keyframe(scene, grease_pencil, paste_layer, false, inserted_keyframe)) {
2790 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil frame to draw on");
2791 return OPERATOR_CANCELLED;
2792 }
2793
2794 Vector<MutableDrawingInfo> drawing_infos =
2796 scene, grease_pencil, paste_layer);
2797 for (const MutableDrawingInfo info : drawing_infos) {
2799 *object,
2800 curves_to_paste,
2801 object_to_paste_layer,
2802 clipboard.object_to_world,
2803 keep_world_transform,
2804 paste_on_back,
2805 info.drawing);
2806 }
2807
2808 if (inserted_keyframe) {
2810 }
2811 }
2812 }
2813 else {
2815 }
2816
2817 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
2818 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
2819
2820 return OPERATOR_FINISHED;
2821}
2822
2824{
2826 return false;
2827 }
2828
2829 std::scoped_lock lock(grease_pencil_clipboard_lock);
2830 /* Check for curves in the Grease Pencil clipboard. */
2831 return (grease_pencil_clipboard && grease_pencil_clipboard->layers.size() > 0);
2832}
2833
2835{
2836 PropertyRNA *prop;
2837
2838 static const EnumPropertyItem rna_paste_items[] = {
2839 {int(PasteType::Active), "ACTIVE", 0, "Paste to Active", ""},
2840 {int(PasteType::ByLayer), "LAYER", 0, "Paste by Layer", ""},
2841 {0, nullptr, 0, nullptr, nullptr},
2842 };
2843
2844 ot->name = "Paste Strokes";
2845 ot->idname = "GREASE_PENCIL_OT_paste";
2846 ot->description =
2847 "Paste Grease Pencil points or strokes from the internal clipboard to the active layer";
2848
2851
2852 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2853
2854 ot->prop = RNA_def_enum(ot->srna, "type", rna_paste_items, int(PasteType::Active), "Type", "");
2855
2856 prop = RNA_def_boolean(
2857 ot->srna, "paste_back", false, "Paste on Back", "Add pasted strokes behind all strokes");
2859
2860 prop = RNA_def_boolean(ot->srna,
2861 "keep_world_transform",
2862 false,
2863 "Keep World Transform",
2864 "Keep the world transform of strokes from the clipboard unchanged");
2866}
2867
2869
2871 Object &object,
2872 const float4x4 &object_to_paste_layer,
2873 const bool keep_world_transform,
2874 const bool paste_back,
2876{
2878 if (clipboard.layers.is_empty()) {
2879 return {};
2880 }
2881
2882 Vector<bke::GeometrySet> geometries_to_join;
2883 for (Clipboard::ClipboardLayer &layer : clipboard.layers) {
2885 }
2886 bke::GeometrySet joined_clipboard_set = geometry::join_geometries(geometries_to_join.as_span(),
2887 {});
2888 BLI_assert(joined_clipboard_set.has_curves());
2889 const bke::CurvesGeometry &joined_clipboard_curves =
2890 joined_clipboard_set.get_curves()->geometry.wrap();
2891
2892 return clipboard_paste_strokes_ex(bmain,
2893 object,
2894 joined_clipboard_curves,
2895 object_to_paste_layer,
2896 clipboard.object_to_world,
2897 keep_world_transform,
2898 paste_back,
2899 drawing);
2900}
2901
2902/* -------------------------------------------------------------------- */
2906{
2907 const Scene *scene = CTX_data_scene(C);
2908 Object *object = CTX_data_active_object(C);
2909 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
2910
2911 const float threshold = RNA_float_get(op->ptr, "threshold");
2912 const bool use_unselected = RNA_boolean_get(op->ptr, "use_unselected");
2913
2914 std::atomic<bool> changed = false;
2915
2916 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
2917 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
2918 bke::greasepencil::Drawing &drawing = info.drawing;
2919 IndexMaskMemory memory;
2920 const IndexMask points = use_unselected ?
2922 *object, drawing, info.layer_index, memory) :
2924 *object, info.drawing, info.layer_index, memory);
2925 if (points.is_empty()) {
2926 return;
2927 }
2929 drawing.strokes(), threshold, points, {});
2930 drawing.tag_topology_changed();
2931 changed.store(true, std::memory_order_relaxed);
2932 });
2933 if (changed) {
2934 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
2935 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
2936 }
2937 return OPERATOR_FINISHED;
2938}
2939
2941{
2942 PropertyRNA *prop;
2943
2944 ot->name = "Merge by Distance";
2945 ot->idname = "GREASE_PENCIL_OT_stroke_merge_by_distance";
2946 ot->description = "Merge points by distance";
2947
2950
2951 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2952
2953 prop = RNA_def_float(ot->srna, "threshold", 0.001f, 0.0f, 100.0f, "Threshold", "", 0.0f, 100.0f);
2954 /* Avoid re-using last var. */
2956
2957 prop = RNA_def_boolean(ot->srna,
2958 "use_unselected",
2959 false,
2960 "Unselected",
2961 "Use whole stroke, not only selected points");
2963}
2964
2966
2967/* -------------------------------------------------------------------- */
2970
2972 const IndexMask &points_to_extrude)
2973{
2974 const OffsetIndices<int> points_by_curve = src.points_by_curve();
2975
2976 const int old_curves_num = src.curves_num();
2977 const int old_points_num = src.points_num();
2978
2979 Vector<int> dst_to_src_points(old_points_num);
2981
2982 Vector<int> dst_to_src_curves(old_curves_num);
2984
2985 Vector<bool> dst_selected(old_points_num, false);
2986
2987 Vector<int> dst_curve_counts(old_curves_num);
2989 points_by_curve, src.curves_range(), dst_curve_counts.as_mutable_span());
2990
2991 const VArray<bool> &src_cyclic = src.cyclic();
2992
2993 /* Point offset keeps track of the points inserted. */
2994 int point_offset = 0;
2995 for (const int curve_index : src.curves_range()) {
2996 const IndexRange curve_points = points_by_curve[curve_index];
2997 const IndexMask curve_points_to_extrude = points_to_extrude.slice_content(curve_points);
2998 const bool curve_cyclic = src_cyclic[curve_index];
2999
3000 curve_points_to_extrude.foreach_index([&](const int src_point_index) {
3001 if (!curve_cyclic && (src_point_index == curve_points.first())) {
3002 /* Start-point extruded, we insert a new point at the beginning of the curve.
3003 * NOTE: all points of a cyclic curve behave like an inner-point. */
3004 dst_to_src_points.insert(src_point_index + point_offset, src_point_index);
3005 dst_selected.insert(src_point_index + point_offset, true);
3006 ++dst_curve_counts[curve_index];
3007 ++point_offset;
3008 return;
3009 }
3010 if (!curve_cyclic && (src_point_index == curve_points.last())) {
3011 /* End-point extruded, we insert a new point at the end of the curve.
3012 * NOTE: all points of a cyclic curve behave like an inner-point. */
3013 dst_to_src_points.insert(src_point_index + point_offset + 1, src_point_index);
3014 dst_selected.insert(src_point_index + point_offset + 1, true);
3015 ++dst_curve_counts[curve_index];
3016 ++point_offset;
3017 return;
3018 }
3019
3020 /* Inner-point extruded: we create a new curve made of two points located at the same
3021 * position. Only one of them is selected so that the other one remains stuck to the curve.
3022 */
3023 dst_to_src_points.append(src_point_index);
3024 dst_selected.append(false);
3025 dst_to_src_points.append(src_point_index);
3026 dst_selected.append(true);
3027 dst_to_src_curves.append(curve_index);
3028 dst_curve_counts.append(2);
3029 });
3030 }
3031
3032 const int new_points_num = dst_to_src_points.size();
3033 const int new_curves_num = dst_to_src_curves.size();
3034
3035 bke::CurvesGeometry dst(new_points_num, new_curves_num);
3037
3038 /* Setup curve offsets, based on the number of points in each curve. */
3039 MutableSpan<int> new_curve_offsets = dst.offsets_for_write();
3040 array_utils::copy(dst_curve_counts.as_span(), new_curve_offsets.drop_back(1));
3042
3043 /* Attributes. */
3044 const bke::AttributeAccessor src_attributes = src.attributes();
3046
3047 /* Selection attribute. */
3048 /* Copy the value of control point selections to all selection attributes.
3049 *
3050 * This will lead to the extruded control point always having both handles selected, if it's a
3051 * bezier type stroke. This is to circumvent the issue of source curves handles not being
3052 * deselected when the user extrudes a bezier control point with both handles selected*/
3053 for (const StringRef selection_attribute_name :
3055 {
3057 dst, bke::AttrDomain::Point, CD_PROP_BOOL, selection_attribute_name);
3058 selection.span.copy_from(dst_selected.as_span());
3059 selection.finish();
3060 }
3061
3062 bke::gather_attributes(src_attributes,
3065 {},
3066 dst_to_src_curves,
3067 dst_attributes);
3068
3069 /* Cyclic attribute : newly created curves cannot be cyclic. */
3070 dst.cyclic_for_write().drop_front(old_curves_num).fill(false);
3071
3072 bke::gather_attributes(src_attributes,
3076 {".selection", ".selection_handle_left", ".selection_handle_right"}),
3077 dst_to_src_points,
3078 dst_attributes);
3079
3080 dst.update_curve_types();
3081 if (src.nurbs_has_custom_knots()) {
3082 IndexMaskMemory memory;
3083 const VArray<int8_t> curve_types = src.curve_types();
3084 const VArray<int8_t> knot_modes = dst.nurbs_knots_modes();
3085 const OffsetIndices<int> dst_points_by_curve = dst.points_by_curve();
3086 const IndexMask include_curves = IndexMask::from_predicate(
3087 src.curves_range(), GrainSize(512), memory, [&](const int64_t curve_index) {
3088 return curve_types[curve_index] == CURVE_TYPE_NURBS &&
3089 knot_modes[curve_index] == NURBS_KNOT_MODE_CUSTOM &&
3090 points_by_curve[curve_index].size() == dst_points_by_curve[curve_index].size();
3091 });
3093 include_curves.complement(dst.curves_range(), memory),
3096 dst);
3097 bke::curves::nurbs::gather_custom_knots(src, include_curves, 0, dst);
3098 }
3099 return dst;
3100}
3101
3103{
3104 const Scene *scene = CTX_data_scene(C);
3105 Object *object = CTX_data_active_object(C);
3106 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
3107
3108 std::atomic<bool> changed = false;
3109 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
3110 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3111 IndexMaskMemory memory;
3112 const IndexMask points_to_extrude = retrieve_editable_and_selected_points(
3113 *object, info.drawing, info.layer_index, memory);
3114 if (points_to_extrude.is_empty()) {
3115 return;
3116 }
3117
3118 const bke::CurvesGeometry &curves = info.drawing.strokes();
3119 info.drawing.strokes_for_write() = extrude_grease_pencil_curves(curves, points_to_extrude);
3120
3122 changed.store(true, std::memory_order_relaxed);
3123 });
3124
3125 if (changed) {
3126 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
3127 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
3128 }
3129
3130 return OPERATOR_FINISHED;
3131}
3132
3134{
3135 ot->name = "Extrude Stroke Points";
3136 ot->idname = "GREASE_PENCIL_OT_extrude";
3137 ot->description = "Extrude the selected points";
3138
3141
3142 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3143}
3144
3146
3147/* -------------------------------------------------------------------- */
3150
3152{
3153 Scene &scene = *CTX_data_scene(C);
3155
3156 View3D *v3d = CTX_wm_view3d(C);
3157 ARegion *region = CTX_wm_region(C);
3158
3159 const ReprojectMode mode = ReprojectMode(RNA_enum_get(op->ptr, "type"));
3160 const bool keep_original = RNA_boolean_get(op->ptr, "keep_original");
3161
3162 Object *object = CTX_data_active_object(C);
3163 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
3164 const float offset = RNA_float_get(op->ptr, "offset");
3165
3166 /* Init snap context for geometry projection. */
3167 transform::SnapObjectContext *snap_context = nullptr;
3168 if (mode == ReprojectMode::Surface) {
3169 snap_context = transform::snap_object_context_create(&scene, 0);
3170 }
3171
3173 scene.toolsettings);
3174
3175 const int oldframe = int(DEG_get_ctime(depsgraph));
3176 if (keep_original) {
3177 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
3178 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3179 IndexMaskMemory memory;
3181 *object, info.drawing, info.layer_index, selection_domain, memory);
3182 if (elements.is_empty()) {
3183 return;
3184 }
3185
3187 if (selection_domain == bke::AttrDomain::Curve) {
3189 }
3190 else if (selection_domain == bke::AttrDomain::Point) {
3192 }
3194 });
3195 }
3196
3197 /* TODO: This can probably be optimized further for the non-Surface projection use case by
3198 * considering all drawings for the parallel loop instead of having to partition by frame number.
3199 */
3200 std::atomic<bool> changed = false;
3201 Array<Vector<MutableDrawingInfo>> drawings_per_frame =
3203 for (const Span<MutableDrawingInfo> drawings : drawings_per_frame) {
3204 if (drawings.is_empty()) {
3205 continue;
3206 }
3207 const int current_frame_number = drawings.first().frame_number;
3208
3209 if (mode == ReprojectMode::Surface) {
3210 scene.r.cfra = current_frame_number;
3212 }
3213
3214 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3216
3217 IndexMaskMemory memory;
3218 const IndexMask editable_points = retrieve_editable_points(
3219 *object, info.drawing, info.layer_index, memory);
3220
3221 for (const StringRef selection_name :
3223 {
3224 const IndexMask selected_points = ed::curves::retrieve_selected_points(
3225 curves, selection_name, memory);
3226 const IndexMask points_to_reproject = IndexMask::from_intersection(
3227 editable_points, selected_points, memory);
3228
3229 if (points_to_reproject.is_empty()) {
3230 return;
3231 }
3232
3233 MutableSpan<float3> positions = curves.positions_for_write();
3234 if (selection_name == ".selection_handle_left") {
3235 positions = curves.handle_positions_left_for_write();
3236 }
3237 else if (selection_name == ".selection_handle_right") {
3238 positions = curves.handle_positions_right_for_write();
3239 }
3240
3241 const bke::greasepencil::Layer &layer = grease_pencil.layer(info.layer_index);
3242 if (mode == ReprojectMode::Surface) {
3243 const float4x4 layer_space_to_world_space = layer.to_world_space(*object);
3244 const float4x4 world_space_to_layer_space = math::invert(layer_space_to_world_space);
3245 points_to_reproject.foreach_index(GrainSize(4096), [&](const int point_i) {
3246 float3 &position = positions[point_i];
3247 const float3 world_pos = math::transform_point(layer_space_to_world_space, position);
3248 float2 screen_co;
3249 if (ED_view3d_project_float_global(region, world_pos, screen_co, V3D_PROJ_TEST_NOP) !=
3251 {
3252 return;
3253 }
3254
3255 float3 ray_start, ray_direction;
3257 depsgraph, region, v3d, screen_co, ray_start, ray_direction, true))
3258 {
3259 return;
3260 }
3261
3262 float hit_depth = std::numeric_limits<float>::max();
3263 float3 hit_position(0.0f);
3264 float3 hit_normal(0.0f);
3265
3267 params.snap_target_select = SCE_SNAP_TARGET_ALL;
3268 if (transform::snap_object_project_ray(snap_context,
3269 depsgraph,
3270 v3d,
3271 &params,
3272 ray_start,
3273 ray_direction,
3274 &hit_depth,
3275 hit_position,
3276 hit_normal))
3277 {
3278 /* Apply offset over surface. */
3279 position = math::transform_point(
3280 world_space_to_layer_space,
3281 hit_position + math::normalize(ray_start - hit_position) * offset);
3282 }
3283 });
3284 }
3285 else {
3286 const DrawingPlacement drawing_placement(
3287 scene, *region, *v3d, *object, &layer, mode, offset, nullptr);
3288 points_to_reproject.foreach_index(GrainSize(4096), [&](const int point_i) {
3289 positions[point_i] = drawing_placement.reproject(positions[point_i]);
3290 });
3291 }
3292
3294 changed.store(true, std::memory_order_relaxed);
3295 }
3296 });
3297 }
3298
3299 if (snap_context != nullptr) {
3301 }
3302
3303 if (mode == ReprojectMode::Surface) {
3304 scene.r.cfra = oldframe;
3306 }
3307
3308 if (changed) {
3309 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
3310 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
3311 }
3312
3313 return OPERATOR_FINISHED;
3314}
3315
3317{
3318 uiLayout *layout = op->layout;
3319 uiLayout *row;
3320
3321 const ReprojectMode type = ReprojectMode(RNA_enum_get(op->ptr, "type"));
3322
3323 uiLayoutSetPropSep(layout, true);
3324 uiLayoutSetPropDecorate(layout, false);
3325 row = &layout->row(true);
3326 row->prop(op->ptr, "type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
3327
3328 if (type == ReprojectMode::Surface) {
3329 row = &layout->row(true);
3330 row->prop(op->ptr, "offset", UI_ITEM_NONE, std::nullopt, ICON_NONE);
3331 }
3332 row = &layout->row(true);
3333 row->prop(op->ptr, "keep_original", UI_ITEM_NONE, std::nullopt, ICON_NONE);
3334}
3335
3337{
3338 static const EnumPropertyItem reproject_type[] = {
3340 "FRONT",
3341 0,
3342 "Front",
3343 "Reproject the strokes using the X-Z plane"},
3344 {int(ReprojectMode::Side), "SIDE", 0, "Side", "Reproject the strokes using the Y-Z plane"},
3345 {int(ReprojectMode::Top), "TOP", 0, "Top", "Reproject the strokes using the X-Y plane"},
3346 {int(ReprojectMode::View),
3347 "VIEW",
3348 0,
3349 "View",
3350 "Reproject the strokes to end up on the same plane, as if drawn from the current "
3351 "viewpoint "
3352 "using 'Cursor' Stroke Placement"},
3354 "SURFACE",
3355 0,
3356 "Surface",
3357 "Reproject the strokes on to the scene geometry, as if drawn using 'Surface' placement"},
3359 "CURSOR",
3360 0,
3361 "Cursor",
3362 "Reproject the strokes using the orientation of 3D cursor"},
3363 {0, nullptr, 0, nullptr, nullptr},
3364 };
3365
3366 /* identifiers */
3367 ot->name = "Reproject Strokes";
3368 ot->idname = "GREASE_PENCIL_OT_reproject";
3369 ot->description =
3370 "Reproject the selected strokes from the current viewpoint as if they had been newly "
3371 "drawn "
3372 "(e.g. to fix problems from accidental 3D cursor movement or accidental viewport changes, "
3373 "or for matching deforming geometry)";
3374
3375 /* callbacks */
3376 ot->invoke = WM_menu_invoke;
3380
3381 /* flags */
3382 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3383
3384 /* properties */
3385 ot->prop = RNA_def_enum(
3386 ot->srna, "type", reproject_type, int(ReprojectMode::View), "Projection Type", "");
3387
3389 ot->srna,
3390 "keep_original",
3391 false,
3392 "Keep Original",
3393 "Keep original strokes and create a copy before reprojecting");
3395
3396 RNA_def_float(ot->srna, "offset", 0.0f, 0.0f, 10.0f, "Surface Offset", "", 0.0f, 10.0f);
3397}
3398
3400/* -------------------------------------------------------------------- */
3403
3404/* Poll callback for snap operators */
3405/* NOTE: For now, we only allow these in the 3D view, as other editors do not
3406 * define a cursor or grid-step which can be used.
3407 */
3409{
3411 return false;
3412 }
3413
3414 const ScrArea *area = CTX_wm_area(C);
3415 if (!(area && area->spacetype == SPACE_VIEW3D)) {
3416 return false;
3417 }
3418 const ARegion *region = CTX_wm_region(C);
3419 if (!(region && region->regiontype == RGN_TYPE_WINDOW)) {
3420 return false;
3421 }
3422
3423 return true;
3424}
3425
3427{
3429
3430 const Scene &scene = *CTX_data_scene(C);
3431 Object &object = *CTX_data_active_object(C);
3432 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
3433 const View3D &v3d = *CTX_wm_view3d(C);
3434 const ARegion &region = *CTX_wm_region(C);
3435 const float grid_size = ED_view3d_grid_view_scale(&scene, &v3d, &region, nullptr);
3436
3437 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
3438 for (const MutableDrawingInfo &drawing_info : drawings) {
3439 bke::CurvesGeometry &curves = drawing_info.drawing.strokes_for_write();
3440 if (curves.is_empty()) {
3441 continue;
3442 }
3444 continue;
3445 }
3446
3448 {
3449 IndexMaskMemory memory;
3450 const IndexMask selected_points = ed::curves::retrieve_selected_points(
3451 curves, selection_name, memory);
3452
3453 const Layer &layer = grease_pencil.layer(drawing_info.layer_index);
3454 const float4x4 layer_to_world = layer.to_world_space(object);
3455 const float4x4 world_to_layer = math::invert(layer_to_world);
3456
3457 MutableSpan<float3> positions = curves.positions_for_write();
3458 if (selection_name == ".selection_handle_left") {
3459 positions = curves.handle_positions_left_for_write();
3460 }
3461 else if (selection_name == ".selection_handle_right") {
3462 positions = curves.handle_positions_right_for_write();
3463 }
3464 selected_points.foreach_index(GrainSize(4096), [&](const int point_i) {
3465 const float3 pos_world = math::transform_point(layer_to_world, positions[point_i]);
3466 const float3 pos_snapped = grid_size * math::floor(pos_world / grid_size + 0.5f);
3467 positions[point_i] = math::transform_point(world_to_layer, pos_snapped);
3468 });
3469 }
3470
3471 drawing_info.drawing.tag_positions_changed();
3475 WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, &grease_pencil);
3476 }
3477
3478 return OPERATOR_FINISHED;
3479}
3480
3482{
3483 ot->name = "Snap Selection to Grid";
3484 ot->idname = "GREASE_PENCIL_OT_snap_to_grid";
3485 ot->description = "Snap selected points to the nearest grid points";
3486
3489
3490 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3491}
3492
3494
3495/* -------------------------------------------------------------------- */
3498
3500{
3502
3503 const Scene &scene = *CTX_data_scene(C);
3504 Object &object = *CTX_data_active_object(C);
3505 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
3506 const bool use_offset = RNA_boolean_get(op->ptr, "use_offset");
3507 const float3 cursor_world = scene.cursor.location;
3508
3509 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
3510 for (const MutableDrawingInfo &drawing_info : drawings) {
3511 bke::CurvesGeometry &curves = drawing_info.drawing.strokes_for_write();
3512 if (curves.is_empty()) {
3513 continue;
3514 }
3516 continue;
3517 }
3518
3519 IndexMaskMemory selected_points_memory;
3521 selected_points_memory);
3522
3523 const Layer &layer = grease_pencil.layer(drawing_info.layer_index);
3524 const float4x4 layer_to_world = layer.to_world_space(object);
3525 const float4x4 world_to_layer = math::invert(layer_to_world);
3526 const float3 cursor_layer = math::transform_point(world_to_layer, cursor_world);
3527
3528 MutableSpan<float3> positions = curves.positions_for_write();
3529 if (use_offset) {
3530 const OffsetIndices points_by_curve = curves.points_by_curve();
3531 IndexMaskMemory selected_curves_memory;
3532 const IndexMask selected_curves = ed::curves::retrieve_selected_curves(
3533 curves, selected_curves_memory);
3534
3535 selected_curves.foreach_index(GrainSize(512), [&](const int curve_i) {
3536 const IndexRange points = points_by_curve[curve_i];
3537
3538 /* Offset from first point of the curve. */
3539 const float3 offset = cursor_layer - positions[points.first()];
3540 selected_points.slice_content(points).foreach_index(
3541 GrainSize(4096), [&](const int point_i) { positions[point_i] += offset; });
3542 });
3543 }
3544 else {
3545 /* Set all selected positions to the cursor location. */
3546 index_mask::masked_fill(positions, cursor_layer, selected_points);
3547 }
3548
3549 curves.calculate_bezier_auto_handles();
3550 drawing_info.drawing.tag_positions_changed();
3554 WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, &grease_pencil);
3555 }
3556
3557 return OPERATOR_FINISHED;
3558}
3559
3561{
3562 /* identifiers */
3563 ot->name = "Snap Selection to Cursor";
3564 ot->idname = "GREASE_PENCIL_OT_snap_to_cursor";
3565 ot->description = "Snap selected points/strokes to the cursor";
3566
3567 /* callbacks */
3570
3571 /* flags */
3572 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3573
3574 /* props */
3575 ot->prop = RNA_def_boolean(ot->srna,
3576 "use_offset",
3577 true,
3578 "With Offset",
3579 "Offset the entire stroke instead of selected points only");
3580}
3581
3583
3584/* -------------------------------------------------------------------- */
3587
3589 const Object &object,
3590 const GreasePencil &grease_pencil,
3591 float3 &r_centroid,
3592 float3 &r_min,
3593 float3 &r_max)
3594{
3596
3597 int num_selected = 0;
3598 r_centroid = float3(0.0f);
3599 r_min = float3(std::numeric_limits<float>::max());
3600 r_max = float3(std::numeric_limits<float>::lowest());
3601
3602 const Vector<DrawingInfo> drawings = retrieve_visible_drawings(scene, grease_pencil, false);
3603 for (const DrawingInfo &drawing_info : drawings) {
3604 const Layer &layer = grease_pencil.layer(drawing_info.layer_index);
3605 if (layer.is_locked()) {
3606 continue;
3607 }
3608 const bke::CurvesGeometry &curves = drawing_info.drawing.strokes();
3609 if (curves.is_empty()) {
3610 continue;
3611 }
3613 continue;
3614 }
3615
3616 IndexMaskMemory selected_points_memory;
3618 selected_points_memory);
3619 const float4x4 layer_to_world = layer.to_world_space(object);
3620
3621 Span<float3> positions = curves.positions();
3622 selected_points.foreach_index(GrainSize(4096), [&](const int point_i) {
3623 const float3 pos_world = math::transform_point(layer_to_world, positions[point_i]);
3624 r_centroid += pos_world;
3625 math::min_max(pos_world, r_min, r_max);
3626 });
3627 num_selected += selected_points.size();
3628 }
3629 if (num_selected == 0) {
3630 r_min = r_max = float3(0.0f);
3631 return false;
3632 }
3633
3634 r_centroid /= num_selected;
3635 return true;
3636}
3637
3639{
3640 Scene &scene = *CTX_data_scene(C);
3641 const Object &object = *CTX_data_active_object(C);
3642 const GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
3643 float3 &cursor = reinterpret_cast<float3 &>(scene.cursor.location);
3644
3645 float3 centroid, points_min, points_max;
3647 scene, object, grease_pencil, centroid, points_min, points_max))
3648 {
3649 return OPERATOR_FINISHED;
3650 }
3651
3652 switch (scene.toolsettings->transform_pivot_point) {
3654 cursor = math::midpoint(points_min, points_max);
3655 break;
3657 case V3D_AROUND_CURSOR:
3659 case V3D_AROUND_ACTIVE:
3660 cursor = centroid;
3661 break;
3662 default:
3664 }
3665
3668
3669 return OPERATOR_FINISHED;
3670}
3671
3673{
3674 /* identifiers */
3675 ot->name = "Snap Cursor to Selected Points";
3676 ot->idname = "GREASE_PENCIL_OT_snap_cursor_to_selected";
3677 ot->description = "Snap cursor to center of selected points";
3678
3679 /* callbacks */
3682
3683 /* flags */
3684 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3685}
3686
3688{
3689 float4x3 strokemat4x3 = float4x3(strokemat);
3690
3691 /*
3692 * We need the diagonal of ones to start from the bottom right instead top left to properly
3693 * apply the two matrices.
3694 *
3695 * i.e.
3696 * # # # # # # # #
3697 * We need # # # # Instead of # # # #
3698 * 0 0 0 1 0 0 1 0
3699 *
3700 */
3701 strokemat4x3[2][2] = 0.0f;
3702 strokemat4x3[3][2] = 1.0f;
3703
3704 return strokemat4x3;
3705}
3706
3708{
3709 const Scene *scene = CTX_data_scene(C);
3710 Object *object = CTX_data_active_object(C);
3711 ARegion *region = CTX_wm_region(C);
3712 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
3713
3714 std::atomic<bool> changed = false;
3715 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
3716 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3717 IndexMaskMemory memory;
3719 *object, info.drawing, info.layer_index, memory);
3720 if (strokes.is_empty()) {
3721 return;
3722 }
3723
3724 const bke::greasepencil::Layer &layer = grease_pencil.layer(info.layer_index);
3725 const float4x4 layer_space_to_world_space = layer.to_world_space(*object);
3726
3727 /* Calculate screen space points. */
3728 const float2 screen_start(RNA_int_get(op->ptr, "xstart"), RNA_int_get(op->ptr, "ystart"));
3729 const float2 screen_end(RNA_int_get(op->ptr, "xend"), RNA_int_get(op->ptr, "yend"));
3730 const float2 screen_direction = screen_end - screen_start;
3731 const float2 screen_tangent = screen_start + float2(-screen_direction[1], screen_direction[0]);
3732
3733 const bke::CurvesGeometry &curves = info.drawing.strokes();
3734 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
3735 const Span<float3> positions = curves.positions();
3737 const VArray<int> materials = *curves.attributes().lookup_or_default<int>(
3738 "material_index", bke::AttrDomain::Curve, 0);
3739
3740 Array<float4x2> texture_matrices(strokes.size());
3741
3742 strokes.foreach_index([&](const int curve_i, const int pos) {
3743 const int material_index = materials[curve_i];
3744
3745 const MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(object,
3746 material_index + 1);
3747 const bool is_radial = gp_style->gradient_type == GP_MATERIAL_GRADIENT_RADIAL;
3748
3749 const float texture_angle = gp_style->texture_angle;
3750 const float2 texture_scale = float2(gp_style->texture_scale);
3751 const float2 texture_offset = float2(gp_style->texture_offset);
3752
3753 const float2x2 texture_rotation = math::from_rotation<float2x2>(
3754 math::AngleRadian(texture_angle));
3755
3756 const float3 point = math::transform_point(layer_space_to_world_space,
3757 positions[points_by_curve[curve_i].first()]);
3758 const float3 normal = math::transform_direction(layer_space_to_world_space,
3759 normals[curve_i]);
3760
3761 const float4 plane = float4(normal, -math::dot(normal, point));
3762
3763 float3 start;
3764 float3 tangent;
3765 float3 end;
3766 ED_view3d_win_to_3d_on_plane(region, plane, screen_start, false, start);
3767 ED_view3d_win_to_3d_on_plane(region, plane, screen_tangent, false, tangent);
3768 ED_view3d_win_to_3d_on_plane(region, plane, screen_end, false, end);
3769
3770 const float3 origin = start;
3771 /* Invert the length by dividing by the length squared. */
3772 const float3 u_dir = (end - origin) / math::length_squared(end - origin);
3773 float3 v_dir = math::cross(u_dir, normal);
3774
3775 /* Flip the texture if need so that it is not mirrored. */
3776 if (math::dot(tangent - start, v_dir) < 0.0f) {
3777 v_dir = -v_dir;
3778 }
3779
3780 /* Calculate the texture space before the texture offset transformation. */
3781 const float4x2 base_texture_space = math::transpose(float2x4(
3782 float4(u_dir, -math::dot(u_dir, origin)), float4(v_dir, -math::dot(v_dir, origin))));
3783
3784 float3x2 offset_matrix = float3x2::identity();
3785
3786 if (is_radial) {
3787 /* Radial gradients are scaled down by a factor of 2 and have the center at 0.5 */
3788 offset_matrix *= 0.5f;
3789 offset_matrix[2] += float2(0.5f, 0.5f);
3790 }
3791
3792 /* For some reason 0.5 is added to the offset before being rendered, so remove it here. */
3793 offset_matrix[2] -= float2(0.5f, 0.5f);
3794
3795 offset_matrix = math::from_scale<float2x2>(texture_scale) * offset_matrix;
3796 offset_matrix = texture_rotation * offset_matrix;
3797 offset_matrix[2] -= texture_offset;
3798
3799 texture_matrices[pos] = (offset_matrix * expand_4x2_mat(base_texture_space)) *
3800 layer_space_to_world_space;
3801 });
3802
3803 info.drawing.set_texture_matrices(texture_matrices, strokes);
3804
3805 changed.store(true, std::memory_order_relaxed);
3806 });
3807
3808 if (changed) {
3809 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
3810 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
3811 }
3812
3814}
3815
3817 wmOperator *op,
3818 const wmEvent *event)
3819{
3821
3822 /* Check for mouse release. */
3823 if ((ret & OPERATOR_RUNNING_MODAL) != 0 && event->type == LEFTMOUSE && event->val == KM_RELEASE)
3824 {
3828 }
3829
3830 return ret;
3831}
3832
3834 wmOperator *op,
3835 const wmEvent *event)
3836{
3837 /* Invoke interactive line drawing (representing the gradient) in viewport. */
3839
3840 if ((ret & OPERATOR_RUNNING_MODAL) != 0) {
3841 ARegion *region = CTX_wm_region(C);
3842 if (region->regiontype == RGN_TYPE_WINDOW && event->type == LEFTMOUSE &&
3843 event->val == KM_PRESS)
3844 {
3845 wmGesture *gesture = static_cast<wmGesture *>(op->customdata);
3846 gesture->is_active = true;
3847 }
3848 }
3849
3850 return ret;
3851}
3852
3854{
3855 /* Identifiers. */
3856 ot->name = "Texture Gradient";
3857 ot->idname = "GREASE_PENCIL_OT_texture_gradient";
3858 ot->description = "Draw a line to set the fill material gradient for the selected strokes";
3859
3860 /* API callbacks. */
3866
3867 /* Flags. */
3868 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3869
3871}
3872
3874
3875/* -------------------------------------------------------------------- */
3878
3880{
3881 const Scene *scene = CTX_data_scene(C);
3882 Object *object = CTX_data_active_object(C);
3883 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
3884
3885 const CurveType dst_type = CurveType(RNA_enum_get(op->ptr, "type"));
3886 const bool use_handles = RNA_boolean_get(op->ptr, "use_handles");
3887
3888 bool changed = false;
3889 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
3890 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3892 IndexMaskMemory memory;
3894 *object, info.drawing, info.layer_index, memory);
3895 if (strokes.is_empty()) {
3896 return;
3897 }
3898
3900 options.convert_bezier_handles_to_poly_points = use_handles;
3901 options.convert_bezier_handles_to_catmull_rom_points = use_handles;
3902 options.keep_bezier_shape_as_nurbs = use_handles;
3903 options.keep_catmull_rom_shape_as_nurbs = use_handles;
3904
3905 curves = geometry::convert_curves(curves, strokes, dst_type, {}, options);
3907
3908 changed = true;
3909 });
3910
3911 if (changed) {
3912 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
3913 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
3914 }
3915
3916 return OPERATOR_FINISHED;
3917}
3918
3920{
3921 ot->name = "Set Curve Type";
3922 ot->idname = "GREASE_PENCIL_OT_set_curve_type";
3923 ot->description = "Set type of selected curves";
3924
3925 ot->invoke = WM_menu_invoke;
3928
3929 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3930
3931 ot->prop = RNA_def_enum(
3932 ot->srna, "type", rna_enum_curves_type_items, CURVE_TYPE_POLY, "Type", "Curve type");
3933
3934 RNA_def_boolean(ot->srna,
3935 "use_handles",
3936 false,
3937 "Handles",
3938 "Take handle information into account in the conversion");
3939}
3940
3942
3943/* -------------------------------------------------------------------- */
3946
3948{
3949 const Scene *scene = CTX_data_scene(C);
3950 Object *object = CTX_data_active_object(C);
3951 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
3952
3953 const HandleType dst_handle_type = HandleType(RNA_enum_get(op->ptr, "type"));
3954
3955 bool changed = false;
3956 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
3957 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
3959 if (!curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
3960 return;
3961 }
3962 IndexMaskMemory memory;
3964 *object, info.drawing, info.layer_index, memory);
3965 const IndexMask bezier_curves = curves.indices_for_curve_type(
3966 CURVE_TYPE_BEZIER, editable_strokes, memory);
3967
3968 const bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
3969 const VArraySpan<bool> selection = *attributes.lookup_or_default<bool>(
3970 ".selection", bke::AttrDomain::Point, true);
3971 const VArraySpan<bool> selection_left = *attributes.lookup_or_default<bool>(
3972 ".selection_handle_left", bke::AttrDomain::Point, true);
3973 const VArraySpan<bool> selection_right = *attributes.lookup_or_default<bool>(
3974 ".selection_handle_right", bke::AttrDomain::Point, true);
3975
3976 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
3977 MutableSpan<int8_t> handle_types_left = curves.handle_types_left_for_write();
3978 MutableSpan<int8_t> handle_types_right = curves.handle_types_right_for_write();
3979 bezier_curves.foreach_index(GrainSize(256), [&](const int curve_i) {
3980 const IndexRange points = points_by_curve[curve_i];
3981 for (const int point_i : points) {
3982 if (selection_left[point_i] || selection[point_i]) {
3983 handle_types_left[point_i] = int8_t(dst_handle_type);
3984 }
3985 if (selection_right[point_i] || selection[point_i]) {
3986 handle_types_right[point_i] = int8_t(dst_handle_type);
3987 }
3988 }
3989 });
3990
3991 curves.calculate_bezier_auto_handles();
3992 curves.tag_topology_changed();
3994
3995 changed = true;
3996 });
3997
3998 if (changed) {
3999 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4000 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4001 }
4002
4003 return OPERATOR_FINISHED;
4004}
4005
4007{
4008 ot->name = "Set Handle Type";
4009 ot->idname = "GREASE_PENCIL_OT_set_handle_type";
4010 ot->description = "Set the handle type for bezier curves";
4011
4012 ot->invoke = WM_menu_invoke;
4015
4016 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4017
4018 ot->prop = RNA_def_enum(
4019 ot->srna, "type", rna_enum_curves_handle_type_items, CURVE_TYPE_POLY, "Type", nullptr);
4020}
4021
4023
4024/* -------------------------------------------------------------------- */
4027
4029{
4030 const Scene *scene = CTX_data_scene(C);
4031 Object *object = CTX_data_active_object(C);
4032 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
4033
4034 const int resolution = RNA_int_get(op->ptr, "resolution");
4035
4036 bool changed = false;
4037 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
4038 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4040 IndexMaskMemory memory;
4042 *object, info.drawing, info.layer_index, memory);
4043 if (editable_strokes.is_empty()) {
4044 return;
4045 }
4046
4047 if (curves.is_single_type(CURVE_TYPE_POLY)) {
4048 return;
4049 }
4050
4051 index_mask::masked_fill(curves.resolution_for_write(), resolution, editable_strokes);
4053 changed = true;
4054 });
4055
4056 if (changed) {
4057 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4058 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4059 }
4060
4061 return OPERATOR_FINISHED;
4062}
4063
4065{
4066 ot->name = "Set Curve Resolution";
4067 ot->idname = "GREASE_PENCIL_OT_set_curve_resolution";
4068 ot->description = "Set resolution of selected curves";
4069
4072
4073 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4074
4075 RNA_def_int(ot->srna,
4076 "resolution",
4077 12,
4078 0,
4079 10000,
4080 "Resolution",
4081 "The resolution to use for each curve segment",
4082 1,
4083 64);
4084}
4085
4087
4088/* -------------------------------------------------------------------- */
4091
4093{
4094 const Scene *scene = CTX_data_scene(C);
4095 Object *object = CTX_data_active_object(C);
4096 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
4097
4098 bool changed = false;
4099 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
4100 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4102 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
4103 IndexMaskMemory memory;
4105 *object, info.drawing, info.layer_index, memory);
4106 if (editable_strokes.is_empty()) {
4107 return;
4108 }
4109
4110 if (attributes.contains("uv_rotation")) {
4111 if (editable_strokes.size() == curves.curves_num()) {
4112 attributes.remove("uv_rotation");
4113 }
4114 else {
4115 bke::SpanAttributeWriter<float> uv_rotations = attributes.lookup_for_write_span<float>(
4116 "uv_rotation");
4117 index_mask::masked_fill(uv_rotations.span, 0.0f, editable_strokes);
4118 uv_rotations.finish();
4119 }
4120 }
4121
4122 if (attributes.contains("uv_translation")) {
4123 if (editable_strokes.size() == curves.curves_num()) {
4124 attributes.remove("uv_translation");
4125 }
4126 else {
4127 bke::SpanAttributeWriter<float2> uv_translations =
4128 attributes.lookup_for_write_span<float2>("uv_translation");
4129 index_mask::masked_fill(uv_translations.span, float2(0.0f, 0.0f), editable_strokes);
4130 uv_translations.finish();
4131 }
4132 }
4133
4134 if (attributes.contains("uv_scale")) {
4135 if (editable_strokes.size() == curves.curves_num()) {
4136 attributes.remove("uv_scale");
4137 }
4138 else {
4140 "uv_scale");
4141 index_mask::masked_fill(uv_scales.span, float2(1.0f, 1.0f), editable_strokes);
4142 uv_scales.finish();
4143 }
4144 }
4145
4146 if (attributes.contains("uv_shear")) {
4147 if (editable_strokes.size() == curves.curves_num()) {
4148 attributes.remove("uv_shear");
4149 }
4150 else {
4151 bke::SpanAttributeWriter<float> uv_shears = attributes.lookup_for_write_span<float>(
4152 "uv_shear");
4153 index_mask::masked_fill(uv_shears.span, 0.0f, editable_strokes);
4154 uv_shears.finish();
4155 }
4156 }
4157
4159 changed = true;
4160 });
4161
4162 if (changed) {
4163 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4164 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4165 }
4166
4167 return OPERATOR_FINISHED;
4168}
4169
4171{
4172 /* Identifiers. */
4173 ot->name = "Reset UVs";
4174 ot->idname = "GREASE_PENCIL_OT_reset_uvs";
4175 ot->description = "Reset UV transformation to default values";
4176
4177 /* Callbacks. */
4180
4181 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4182}
4183
4185{
4186 const Scene &scene = *CTX_data_scene(C);
4187 Object &object = *CTX_data_active_object(C);
4188 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
4189 std::atomic<bool> changed = false;
4190
4191 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
4192 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4193 IndexMaskMemory memory;
4194 const IndexMask selected_points =
4196 object, info.drawing, info.layer_index, memory);
4197
4198 if (selected_points.is_empty()) {
4199 return;
4200 }
4201
4203 selected_points);
4205 changed.store(true, std::memory_order_relaxed);
4206 });
4207
4208 if (changed) {
4209 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4210 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4211 return OPERATOR_FINISHED;
4212 }
4213
4214 return OPERATOR_CANCELLED;
4215}
4216
4218{
4219 /* Identifiers. */
4220 ot->name = "Split stroke";
4221 ot->idname = "GREASE_PENCIL_OT_stroke_split";
4222 ot->description = "Split selected points to a new stroke";
4223
4224 /* Callbacks. */
4227
4228 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4229}
4230
4232
4233/* -------------------------------------------------------------------- */
4236
4237enum class RemoveFillGuidesMode : int8_t { ActiveFrame = 0, AllFrames = 1 };
4238
4240{
4241 using namespace blender::bke::greasepencil;
4242 const Scene &scene = *CTX_data_scene(C);
4243 Object &object = *CTX_data_active_object(C);
4244 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
4245
4246 const RemoveFillGuidesMode mode = RemoveFillGuidesMode(RNA_enum_get(op->ptr, "mode"));
4247
4248 std::atomic<bool> changed = false;
4251 for (const int layer_i : grease_pencil.layers().index_range()) {
4252 const Layer &layer = grease_pencil.layer(layer_i);
4253 if (!layer.is_editable()) {
4254 continue;
4255 }
4256 if (Drawing *drawing = grease_pencil.get_editable_drawing_at(layer, scene.r.cfra)) {
4257 drawings.append({*drawing, layer_i, scene.r.cfra, 1.0f});
4258 }
4259 }
4260 }
4261 else if (mode == RemoveFillGuidesMode::AllFrames) {
4262 for (const int layer_i : grease_pencil.layers().index_range()) {
4263 const Layer &layer = grease_pencil.layer(layer_i);
4264 if (!layer.is_editable()) {
4265 continue;
4266 }
4267 for (const auto [frame_number, frame] : layer.frames().items()) {
4268 if (Drawing *drawing = grease_pencil.get_editable_drawing_at(layer, frame_number)) {
4269 drawings.append({*drawing, layer_i, frame_number, 1.0f});
4270 }
4271 }
4272 }
4273 }
4274 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4277 changed.store(true, std::memory_order_relaxed);
4278 }
4279 });
4280
4281 if (changed) {
4282 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4283 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4284 return OPERATOR_FINISHED;
4285 }
4286
4287 return OPERATOR_CANCELLED;
4288}
4289
4291{
4292 static const EnumPropertyItem rna_mode_items[] = {
4293 {int(RemoveFillGuidesMode::ActiveFrame), "ACTIVE_FRAME", 0, "Active Frame", ""},
4294 {int(RemoveFillGuidesMode::AllFrames), "ALL_FRAMES", 0, "All Frames", ""},
4295 {0, nullptr, 0, nullptr, nullptr},
4296 };
4297
4298 /* Identifiers. */
4299 ot->name = "Remove Fill Guides";
4300 ot->idname = "GREASE_PENCIL_OT_remove_fill_guides";
4301 ot->description = "Remove all the strokes that were created from the fill tool as guides";
4302
4303 /* Callbacks. */
4306
4307 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4308
4309 ot->prop = RNA_def_enum(
4310 ot->srna, "mode", rna_mode_items, int(RemoveFillGuidesMode::AllFrames), "Mode", "");
4311}
4312
4313/* -------------------------------------------------------------------- */
4316
4317enum class OutlineMode : int8_t {
4318 View = 0,
4320 Side = 2,
4321 Top = 3,
4324};
4325
4327 {int(OutlineMode::View), "VIEW", 0, "View", ""},
4328 {int(OutlineMode::Front), "FRONT", 0, "Front", ""},
4329 {int(OutlineMode::Side), "SIDE", 0, "Side", ""},
4330 {int(OutlineMode::Top), "TOP", 0, "Top", ""},
4331 {int(OutlineMode::Cursor), "CURSOR", 0, "Cursor", ""},
4332 {int(OutlineMode::Camera), "CAMERA", 0, "Camera", ""},
4333 {0, nullptr, 0, nullptr, nullptr},
4334};
4335
4337{
4339
4340 const Scene *scene = CTX_data_scene(C);
4341 Object *object = CTX_data_active_object(C);
4342 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
4343
4344 const float radius = RNA_float_get(op->ptr, "radius");
4345 const float offset_factor = RNA_float_get(op->ptr, "offset_factor");
4346 const int corner_subdivisions = RNA_int_get(op->ptr, "corner_subdivisions");
4347 const float outline_offset = radius * offset_factor;
4348 const int mat_nr = -1;
4349
4350 const OutlineMode mode = OutlineMode(RNA_enum_get(op->ptr, "type"));
4351
4352 float4x4 viewinv = float4x4::identity();
4353 switch (mode) {
4354 case OutlineMode::View: {
4356 viewinv = float4x4(rv3d->viewmat);
4357 break;
4358 }
4359 case OutlineMode::Front:
4360 viewinv = float4x4({1.0f, 0.0f, 0.0f, 0.0f},
4361 {0.0f, 0.0f, 1.0f, 0.0f},
4362 {0.0f, 1.0f, 0.0f, 0.0f},
4363 {0.0f, 0.0f, 0.0f, 1.0f});
4364 break;
4365 case OutlineMode::Side:
4366 viewinv = float4x4({0.0f, 0.0f, 1.0f, 0.0f},
4367 {0.0f, 1.0f, 0.0f, 0.0f},
4368 {1.0f, 0.0f, 0.0f, 0.0f},
4369 {0.0f, 0.0f, 0.0f, 1.0f});
4370 break;
4371 case OutlineMode::Top:
4372 viewinv = float4x4::identity();
4373 break;
4374 case OutlineMode::Cursor: {
4375 viewinv = scene->cursor.matrix<float4x4>();
4376 break;
4377 }
4379 viewinv = scene->camera->world_to_object();
4380 break;
4381 default:
4383 break;
4384 }
4385
4386 bool changed = false;
4387 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
4388 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4389 IndexMaskMemory memory;
4391 *object, info.drawing, info.layer_index, memory);
4392 if (editable_strokes.is_empty()) {
4393 return;
4394 }
4395
4396 const Layer &layer = grease_pencil.layer(info.layer_index);
4397 const float4x4 viewmat = viewinv * layer.to_world_space(*object);
4398
4400 editable_strokes,
4401 viewmat,
4402 corner_subdivisions,
4403 radius,
4404 outline_offset,
4405 mat_nr);
4406
4407 info.drawing.strokes_for_write().remove_curves(editable_strokes, {});
4408
4409 /* Join the outline stroke into the drawing. */
4410 Curves *strokes = bke::curves_new_nomain(std::move(outline));
4411
4412 Curves *other_curves = bke::curves_new_nomain(std::move(info.drawing.strokes_for_write()));
4413 const std::array<bke::GeometrySet, 2> geometry_sets = {
4415
4416 info.drawing.strokes_for_write() = std::move(
4417 geometry::join_geometries(geometry_sets, {}).get_curves_for_write()->geometry.wrap());
4418
4420 changed = true;
4421 });
4422
4423 if (changed) {
4424 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4425 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4426 }
4427
4428 return OPERATOR_FINISHED;
4429}
4430
4432{
4433 /* Identifiers. */
4434 ot->name = "Outline";
4435 ot->idname = "GREASE_PENCIL_OT_outline";
4436 ot->description = "Convert selected strokes to perimeter";
4437
4438 /* Callbacks. */
4441
4442 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4443
4444 /* Properties */
4445 ot->prop = RNA_def_enum(
4446 ot->srna, "type", prop_outline_modes, int(OutlineMode::View), "Projection Mode", "");
4447 RNA_def_float_distance(ot->srna, "radius", 0.01f, 0.0f, 10.0f, "Radius", "", 0.0f, 10.0f);
4449 ot->srna, "offset_factor", -1.0f, -1.0f, 1.0f, "Offset Factor", "", -1.0f, 1.0f);
4450 RNA_def_int(ot->srna, "corner_subdivisions", 2, 0, 10, "Corner Subdivisions", "", 0, 5);
4451}
4452
4454
4455/* -------------------------------------------------------------------- */
4458
4460 const IndexMask &selection,
4461 const float threshold)
4462{
4463 const VArray<float> thresholds = VArray<float>::ForSingle(threshold, curves.curves_num());
4464 /* TODO: Detect or manually provide corners. */
4465 const VArray<bool> corners = VArray<bool>::ForSingle(false, curves.points_num());
4467 curves, selection, thresholds, corners, geometry::FitMethod::Refit, {});
4468}
4469
4471 const IndexMask &selection,
4472 const float threshold)
4473{
4474 if (curves.is_single_type(CURVE_TYPE_CATMULL_ROM)) {
4475 return;
4476 }
4477 IndexMaskMemory memory;
4478 const IndexMask non_catmull_rom_curves_selection =
4479 curves.indices_for_curve_type(CURVE_TYPE_CATMULL_ROM, selection, memory)
4480 .complement(selection, memory);
4481 BLI_assert(!non_catmull_rom_curves_selection.is_empty());
4482 curves = geometry::resample_to_evaluated(curves, non_catmull_rom_curves_selection);
4483
4484 /* To avoid having too many control points, simplify the position attribute based on the
4485 * threshold. This doesn't replace an actual curve fitting (which would be better), but
4486 * is a decent approximation for the meantime. */
4487 const IndexMask points_to_remove = geometry::simplify_curve_attribute(
4488 curves.positions(),
4489 non_catmull_rom_curves_selection,
4490 curves.points_by_curve(),
4491 curves.cyclic(),
4492 threshold,
4493 curves.positions(),
4494 memory);
4495 curves.remove_points(points_to_remove, {});
4496
4498 options.convert_bezier_handles_to_poly_points = false;
4499 options.convert_bezier_handles_to_catmull_rom_points = false;
4500 options.keep_bezier_shape_as_nurbs = true;
4501 options.keep_catmull_rom_shape_as_nurbs = true;
4503 curves, non_catmull_rom_curves_selection, CURVE_TYPE_CATMULL_ROM, {}, options);
4504}
4505
4507{
4508 if (curves.is_single_type(CURVE_TYPE_POLY)) {
4509 return;
4510 }
4511 IndexMaskMemory memory;
4512 const IndexMask non_poly_curves_selection = curves
4513 .indices_for_curve_type(
4514 CURVE_TYPE_POLY, selection, memory)
4515 .complement(selection, memory);
4516 BLI_assert(!non_poly_curves_selection.is_empty());
4517 curves = geometry::resample_to_evaluated(curves, non_poly_curves_selection);
4518}
4519
4521 const IndexMask &selection,
4522 const float threshold)
4523{
4524 if (curves.is_single_type(CURVE_TYPE_BEZIER)) {
4525 return;
4526 }
4527 IndexMaskMemory memory;
4528 const IndexMask poly_curves_selection = curves.indices_for_curve_type(
4529 CURVE_TYPE_POLY, selection, memory);
4530 if (!poly_curves_selection.is_empty()) {
4531 curves = fit_poly_curves(curves, poly_curves_selection, threshold);
4532 }
4533
4535 options.convert_bezier_handles_to_poly_points = false;
4536 options.convert_bezier_handles_to_catmull_rom_points = false;
4537 options.keep_bezier_shape_as_nurbs = true;
4538 options.keep_catmull_rom_shape_as_nurbs = true;
4540}
4541
4543 const IndexMask &selection,
4544 const float threshold)
4545{
4546 if (curves.is_single_type(CURVE_TYPE_NURBS)) {
4547 return;
4548 }
4549
4550 IndexMaskMemory memory;
4551 const IndexMask poly_curves_selection = curves.indices_for_curve_type(
4552 CURVE_TYPE_POLY, selection, memory);
4553 if (!poly_curves_selection.is_empty()) {
4554 curves = fit_poly_curves(curves, poly_curves_selection, threshold);
4555 }
4556
4558 options.convert_bezier_handles_to_poly_points = false;
4559 options.convert_bezier_handles_to_catmull_rom_points = false;
4560 options.keep_bezier_shape_as_nurbs = true;
4561 options.keep_catmull_rom_shape_as_nurbs = true;
4563}
4564
4566{
4567 const Scene *scene = CTX_data_scene(C);
4568 Object *object = CTX_data_active_object(C);
4569 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
4570
4571 const CurveType dst_type = CurveType(RNA_enum_get(op->ptr, "type"));
4572 const float threshold = RNA_float_get(op->ptr, "threshold");
4573
4574 std::atomic<bool> changed = false;
4575 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
4576 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
4578 IndexMaskMemory memory;
4580 *object, info.drawing, info.layer_index, memory);
4581 if (strokes.is_empty()) {
4582 return;
4583 }
4584
4585 switch (dst_type) {
4587 convert_to_catmull_rom(curves, strokes, threshold);
4588 break;
4589 case CURVE_TYPE_POLY:
4590 convert_to_poly(curves, strokes);
4591 break;
4592 case CURVE_TYPE_BEZIER:
4593 convert_to_bezier(curves, strokes, threshold);
4594 break;
4595 case CURVE_TYPE_NURBS:
4596 convert_to_nurbs(curves, strokes, threshold);
4597 break;
4598 }
4599
4601 changed.store(true, std::memory_order_relaxed);
4602 });
4603
4604 if (changed) {
4605 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
4606 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
4607 }
4608
4609 return OPERATOR_FINISHED;
4610}
4611
4613{
4614 uiLayout *layout = op->layout;
4616
4618
4619 uiLayoutSetPropSep(layout, true);
4620 uiLayoutSetPropDecorate(layout, false);
4621
4622 layout->prop(&ptr, "type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
4623
4624 const CurveType dst_type = CurveType(RNA_enum_get(op->ptr, "type"));
4625
4626 if (dst_type == CURVE_TYPE_POLY) {
4627 return;
4628 }
4629
4630 layout->prop(&ptr, "threshold", UI_ITEM_NONE, std::nullopt, ICON_NONE);
4631}
4632
4634{
4635 ot->name = "Convert Curve Type";
4636 ot->idname = "GREASE_PENCIL_OT_convert_curve_type";
4637 ot->description = "Convert type of selected curves";
4638
4639 ot->invoke = WM_menu_invoke;
4643
4644 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4645
4646 ot->prop = RNA_def_enum(
4647 ot->srna, "type", rna_enum_curves_type_items, CURVE_TYPE_POLY, "Type", "");
4649
4650 PropertyRNA *prop = RNA_def_float(
4651 ot->srna,
4652 "threshold",
4653 0.01f,
4654 0.0f,
4655 100.0f,
4656 "Threshold",
4657 "The distance that the resulting points are allowed to be within",
4658 0.0f,
4659 100.0f);
4661}
4662
4664
4665} // namespace blender::ed::greasepencil
4666
4668{
4669 using namespace blender::ed::greasepencil;
4708}
4709
4710/* -------------------------------------------------------------------- */
4713
4714namespace blender::ed::greasepencil {
4715
4716/* Note: the `duplicate_layer` API would be nicer, but only supports duplicating groups from the
4717 * same datablock. */
4720 const bke::greasepencil::Layer &layer_src)
4721{
4722 using namespace blender::bke::greasepencil;
4723
4724 Layer &layer_dst = grease_pencil_dst.add_layer(group_dst, layer_src.name());
4725 BKE_grease_pencil_copy_layer_parameters(layer_src, layer_dst);
4726
4727 layer_dst.frames_for_write() = layer_src.frames();
4728 layer_dst.tag_frames_map_changed();
4729
4730 return layer_dst;
4731}
4732
4734 GreasePencil &grease_pencil_dst,
4736 const bke::greasepencil::LayerGroup &group_src,
4737 Map<StringRefNull, StringRefNull> &layer_name_map);
4738
4739static void copy_layer_group_content(GreasePencil &grease_pencil_dst,
4741 const bke::greasepencil::LayerGroup &group_src,
4742 Map<StringRefNull, StringRefNull> &layer_name_map)
4743{
4744 using namespace blender::bke::greasepencil;
4745
4746 LISTBASE_FOREACH (GreasePencilLayerTreeNode *, child, &group_src.children) {
4747 switch (child->type) {
4748 case GP_LAYER_TREE_LEAF: {
4749 Layer &layer_src = reinterpret_cast<GreasePencilLayer *>(child)->wrap();
4750 Layer &layer_dst = copy_layer(grease_pencil_dst, group_dst, layer_src);
4751 layer_name_map.add_new(layer_src.name(), layer_dst.name());
4752 break;
4753 }
4754 case GP_LAYER_TREE_GROUP: {
4755 LayerGroup &group_src = reinterpret_cast<GreasePencilLayerTreeGroup *>(child)->wrap();
4756 copy_layer_group_recursive(grease_pencil_dst, group_dst, group_src, layer_name_map);
4757 break;
4758 }
4759 }
4760 }
4761}
4762
4764 GreasePencil &grease_pencil_dst,
4766 const bke::greasepencil::LayerGroup &group_src,
4767 Map<StringRefNull, StringRefNull> &layer_name_map)
4768{
4769 bke::greasepencil::LayerGroup &group_dst = grease_pencil_dst.add_layer_group(
4770 parent_dst, group_src.base.name);
4772
4773 copy_layer_group_content(grease_pencil_dst, group_dst, group_src, layer_name_map);
4774 return group_dst;
4775}
4776
4778{
4779 BLI_assert(object.type == OB_GREASE_PENCIL);
4780 Array<int> material_index_map(*BKE_object_material_len_p(&object));
4781 for (const int i : material_index_map.index_range()) {
4782 Material *material = BKE_object_material_get(&object, i + 1);
4783 if (material != nullptr) {
4784 material_index_map[i] = materials.index_of_or_add(material);
4785 }
4786 else {
4787 material_index_map[i] = 0;
4788 }
4789 }
4790 return material_index_map;
4791}
4792
4794 const Span<int> material_index_map)
4795{
4797 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
4798 /* Validate material indices and add missing materials. */
4799 bke::SpanAttributeWriter<int> material_writer = attributes.lookup_or_add_for_write_span<int>(
4800 "material_index", bke::AttrDomain::Curve);
4801 threading::parallel_for(curves.curves_range(), 1024, [&](const IndexRange range) {
4802 for (const int curve_i : range) {
4803 material_writer.span[curve_i] = material_index_map[material_writer.span[curve_i]];
4804 }
4805 });
4806 material_writer.finish();
4807}
4808
4810 GreasePencil &grease_pencil,
4811 const ListBase &vertex_group_names)
4812{
4813 Map<StringRefNull, StringRefNull> vertex_group_map;
4814 LISTBASE_FOREACH (bDeformGroup *, dg, &vertex_group_names) {
4815 bDeformGroup *vgroup = static_cast<bDeformGroup *>(MEM_dupallocN(dg));
4816 BKE_object_defgroup_unique_name(vgroup, &object);
4817 BLI_addtail(&grease_pencil.vertex_group_names, vgroup);
4818 vertex_group_map.add_new(dg->name, vgroup->name);
4819 }
4820 return vertex_group_map;
4821}
4822
4824 const Map<StringRefNull, StringRefNull> &vertex_group_map)
4825{
4827 STRNCPY(dg->name, vertex_group_map.lookup(dg->name).c_str());
4828 }
4829
4830 /* Indices in vertex weights remain valid, they are local to the drawing's vertex groups.
4831 * Only the names of the groups change. */
4832}
4833
4835 Object &ob_src,
4836 Object &ob_dst,
4837 VectorSet<Material *> &materials)
4838{
4839 using namespace blender::bke::greasepencil;
4840
4841 /* Skip if the datablock is already used by the active object. */
4842 if (ob_src.data == ob_dst.data) {
4843 return;
4844 }
4845
4846 BLI_assert(ob_src.type == OB_GREASE_PENCIL);
4847 BLI_assert(ob_dst.type == OB_GREASE_PENCIL);
4848 GreasePencil &grease_pencil_src = *static_cast<GreasePencil *>(ob_src.data);
4849 GreasePencil &grease_pencil_dst = *static_cast<GreasePencil *>(ob_dst.data);
4850 /* Number of existing layers that don't need to be updated. */
4851 const int orig_layers_num = grease_pencil_dst.layers().size();
4852
4854 ob_dst, grease_pencil_dst, grease_pencil_src.vertex_group_names);
4855 const Array<int> material_index_map = add_materials_to_map(ob_src, materials);
4856
4857 /* Concatenate drawing arrays. Existing drawings in dst keep their position, new drawings are
4858 * mapped to the new index range. */
4859 const int new_drawing_array_num = grease_pencil_dst.drawing_array_num +
4860 grease_pencil_src.drawing_array_num;
4861 GreasePencilDrawingBase **new_drawing_array = static_cast<GreasePencilDrawingBase **>(
4862 MEM_malloc_arrayN(new_drawing_array_num, sizeof(GreasePencilDrawingBase *), __func__));
4863 MutableSpan<GreasePencilDrawingBase *> new_drawings = {new_drawing_array, new_drawing_array_num};
4864 const IndexRange new_drawings_dst = IndexRange::from_begin_size(
4865 0, grease_pencil_dst.drawing_array_num);
4866 const IndexRange new_drawings_src = IndexRange::from_begin_size(
4867 grease_pencil_dst.drawing_array_num, grease_pencil_src.drawing_array_num);
4868
4869 copy_drawing_array(grease_pencil_dst.drawings(), new_drawings.slice(new_drawings_dst));
4870 copy_drawing_array(grease_pencil_src.drawings(), new_drawings.slice(new_drawings_src));
4871
4872 /* Free existing drawings array. */
4873 grease_pencil_dst.resize_drawings(0);
4874 grease_pencil_dst.drawing_array = new_drawing_array;
4875 grease_pencil_dst.drawing_array_num = new_drawing_array_num;
4876
4877 /* Maps original names of source layers to new unique layer names. */
4878 Map<StringRefNull, StringRefNull> layer_name_map;
4879 /* Only copy the content of the root group, not the root node itself. */
4880 copy_layer_group_content(grease_pencil_dst,
4881 grease_pencil_dst.root_group(),
4882 grease_pencil_src.root_group(),
4883 layer_name_map);
4884
4885 /* Copy custom attributes for new layers. */
4886 CustomData_merge_layout(&grease_pencil_src.layers_data,
4887 &grease_pencil_dst.layers_data,
4890 grease_pencil_dst.layers().size());
4891 CustomData_copy_data(&grease_pencil_src.layers_data,
4892 &grease_pencil_dst.layers_data,
4893 0,
4894 orig_layers_num,
4895 grease_pencil_src.layers().size());
4896
4897 /* Fix names, indices and transforms to keep relationships valid. */
4898 for (const int layer_index : grease_pencil_dst.layers().index_range()) {
4899 Layer &layer = *grease_pencil_dst.layers_for_write()[layer_index];
4900 const bool is_orig_layer = (layer_index < orig_layers_num);
4901 const float4x4 old_layer_to_world = (is_orig_layer ? layer.to_world_space(ob_dst) :
4902 layer.to_world_space(ob_src));
4903
4904 /* Update newly added layers. */
4905 if (!is_orig_layer) {
4906 /* Update name references for masks. */
4907 LISTBASE_FOREACH (GreasePencilLayerMask *, dst_mask, &layer.masks) {
4908 const StringRefNull *new_mask_name = layer_name_map.lookup_ptr(dst_mask->layer_name);
4909 if (new_mask_name) {
4910 MEM_SAFE_FREE(dst_mask->layer_name);
4911 dst_mask->layer_name = BLI_strdup(new_mask_name->c_str());
4912 }
4913 }
4914 /* Shift drawing indices to match the new drawings array. */
4915 for (const int key : layer.frames_for_write().keys()) {
4916 int &drawing_index = layer.frames_for_write().lookup(key).drawing_index;
4917 drawing_index = new_drawings_src[drawing_index];
4918 }
4919 }
4920
4921 /* Layer parent object may become invalid. This can be an original layer pointing at the joined
4922 * object which gets destroyed, or a new layer that points at the target object which is now
4923 * its owner. */
4924 if (ELEM(layer.parent, &ob_dst, &ob_src)) {
4925 layer.parent = nullptr;
4926 }
4927
4928 /* Apply relative object transform to new drawings to keep world-space positions unchanged.
4929 * Be careful where the matrix is computed: changing the parent pointer (above) can affect
4930 * this! */
4931 const float4x4 new_layer_to_world = layer.to_world_space(ob_dst);
4932 for (const int key : layer.frames_for_write().keys()) {
4933 const int drawing_index = layer.frames_for_write().lookup(key).drawing_index;
4934 GreasePencilDrawingBase *drawing_base = grease_pencil_dst.drawings()[drawing_index];
4935 if (drawing_base->type != GP_DRAWING) {
4936 continue;
4937 }
4938 Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base)->wrap();
4940 curves.transform(math::invert(new_layer_to_world) * old_layer_to_world);
4941
4942 if (!is_orig_layer) {
4943 remap_vertex_groups(drawing, vertex_group_map);
4944 remap_material_indices(drawing, material_index_map);
4945 }
4946 }
4947 }
4948
4949 /* Rename animation paths to layers. */
4950 BKE_fcurves_main_cb(&bmain, [&](ID *id, FCurve *fcu) {
4951 if (id == &grease_pencil_src.id && fcu->rna_path && strstr(fcu->rna_path, "layers[")) {
4952 /* Have to use linear search, the layer name map only contains sub-strings of RNA paths. */
4953 for (auto [name_src, name_dst] : layer_name_map.items()) {
4954 if (name_dst != name_src) {
4955 const char *old_path = fcu->rna_path;
4957 id, fcu->rna_path, "layers", name_src.c_str(), name_dst.c_str(), 0, 0, false);
4958 if (old_path != fcu->rna_path) {
4959 /* Stop after first match. */
4960 break;
4961 }
4962 }
4963 }
4964 }
4965 /* Fix driver targets. */
4966 if (fcu->driver) {
4967 LISTBASE_FOREACH (DriverVar *, dvar, &fcu->driver->variables) {
4968 /* Only change the used targets, since the others will need fixing manually anyway. */
4970 if (dtar->id != &grease_pencil_src.id) {
4971 continue;
4972 }
4973 dtar->id = &grease_pencil_dst.id;
4974
4975 if (dtar->rna_path && strstr(dtar->rna_path, "layers[")) {
4976 for (auto [name_src, name_dst] : layer_name_map.items()) {
4977 if (name_dst != name_src) {
4978 const char *old_path = fcu->rna_path;
4979 dtar->rna_path = BKE_animsys_fix_rna_path_rename(
4980 id, dtar->rna_path, "layers", name_src.c_str(), name_dst.c_str(), 0, 0, false);
4981 if (old_path != dtar->rna_path) {
4982 break;
4983 }
4984 }
4985 }
4986 }
4987 }
4989 }
4990 }
4991 });
4992
4993 /* Merge animation data of objects and grease pencil datablocks. */
4994 if (ob_src.adt) {
4995 if (ob_dst.adt == nullptr) {
4996 ob_dst.adt = BKE_animdata_copy(&bmain, ob_src.adt, 0);
4997 }
4998 else {
4999 BKE_animdata_merge_copy(&bmain, &ob_dst.id, &ob_src.id, ADT_MERGECOPY_KEEP_DST, false);
5000 }
5001
5002 if (ob_dst.adt->action) {
5004 }
5005 }
5006 if (grease_pencil_src.adt) {
5007 if (grease_pencil_dst.adt == nullptr) {
5008 grease_pencil_dst.adt = BKE_animdata_copy(&bmain, grease_pencil_src.adt, 0);
5009 }
5010 else {
5012 &bmain, &grease_pencil_dst.id, &grease_pencil_src.id, ADT_MERGECOPY_KEEP_DST, false);
5013 }
5014
5015 if (grease_pencil_dst.adt->action) {
5017 }
5018 }
5019}
5020
5021} // namespace blender::ed::greasepencil
5022
5024{
5025 Main *bmain = CTX_data_main(C);
5026 Scene *scene = CTX_data_scene(C);
5027 Object *ob_active = CTX_data_active_object(C);
5028
5029 /* Ensure we're in right mode and that the active object is correct. */
5030 if (!ob_active || ob_active->type != OB_GREASE_PENCIL) {
5031 return OPERATOR_CANCELLED;
5032 }
5033
5034 bool ok = false;
5035 CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) {
5036 if (ob_iter == ob_active) {
5037 ok = true;
5038 break;
5039 }
5040 }
5042 /* Active object must always selected. */
5043 if (ok == false) {
5044 BKE_report(op->reports, RPT_WARNING, "Active object is not a selected Grease Pencil");
5045 return OPERATOR_CANCELLED;
5046 }
5047
5048 Object *ob_dst = ob_active;
5049 GreasePencil *grease_pencil_dst = static_cast<GreasePencil *>(ob_dst->data);
5050
5053 *ob_dst, materials);
5054 /* Reassign material indices in the original layers, in case materials are deduplicated. */
5055 for (GreasePencilDrawingBase *drawing_base : grease_pencil_dst->drawings()) {
5056 if (drawing_base->type != GP_DRAWING) {
5057 continue;
5058 }
5060 reinterpret_cast<GreasePencilDrawing *>(drawing_base)->wrap();
5061 blender::ed::greasepencil::remap_material_indices(drawing, material_index_map);
5062 }
5063
5064 /* Loop and join all data. */
5065 CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) {
5066 if (ob_iter->type != OB_GREASE_PENCIL || ob_iter == ob_active) {
5067 continue;
5068 }
5069
5070 blender::ed::greasepencil::join_object_with_active(*bmain, *ob_iter, *ob_dst, materials);
5071
5072 /* Free the old object. */
5073 blender::ed::object::base_free_and_unlink(bmain, scene, ob_iter);
5074 }
5076
5077 /* Transfer material pointers. The material indices are updated for each drawing separately. */
5078 if (!materials.is_empty()) {
5079 /* Old C API, needs a const_cast but doesn't actually change anything. */
5080 Material **materials_ptr = const_cast<Material **>(materials.data());
5082 bmain, DEG_get_original(ob_dst), &materials_ptr, materials.size(), false);
5083 }
5084
5085 DEG_id_tag_update(&grease_pencil_dst->id, ID_RECALC_GEOMETRY);
5087
5090
5091 return OPERATOR_FINISHED;
5092}
5093
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
#define NC_SCENE
Definition WM_types.hh:375
#define ND_LAYER_CONTENT
Definition WM_types.hh:450
@ OPTYPE_UNDO
Definition WM_types.hh:182
@ OPTYPE_REGISTER
Definition WM_types.hh:180
#define NA_EDITED
Definition WM_types.hh:581
@ KM_PRESS
Definition WM_types.hh:308
@ KM_RELEASE
Definition WM_types.hh:309
#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
#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 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 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
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]
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 *)