Blender V4.5
grease_pencil_select.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 "BKE_attribute.hh"
10#include "BKE_context.hh"
11#include "BKE_curves.hh"
12#include "BKE_grease_pencil.hh"
13#include "BKE_object.hh"
14
16#include "BLI_index_mask.hh"
17#include "BLI_offset_indices.hh"
18#include "BLI_task.hh"
19
20#include "DNA_object_types.h"
21
22#include "DEG_depsgraph.hh"
24
25#include "ED_curves.hh"
26#include "ED_grease_pencil.hh"
27#include "ED_screen.hh"
28#include "ED_select_utils.hh"
29#include "ED_view3d.hh"
30
31#include "RNA_access.hh"
32#include "RNA_define.hh"
33#include "RNA_enum_types.hh"
34
35#include "WM_api.hh"
36
38
39/* -------------------------------------------------------------------- */
42
43inline int clamp_range(const IndexRange range, const int index)
44{
45 BLI_assert(!range.is_empty());
46 return std::clamp(index, int(range.first()), int(range.last()));
47}
48
56template<typename Fn>
57static int foreach_curve_segment(const CurveSegmentsData &segment_data,
58 const int curve_index,
59 const IndexRange points,
60 Fn &&fn)
61{
62 if (points.is_empty()) {
63 return 0;
64 }
65
66 const OffsetIndices segments_by_curve = OffsetIndices<int>(segment_data.segment_offsets);
67 const IndexRange segments = segments_by_curve[curve_index];
68
69 for (const int segment_i : segments) {
70 const int segment_point_i = segment_data.segment_start_points[segment_i];
71 const float segment_fraction = segment_data.segment_start_fractions[segment_i];
72
73 if (segment_i < segments.last()) {
74 const int next_segment_i = segment_i + 1;
75 const int next_segment_point_i = segment_data.segment_start_points[next_segment_i];
76 const float next_segment_fraction = segment_data.segment_start_fractions[next_segment_i];
77
78 /* Start point with zero fraction is included. */
79 const int first_point_i = (segment_fraction == 0.0f ?
80 segment_point_i :
81 clamp_range(points, segment_point_i + 1));
82 const int next_first_point_i = (next_segment_fraction == 0.0f ?
83 next_segment_point_i :
84 clamp_range(points, next_segment_point_i + 1));
85 const IndexRange points_range = IndexRange::from_begin_end(first_point_i,
86 next_first_point_i);
87 fn(segment_i, points_range, IndexRange());
88 }
89 else {
90 const int first_segment_point_i = segment_data.segment_start_points[segments.first()];
91 const float first_segment_fraction = segment_data.segment_start_fractions[segments.first()];
92 /* Start point with zero fraction is included. */
93 const int first_point_i = (segment_fraction == 0.0f ?
94 segment_point_i :
95 clamp_range(points, segment_point_i + 1));
96 /* End point with zero fraction is excluded. */
97 const int next_first_point_i = (first_segment_fraction == 0.0f ?
98 first_segment_point_i :
99 clamp_range(points, first_segment_point_i + 1));
100 const IndexRange points_range1 = IndexRange::from_begin_end(points.first(),
101 next_first_point_i);
102 const IndexRange points_range2 = IndexRange::from_begin_end_inclusive(first_point_i,
103 points.last());
104
105 fn(segment_i, points_range1, points_range2);
106 }
107 }
108 return segments.size();
109}
110
112 const IndexMask &selection_mask,
113 const bke::AttrDomain selection_domain,
114 const StringRef attribute_name,
115 const GrainSize grain_size,
116 const eSelectOp sel_op)
117{
118 if (selection_mask.is_empty()) {
119 return false;
120 }
121
122 const eCustomDataType create_type = CD_PROP_BOOL;
124 curves, selection_domain, create_type, attribute_name);
125
126 selection_mask.foreach_index(grain_size, [&](const int64_t element_i) {
127 ed::curves::apply_selection_operation_at_index(writer.span, element_i, sel_op);
128 });
129
130 writer.finish();
131
132 return true;
133}
134
136 const IndexMask &point_selection_mask,
137 const StringRef attribute_name,
138 const Curves2DBVHTree &tree_data,
139 const IndexRange tree_data_range,
140 const GrainSize grain_size,
141 const eSelectOp sel_op)
142{
143 /* Use regular selection for anything other than the ".selection" attribute. */
144 if (attribute_name != ".selection") {
146 curves, point_selection_mask, bke::AttrDomain::Point, attribute_name, grain_size, sel_op);
147 }
148
149 if (point_selection_mask.is_empty()) {
150 return false;
151 }
152 IndexMaskMemory memory;
153
154 const IndexMask changed_curve_mask = ed::curves::curve_mask_from_points(
155 curves, point_selection_mask, GrainSize(512), memory);
156
157 const OffsetIndices points_by_curve = curves.points_by_curve();
158 const Span<float2> screen_space_positions = tree_data.start_positions.as_span().slice(
159 tree_data_range);
160
162 curves, changed_curve_mask, screen_space_positions, tree_data, tree_data_range);
163
164 const OffsetIndices<int> segments_by_curve = OffsetIndices<int>(segment_data.segment_offsets);
165 const eCustomDataType create_type = CD_PROP_BOOL;
167 curves, bke::AttrDomain::Point, create_type, attribute_name);
168
169 /* Find all segments that have changed points and fill them. */
170 Array<bool> changed_points(curves.points_num());
171 point_selection_mask.to_bools(changed_points);
172
173 auto test_points_range = [&](const IndexRange range) -> bool {
174 for (const int point_i : range) {
175 if (changed_points[point_i]) {
176 return true;
177 }
178 }
179 return false;
180 };
181 auto update_points_range = [&](const IndexRange range) {
182 for (const int point_i : range) {
183 ed::curves::apply_selection_operation_at_index(attribute_writer.span, point_i, sel_op);
184 }
185 };
186
188 segments_by_curve.index_range(), grain_size.value, [&](const IndexRange range) {
189 for (const int curve_i : range) {
190 const IndexRange points = points_by_curve[curve_i];
191
192 const int num_segments = foreach_curve_segment(
193 segment_data,
194 curve_i,
195 points,
196 [&](const int /*segment_i*/, const IndexRange points1, const IndexRange points2) {
197 if (test_points_range(points1) || test_points_range(points2)) {
198 update_points_range(points1);
199 update_points_range(points2);
200 }
201 });
202 if (num_segments == 0 && test_points_range(points)) {
203 /* Cyclic curve without cuts, select all. */
204 update_points_range(points);
205 }
206 }
207 });
208
209 attribute_writer.finish();
210 return true;
211}
212
214 const eSelectOp sel_op,
215 SelectionUpdateFunc select_operation)
216{
217 using namespace blender;
218
219 Object *object = (vc->obedit ? vc->obedit : vc->obact);
220 const Object *ob_eval = DEG_get_evaluated(vc->depsgraph, object);
221 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
222
223 /* Get selection domain from tool settings. */
225 vc->scene->toolsettings, object);
226 const bool use_segment_selection = ED_grease_pencil_segment_selection_enabled(
227 vc->scene->toolsettings, object);
228
229 bool changed = false;
232
233 for (const Span<ed::greasepencil::MutableDrawingInfo> drawings : drawings_by_frame) {
234 if (drawings.is_empty()) {
235 continue;
236 }
237 const int frame_number = drawings.first().frame_number;
238
239 /* Construct BVH tree for all drawings on the same frame. */
242 if (use_segment_selection) {
244 *vc, *ob_eval, grease_pencil, drawings, frame_number);
245 }
246 OffsetIndices tree_data_by_drawing = OffsetIndices<int>(tree_data.drawing_offsets);
247
248 for (const int i_drawing : drawings.index_range()) {
249 // TODO optimize by lazy-initializing the tree data ONLY IF the changed_element_mask is not
250 // empty.
251
252 const ed::greasepencil::MutableDrawingInfo &info = drawings[i_drawing];
254 const Span<StringRef> selection_attribute_names =
256
257 IndexMaskMemory memory;
259 *object, info, selection_domain, memory);
260 if (elements.is_empty()) {
261 continue;
262 }
263
264 for (const StringRef attribute_name : selection_attribute_names) {
265 const IndexMask changed_element_mask = select_operation(
266 info, elements, attribute_name, memory);
267
268 /* Modes that un-set all elements not in the mask. */
269 if (ELEM(sel_op, SEL_OP_SET, SEL_OP_AND)) {
270 if (bke::SpanAttributeWriter<bool> selection =
271 curves.attributes_for_write().lookup_or_add_for_write_span<bool>(
272 attribute_name, selection_domain))
273 {
274 const IndexMask not_in_mask = changed_element_mask.complement(
275 selection.span.index_range(), memory);
276 ed::curves::fill_selection_false(selection.span, not_in_mask);
277 changed = true;
278 selection.finish();
279 }
280 }
281
282 if (use_segment_selection) {
283 /* Range of points in tree data matching this curve, for re-using screen space
284 * positions. */
285 const IndexRange tree_data_range = tree_data_by_drawing[i_drawing];
287 changed_element_mask,
288 attribute_name,
289 tree_data,
290 tree_data_range,
291 GrainSize(4096),
292 sel_op);
293 }
294 else {
296 changed_element_mask,
297 selection_domain,
298 attribute_name,
299 GrainSize(4096),
300 sel_op);
301 }
302 }
303 }
304 }
305
306 if (changed) {
307 /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a
308 * generic attribute for now. */
309 DEG_id_tag_update(static_cast<ID *>(object->data), ID_RECALC_GEOMETRY);
311 }
312
313 return changed;
314}
315
317
319{
320 int action = RNA_enum_get(op->ptr, "action");
321 Scene *scene = CTX_data_scene(C);
323 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
325 object);
326
327 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
328 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
329 IndexMaskMemory memory;
330 const IndexMask selectable_elements = retrieve_editable_elements(
331 *object, info, selection_domain, memory);
332 if (selectable_elements.is_empty()) {
333 return;
334 }
335 if (action == SEL_TOGGLE) {
337 selection_domain) ?
340 }
342 info.drawing.strokes_for_write(), selectable_elements, selection_domain, action);
343 });
344
345 /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
346 * attribute for now. */
347 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
348 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
349
350 return OPERATOR_FINISHED;
351}
352
354{
355 ot->name = "(De)select All Strokes";
356 ot->idname = "GREASE_PENCIL_OT_select_all";
357 ot->description = "(De)select all visible strokes";
358
359 ot->exec = select_all_exec;
361
363
365}
366
368{
370 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
372
376 const IndexMask & /*universe*/,
377 StringRef attribute_name,
378 IndexMaskMemory &memory) {
380 info.drawing.strokes(), attribute_name, false, memory);
381 });
382
383 /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
384 * attribute for now. */
385 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
386 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
387
388 return OPERATOR_FINISHED;
389}
390
392{
393 ot->name = "Select More";
394 ot->idname = "GREASE_PENCIL_OT_select_more";
395 ot->description = "Grow the selection by one point";
396
397 ot->exec = select_more_exec;
399
401}
402
404{
406 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
408
412 const IndexMask & /*universe*/,
413 StringRef attribute_name,
414 IndexMaskMemory &memory) {
416 info.drawing.strokes(), attribute_name, true, memory);
417 });
418
419 /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
420 * attribute for now. */
421 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
422 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
423
424 return OPERATOR_FINISHED;
425}
426
428{
429 ot->name = "Select Less";
430 ot->idname = "GREASE_PENCIL_OT_select_less";
431 ot->description = "Shrink the selection by one point";
432
433 ot->exec = select_less_exec;
435
437}
438
440{
441 Scene *scene = CTX_data_scene(C);
443 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
444
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, memory);
450 if (selectable_strokes.is_empty()) {
451 return;
452 }
454 });
455
456 /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
457 * attribute for now. */
458 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
459 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
460
461 return OPERATOR_FINISHED;
462}
463
465{
466 ot->name = "Select Linked";
467 ot->idname = "GREASE_PENCIL_OT_select_linked";
468 ot->description = "Select all points in curves with any point selection";
469
470 ot->exec = select_linked_exec;
472
474}
475
477{
478 using namespace blender;
479 const float ratio = RNA_float_get(op->ptr, "ratio");
481 Scene *scene = CTX_data_scene(C);
483 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
485 object);
487
488 /* Note: For segment selection this doesn't work very well, because it is based on random point
489 * selection. A segment has a high probability of getting at least one selected point and be
490 * itself selected.
491 * For better distribution the random value must be generated per segment and possibly weighted
492 * by segment length.
493 */
495 &vc,
498 const IndexMask & /*universe*/,
499 StringRef /*attribute_name*/,
500 IndexMaskMemory &memory) -> IndexMask {
501 const IndexMask selectable_elements = retrieve_editable_elements(
502 *object, info, selection_domain, memory);
503 if (selectable_elements.is_empty()) {
504 return {};
505 }
506 return random_mask(selectable_elements,
507 info.drawing.strokes().points_num(),
509 ratio,
510 memory);
511 });
512
513 /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
514 * attribute for now. */
515 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
516 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
517
518 return OPERATOR_FINISHED;
519}
520
522{
523 ot->name = "Select Random";
524 ot->idname = "GREASE_PENCIL_OT_select_random";
525 ot->description = "Selects random points from the current strokes selection";
526
527 ot->exec = select_random_exec;
529
531
533}
534
536{
537 const bool deselect_ends = RNA_boolean_get(op->ptr, "deselect_ends");
538 Scene *scene = CTX_data_scene(C);
540 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
541
542 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
543 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
545 });
546
547 /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
548 * attribute for now. */
549 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
550 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
551
552 return OPERATOR_FINISHED;
553}
554
556{
557 ot->name = "Select Alternate";
558 ot->idname = "GREASE_PENCIL_OT_select_alternate";
559 ot->description = "Select alternated points in strokes with already selected points";
560
563
565
566 RNA_def_boolean(ot->srna,
567 "deselect_ends",
568 false,
569 "Deselect Ends",
570 "(De)select the first and last point of each stroke");
571}
572
580
582 {int(SelectSimilarMode::LAYER), "LAYER", 0, "Layer", ""},
583 {int(SelectSimilarMode::MATERIAL), "MATERIAL", 0, "Material", ""},
584 {int(SelectSimilarMode::VERTEX_COLOR), "VERTEX_COLOR", 0, "Vertex Color", ""},
585 {int(SelectSimilarMode::RADIUS), "RADIUS", 0, "Radius", ""},
586 {int(SelectSimilarMode::OPACITY), "OPACITY", 0, "Opacity", ""},
587 {0, nullptr, 0, nullptr, nullptr},
588};
589
590template<typename T>
592 const bke::AttrDomain domain,
593 const StringRef attribute_id,
594 blender::Set<T> &r_value_set)
595{
596 T default_value;
597 CPPType::get<T>().default_construct(&default_value);
598
599 const bke::AttributeAccessor attributes = curves.attributes();
600 const VArraySpan<bool> selection = *attributes.lookup_or_default<bool>(
601 ".selection", domain, true);
602 const VArraySpan<T> values = *attributes.lookup_or_default<T>(
603 attribute_id, domain, default_value);
604
607 IndexRange(attributes.domain_size(domain)), 1024, [&](const IndexRange range) {
608 Set<T> &local_value_set = value_set_by_thread.local();
609 for (const int i : range) {
610 if (selection[i]) {
611 local_value_set.add(values[i]);
612 }
613 }
614 });
615
616 for (const Set<T> &local_value_set : value_set_by_thread) {
617 /* TODO is there a union function that can do this more efficiently? */
618 for (const T &key : local_value_set) {
619 r_value_set.add(key);
620 }
621 }
622}
623
624template<typename T, typename DistanceFn>
626 Object *object,
627 GreasePencil &grease_pencil,
628 const bke::AttrDomain selection_domain,
629 const StringRef attribute_id,
630 float threshold,
631 DistanceFn distance_fn)
632{
633 using namespace blender::ed::greasepencil;
634
635 T default_value;
636 CPPType::get<T>().default_construct(&default_value);
637
639 grease_pencil);
640
641 blender::Set<T> selected_values;
642 for (const MutableDrawingInfo &info : drawings) {
644 info.drawing.strokes(), selection_domain, attribute_id, selected_values);
645 }
646
647 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
650 curves, selection_domain, CD_PROP_BOOL);
651 MutableSpan<bool> selection = selection_writer.span.typed<bool>();
652 const VArraySpan<T> values = *curves.attributes().lookup_or_default<T>(
653 attribute_id, selection_domain, default_value);
654
655 IndexMaskMemory memory;
657 *object, info.drawing, info.layer_index, memory);
658
659 mask.foreach_index(GrainSize(1024), [&](const int index) {
660 if (selection[index]) {
661 return;
662 }
663 for (const T &test_value : selected_values) {
664 if (distance_fn(values[index], test_value) <= threshold) {
665 selection[index] = true;
666 }
667 }
668 });
669
670 selection_writer.finish();
671 });
672}
673
675 Object *object,
676 GreasePencil &grease_pencil,
677 bke::AttrDomain domain)
678{
680 grease_pencil);
681
682 blender::Set<int> selected_layers;
683 /* Layer is selected if any point is selected. */
684 for (const MutableDrawingInfo &info : drawings) {
685 const VArraySpan<bool> selection =
686 *info.drawing.strokes().attributes().lookup_or_default<bool>(".selection", domain, true);
687 for (const int i : selection.index_range()) {
688 if (selection[i]) {
689 selected_layers.add(info.layer_index);
690 break;
691 }
692 }
693 }
694
695 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
696 if (!selected_layers.contains(info.layer_index)) {
697 return;
698 }
699
700 IndexMaskMemory memory;
701 const IndexMask editable_elements = retrieve_editable_elements(*object, info, domain, memory);
702 if (editable_elements.is_empty()) {
703 return;
704 }
706 info.drawing.strokes_for_write(), editable_elements, domain, SEL_SELECT);
707 });
708}
709
711{
712 const SelectSimilarMode mode = SelectSimilarMode(RNA_enum_get(op->ptr, "mode"));
713 const float threshold = RNA_float_get(op->ptr, "threshold");
714 Scene *scene = CTX_data_scene(C);
716 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
718 object);
719
720 switch (mode) {
722 select_similar_by_layer(scene, object, grease_pencil, selection_domain);
723 break;
726 scene,
727 object,
728 grease_pencil,
729 selection_domain,
730 "material_index",
731 threshold,
732 [](const int a, const int b) -> float { return float(math::distance(a, b)); });
733 break;
736 scene,
737 object,
738 grease_pencil,
739 selection_domain,
740 "vertex_color",
741 threshold,
742 [](const ColorGeometry4f &a, const ColorGeometry4f &b) -> float {
743 return math::distance(float4(a), float4(b));
744 });
745 break;
748 scene,
749 object,
750 grease_pencil,
751 selection_domain,
752 "radius",
753 threshold,
754 [](const float a, const float b) -> float { return math::distance(a, b); });
755 break;
758 scene,
759 object,
760 grease_pencil,
761 selection_domain,
762 "opacity",
763 threshold,
764 [](const float a, const float b) -> float { return math::distance(a, b); });
765 break;
766 }
767
768 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
769 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
770
771 return OPERATOR_FINISHED;
772}
773
775{
776 ot->name = "Select Similar";
777 ot->idname = "GREASE_PENCIL_OT_select_similar";
778 ot->description = "Select all strokes with similar characteristics";
779
780 ot->invoke = WM_menu_invoke;
781 ot->exec = select_similar_exec;
783
785
786 ot->prop = RNA_def_enum(
787 ot->srna, "mode", select_similar_mode_items, int(SelectSimilarMode::LAYER), "Mode", "");
788
789 RNA_def_float(ot->srna, "threshold", 0.1f, 0.0f, FLT_MAX, "Threshold", "", 0.0f, 10.0f);
790}
791
793{
794 const int amount_start = RNA_int_get(op->ptr, "amount_start");
795 const int amount_end = RNA_int_get(op->ptr, "amount_end");
797 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
799
801 &vc,
804 const IndexMask & /*universe*/,
805 StringRef /*attribute_name*/,
806 IndexMaskMemory &memory) {
808 *object, info.drawing, info.layer_index, memory);
810 info.drawing.strokes(), selectable_strokes, amount_start, amount_end, false, memory);
811 });
812
813 /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
814 * attribute for now. */
815 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
816 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
817
818 return OPERATOR_FINISHED;
819}
820
822{
823 ot->name = "Select Ends";
824 ot->idname = "GREASE_PENCIL_OT_select_ends";
825 ot->description = "Select end points of strokes";
826
827 ot->exec = select_ends_exec;
829
831
832 RNA_def_int(ot->srna,
833 "amount_start",
834 0,
835 0,
836 INT32_MAX,
837 "Amount Start",
838 "Number of points to select from the start",
839 0,
840 INT32_MAX);
841 RNA_def_int(ot->srna,
842 "amount_end",
843 1,
844 0,
845 INT32_MAX,
846 "Amount End",
847 "Number of points to select from the end",
848 0,
849 INT32_MAX);
850}
851
853{
854 bool changed = false;
855
856 /* Convert all drawings of the active GP to the new selection domain. */
858 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
859 Span<GreasePencilDrawingBase *> drawings = grease_pencil.drawings();
860
861 for (const int index : drawings.index_range()) {
862 GreasePencilDrawingBase *drawing_base = drawings[index];
863 if (drawing_base->type != GP_DRAWING) {
864 continue;
865 }
866
867 GreasePencilDrawing *drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base);
868 bke::CurvesGeometry &curves = drawing->wrap().strokes_for_write();
869 if (curves.is_empty()) {
870 continue;
871 }
872
873 /* Skip curve when the selection domain already matches, or when there is no selection
874 * at all. */
875 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
876 const std::optional<bke::AttributeMetaData> meta_data = attributes.lookup_meta_data(
877 ".selection");
878 if ((!meta_data) || (meta_data->domain == domain)) {
879 continue;
880 }
881
882 /* When the new selection domain is 'curve', ensure all curves with a point selection
883 * are selected. */
884 if (domain == bke::AttrDomain::Curve) {
886 }
887
888 /* Convert selection domain. */
889 const GVArray src = *attributes.lookup(".selection", domain);
890 if (src) {
891 const CPPType &type = src.type();
892 void *dst = MEM_malloc_arrayN(attributes.domain_size(domain), type.size, __func__);
893 src.materialize(dst);
894
895 attributes.remove(".selection");
896 if (!attributes.add(".selection",
897 domain,
900 {
901 MEM_freeN(dst);
902 }
903
904 changed = true;
905
906 /* TODO: expand point selection to segments when in 'segment' mode. */
907 }
908 }
909
910 return changed;
911}
912
914{
915 using namespace blender::bke::greasepencil;
916
917 /* Set new selection mode. */
918 const int mode_new = RNA_enum_get(op->ptr, "mode");
921
922 bool changed = false;
924 changed = (mode_new != ts->gpencil_selectmode_edit);
925 ts->gpencil_selectmode_edit = mode_new;
926 }
928 changed = (mode_new != ts->gpencil_selectmode_sculpt);
929 ts->gpencil_selectmode_sculpt = mode_new;
930 }
932 changed = (mode_new != ts->gpencil_selectmode_vertex);
933 ts->gpencil_selectmode_vertex = mode_new;
934 }
935
936 changed |= ensure_selection_domain(ts, ob);
937
938 if (changed) {
939 /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
940 * attribute for now. */
941 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(ob->data);
942 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
943 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
944
946 }
947
948 return OPERATOR_FINISHED;
949}
950
952{
953 PropertyRNA *prop;
954
955 ot->name = "Select Mode";
956 ot->idname = __func__;
957 ot->description = "Change the selection mode for Grease Pencil strokes";
958
959 ot->exec = select_set_mode_exec;
961
963
964 ot->prop = prop = RNA_def_enum(
965 ot->srna, "mode", rna_enum_grease_pencil_selectmode_items, 0, "Mode", "");
967}
968
970{
971 const Scene *scene = CTX_data_scene(C);
974 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
975 const bool select = !RNA_boolean_get(op->ptr, "deselect");
976 const int material_index = object->actcol - 1;
978
979 if (material_index == -1) {
980 return OPERATOR_CANCELLED;
981 }
982
983 const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
984 threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
986
987 IndexMaskMemory memory;
989 *object, info.drawing, material_index, memory);
990 if (strokes.is_empty()) {
991 return;
992 }
993
994 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
995 const Span<StringRef> selection_attribute_names =
997
998 for (const int i : selection_attribute_names.index_range()) {
1000 curves, domain, CD_PROP_BOOL, selection_attribute_names[i]);
1001 switch (domain) {
1003 index_mask::masked_fill(selection.span.typed<bool>(), select, strokes);
1004 break;
1005 }
1007 strokes.foreach_index([&](const int curve_index) {
1008 const IndexRange points = points_by_curve[curve_index];
1009 ed::curves::fill_selection(selection.span.slice(points), select);
1010 });
1011 break;
1012 }
1013 default:
1015 }
1016 selection.finish();
1017 }
1018 });
1019
1020 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
1021 WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_EDITED, &grease_pencil);
1022
1023 return OPERATOR_FINISHED;
1024}
1025
1027{
1028 /* identifiers */
1029 ot->name = "Select Material";
1030 ot->idname = "GREASE_PENCIL_OT_material_select";
1031 ot->description = "Select/Deselect all Grease Pencil strokes using current material";
1032
1033 /* callbacks. */
1036
1037 /* flags. */
1038 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1039
1040 /* props */
1041 ot->prop = RNA_def_boolean(ot->srna, "deselect", false, "Deselect", "Unselect strokes");
1043}
1044
1045} // namespace blender::ed::greasepencil
1046
1060
1062 const ToolSettings *tool_settings)
1063{
1064 const int selectmode = tool_settings->gpencil_selectmode_sculpt;
1067 }
1068 if (selectmode & (GP_SCULPT_MASK_SELECTMODE_STROKE)) {
1070 }
1072}
1073
1075 const ToolSettings *tool_settings)
1076{
1077 const int selectmode = tool_settings->gpencil_selectmode_vertex;
1080 }
1081 if (selectmode & (GP_VERTEX_MASK_SELECTMODE_STROKE)) {
1083 }
1085}
1086
1088 const Object *object)
1089{
1090 if (object->mode & OB_MODE_EDIT) {
1091 return ED_grease_pencil_edit_selection_domain_get(tool_settings);
1092 }
1095 }
1098 }
1100}
1101
1103{
1104 return tool_settings->gpencil_selectmode_edit == GP_SELECTMODE_SEGMENT;
1105}
1106
1111
1116
1118 const Object *object)
1119{
1120 if (object->mode & OB_MODE_EDIT) {
1122 }
1125 }
1128 }
1129 return false;
1130}
1131
Depsgraph * CTX_data_depsgraph_pointer(const bContext *C)
Object * CTX_data_active_object(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
ToolSettings * CTX_data_tool_settings(const bContext *C)
Low-level operations for curves.
Low-level operations for grease pencil.
General operations, lookup, etc. for blender objects.
bool BKE_object_is_mode_compat(const Object *ob, eObjectMode object_mode)
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_SCOPED_DEFER(function_to_defer)
#define ELEM(...)
void DEG_id_tag_update(ID *id, unsigned int flags)
T * DEG_get_evaluated(const Depsgraph *depsgraph, T *id)
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:982
@ OB_MODE_VERTEX_GREASE_PENCIL
@ OB_MODE_EDIT
@ OB_MODE_SCULPT_GREASE_PENCIL
Object is a sort of wrapper for general info.
@ GP_SELECTMODE_POINT
@ GP_SELECTMODE_SEGMENT
@ GP_SELECTMODE_STROKE
@ GP_SCULPT_MASK_SELECTMODE_POINT
@ GP_SCULPT_MASK_SELECTMODE_STROKE
@ GP_SCULPT_MASK_SELECTMODE_SEGMENT
@ GP_VERTEX_MASK_SELECTMODE_SEGMENT
@ GP_VERTEX_MASK_SELECTMODE_STROKE
@ GP_VERTEX_MASK_SELECTMODE_POINT
@ OPERATOR_CANCELLED
@ OPERATOR_FINISHED
eSelectOp
@ SEL_OP_ADD
@ SEL_OP_SUB
@ SEL_OP_SET
@ SEL_OP_AND
@ SEL_SELECT
@ SEL_DESELECT
@ SEL_TOGGLE
ViewContext ED_view3d_viewcontext_init(bContext *C, Depsgraph *depsgraph)
@ PROP_SKIP_SAVE
Definition RNA_types.hh:330
@ PROP_HIDDEN
Definition RNA_types.hh:324
#define C
Definition RandGen.cpp:29
#define NC_GEOM
Definition WM_types.hh:390
#define ND_DATA
Definition WM_types.hh:506
@ OPTYPE_UNDO
Definition WM_types.hh:182
@ OPTYPE_REGISTER
Definition WM_types.hh:180
#define NA_EDITED
Definition WM_types.hh:581
#define ND_SPACE_VIEW3D
Definition WM_types.hh:525
#define NC_SPACE
Definition WM_types.hh:389
long long int int64_t
static unsigned long seed
Definition btSoftBody.h:39
static const CPPType & get()
void default_construct(void *ptr) const
GMutableSpan slice(const int64_t start, int64_t size) const
MutableSpan< T > typed() const
void materialize(void *dst) const
constexpr int64_t first() const
constexpr int64_t last(const int64_t n=0) const
constexpr int64_t size() const
constexpr bool is_empty() const
static constexpr IndexRange from_begin_end(const int64_t begin, const int64_t end)
static constexpr IndexRange from_begin_end_inclusive(const int64_t begin, const int64_t last)
bool contains(const Key &key) const
Definition BLI_set.hh:310
bool add(const Key &key)
Definition BLI_set.hh:248
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
GAttributeReader lookup(const StringRef attribute_id) const
GAttributeReader lookup_or_default(StringRef attribute_id, AttrDomain domain, eCustomDataType data_type, const void *default_value=nullptr) const
int domain_size(const AttrDomain domain) const
std::optional< AttributeMetaData > lookup_meta_data(StringRef attribute_id) const
bool remove(const StringRef attribute_id)
bool add(const StringRef attribute_id, const AttrDomain domain, const eCustomDataType data_type, const AttributeInit &initializer)
bke::CurvesGeometry & strokes_for_write()
const bke::CurvesGeometry & strokes() const
IndexMask complement(const IndexMask &universe, IndexMaskMemory &memory) const
void to_bools(MutableSpan< bool > r_bools) const
void foreach_index(Fn &&fn) const
static wmOperatorStatus select_linked_exec(bContext *C, wmOperator *)
#define INT32_MAX
VecBase< float, 4 > float4
#define select(A, B, C)
blender::bke::AttrDomain ED_grease_pencil_sculpt_selection_domain_get(const ToolSettings *tool_settings)
bool ED_grease_pencil_sculpt_segment_selection_enabled(const ToolSettings *tool_settings)
blender::bke::AttrDomain ED_grease_pencil_edit_selection_domain_get(const ToolSettings *tool_settings)
bool ED_grease_pencil_edit_segment_selection_enabled(const ToolSettings *tool_settings)
blender::bke::AttrDomain ED_grease_pencil_vertex_selection_domain_get(const ToolSettings *tool_settings)
void ED_operatortypes_grease_pencil_select()
blender::bke::AttrDomain ED_grease_pencil_selection_domain_get(const ToolSettings *tool_settings, const Object *object)
bool ED_grease_pencil_segment_selection_enabled(const ToolSettings *tool_settings, const Object *object)
bool ED_grease_pencil_vertex_segment_selection_enabled(const ToolSettings *tool_settings)
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
static wmOperatorStatus select_all_exec(bContext *C, wmOperator *op)
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
#define T
eCustomDataType cpp_type_to_custom_data_type(const CPPType &type)
void select_linked(bke::CurvesGeometry &curves, const IndexMask &curves_mask)
void apply_selection_operation_at_index(GMutableSpan selection, const int index, const eSelectOp sel_op)
static bool has_anything_selected(const Span< Curves * > curves_ids)
void select_all(bke::CurvesGeometry &curves, const IndexMask &mask, const bke::AttrDomain selection_domain, int action)
void select_alternate(bke::CurvesGeometry &curves, const IndexMask &curves_mask, const bool deselect_ends)
IndexMask curve_mask_from_points(const bke::CurvesGeometry &curves, const IndexMask &point_mask, const GrainSize grain_size, IndexMaskMemory &memory)
void fill_selection_false(GMutableSpan selection)
IndexMask select_adjacent_mask(const bke::CurvesGeometry &curves, const IndexMask &curves_mask, const StringRef attribute_name, const bool deselect, IndexMaskMemory &memory)
Span< StringRef > get_curves_selection_attribute_names(const bke::CurvesGeometry &curves)
void fill_selection(GMutableSpan selection, bool value)
IndexMask end_points(const bke::CurvesGeometry &curves, const IndexMask &curves_mask, const int amount_start, const int amount_end, const bool inverted, IndexMaskMemory &memory)
bke::GSpanAttributeWriter ensure_selection_attribute(bke::CurvesGeometry &curves, bke::AttrDomain selection_domain, eCustomDataType create_type, StringRef attribute_name)
static void GREASE_PENCIL_OT_select_linked(wmOperatorType *ot)
static void select_similar_by_value(Scene *scene, Object *object, GreasePencil &grease_pencil, const bke::AttrDomain selection_domain, const StringRef attribute_id, float threshold, DistanceFn distance_fn)
static wmOperatorStatus select_ends_exec(bContext *C, wmOperator *op)
IndexMask retrieve_editable_points(Object &object, const bke::greasepencil::Drawing &drawing, int layer_index, IndexMaskMemory &memory)
FunctionRef< IndexMask(const ed::greasepencil::MutableDrawingInfo &info, const IndexMask &universe, StringRef attribute_name, IndexMaskMemory &memory)> SelectionUpdateFunc
static void GREASE_PENCIL_OT_select_more(wmOperatorType *ot)
static wmOperatorStatus select_set_mode_exec(bContext *C, wmOperator *op)
bool selection_update(const ViewContext *vc, const eSelectOp sel_op, SelectionUpdateFunc select_operation)
bool ensure_selection_domain(ToolSettings *ts, Object *object)
static wmOperatorStatus select_more_exec(bContext *C, wmOperator *)
bool editable_grease_pencil_point_selection_poll(bContext *C)
IndexMask retrieve_editable_elements(Object &object, const MutableDrawingInfo &info, const bke::AttrDomain selection_domain, IndexMaskMemory &memory)
void free_curves_2d_bvh_data(Curves2DBVHTree &data)
static const EnumPropertyItem select_similar_mode_items[]
CurveSegmentsData find_curve_segments(const bke::CurvesGeometry &curves, const IndexMask &curve_mask, const Span< float2 > screen_space_positions, const Curves2DBVHTree &tree_data, const IndexRange tree_data_range)
static wmOperatorStatus select_alternate_exec(bContext *C, wmOperator *op)
static void select_similar_by_layer(Scene *scene, Object *object, GreasePencil &grease_pencil, bke::AttrDomain domain)
IndexMask retrieve_editable_strokes_by_material(Object &object, const bke::greasepencil::Drawing &drawing, const int mat_i, IndexMaskMemory &memory)
static wmOperatorStatus select_all_exec(bContext *C, wmOperator *op)
static wmOperatorStatus select_random_exec(bContext *C, wmOperator *op)
int clamp_range(const IndexRange range, const int index)
bool editable_grease_pencil_poll(bContext *C)
static wmOperatorStatus select_similar_exec(bContext *C, wmOperator *op)
static int foreach_curve_segment(const CurveSegmentsData &segment_data, const int curve_index, const IndexRange points, Fn &&fn)
static void GREASE_PENCIL_OT_select_less(wmOperatorType *ot)
void insert_selected_values(const bke::CurvesGeometry &curves, const bke::AttrDomain domain, const StringRef attribute_id, blender::Set< T > &r_value_set)
bool apply_mask_as_selection(bke::CurvesGeometry &curves, const IndexMask &selection_mask, const bke::AttrDomain selection_domain, const StringRef attribute_name, const GrainSize grain_size, const eSelectOp sel_op)
static void GREASE_PENCIL_OT_select_all(wmOperatorType *ot)
static void GREASE_PENCIL_OT_select_alternate(wmOperatorType *ot)
static void GREASE_PENCIL_OT_select_ends(wmOperatorType *ot)
static void GREASE_PENCIL_OT_select_similar(wmOperatorType *ot)
IndexMask retrieve_editable_strokes(Object &object, const bke::greasepencil::Drawing &drawing, int layer_index, IndexMaskMemory &memory)
static wmOperatorStatus select_less_exec(bContext *C, wmOperator *)
Vector< MutableDrawingInfo > retrieve_editable_drawings(const Scene &scene, GreasePencil &grease_pencil)
Curves2DBVHTree build_curves_2d_bvh_from_visible(const ViewContext &vc, const Object &object, const GreasePencil &grease_pencil, Span< MutableDrawingInfo > drawings, const int frame_number)
static wmOperatorStatus select_linked_exec(bContext *C, wmOperator *)
static void GREASE_PENCIL_OT_select_random(wmOperatorType *ot)
static wmOperatorStatus grease_pencil_material_select_exec(bContext *C, wmOperator *op)
static void GREASE_PENCIL_OT_material_select(wmOperatorType *ot)
static void GREASE_PENCIL_OT_set_selection_mode(wmOperatorType *ot)
Array< Vector< MutableDrawingInfo > > retrieve_editable_drawings_grouped_per_frame(const Scene &scene, GreasePencil &grease_pencil)
bool apply_mask_as_segment_selection(bke::CurvesGeometry &curves, const IndexMask &point_selection_mask, const StringRef attribute_name, const Curves2DBVHTree &tree_data, const IndexRange tree_data_range, const GrainSize grain_size, const eSelectOp sel_op)
void masked_fill(MutableSpan< T > data, const T &value, const IndexMask &mask)
T distance(const T &a, const T &b)
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
uint64_t get_default_hash(const T &v, const Args &...args)
Definition BLI_hash.hh:233
ColorSceneLinear4f< eAlpha::Premultiplied > ColorGeometry4f
Definition BLI_color.hh:342
static wmOperatorStatus select_random_exec(bContext *C, wmOperator *op)
static wmOperatorStatus select_less_exec(bContext *C, wmOperator *)
static wmOperatorStatus select_more_exec(bContext *C, wmOperator *)
int RNA_int_get(PointerRNA *ptr, const char *name)
float RNA_float_get(PointerRNA *ptr, const char *name)
bool RNA_boolean_get(PointerRNA *ptr, const char *name)
int RNA_enum_get(PointerRNA *ptr, const char *name)
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)
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_flag(PropertyRNA *prop, PropertyFlag flag)
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_grease_pencil_selectmode_items[]
Definition rna_scene.cc:668
#define FLT_MAX
Definition stdcycles.h:14
Definition DNA_ID.h:404
struct ToolSettings * toolsettings
char gpencil_selectmode_vertex
char gpencil_selectmode_sculpt
Scene * scene
Definition ED_view3d.hh:73
bContext * C
Definition ED_view3d.hh:66
Object * obact
Definition ED_view3d.hh:75
Object * obedit
Definition ED_view3d.hh:76
Depsgraph * depsgraph
Definition ED_view3d.hh:72
struct PointerRNA * ptr
i
Definition text_draw.cc:230
void WM_main_add_notifier(uint type, void *reference)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
wmOperatorType * ot
Definition wm_files.cc:4225
int WM_operator_properties_select_random_seed_increment_get(wmOperator *op)
void WM_operator_properties_select_random(wmOperatorType *ot)
void WM_operator_properties_select_all(wmOperatorType *ot)
void WM_operatortype_append(void(*opfunc)(wmOperatorType *))
wmOperatorStatus WM_menu_invoke(bContext *C, wmOperator *op, const wmEvent *)