Blender V5.0
grease_pencil_join_selection.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_grease_pencil.hh"
12#include "BKE_report.hh"
13
14#include "DNA_scene_types.h"
15
16#include "DEG_depsgraph.hh"
17
18#include "ED_curves.hh"
19#include "ED_grease_pencil.hh"
20#include "RNA_access.hh"
21
22#include "WM_api.hh"
23
24#include "RNA_define.hh"
25
26#include <algorithm>
27
29
30namespace {
31
36struct PointsRange {
37 bke::greasepencil::Drawing *from_drawing;
38 IndexRange range;
39};
40
41enum class ActionOnNextRange { Nothing, ReverseExisting, ReverseAddition, ReverseBoth };
42
43enum class ActiveLayerBehavior { JoinStrokes, SplitAndCopy, SplitPoints };
44
51Vector<PointsRange> retrieve_selection_ranges(Object &object,
52 const Span<MutableDrawingInfo> drawings,
53 const ActiveLayerBehavior active_layer_behavior,
54 int64_t &r_total_points_selected,
55 IndexMaskMemory &memory)
56{
57 Vector<PointsRange> selected_ranges{};
58 r_total_points_selected = 0;
59
60 for (const MutableDrawingInfo &info : drawings) {
61 if (active_layer_behavior == ActiveLayerBehavior::JoinStrokes) {
62 IndexMask curves_selection = retrieve_editable_and_selected_strokes(
63 object, info.drawing, info.layer_index, memory);
64 if (curves_selection.is_empty()) {
65 continue;
66 }
67
68 const OffsetIndices<int> points_by_curve = info.drawing.strokes().points_by_curve();
69 curves_selection.foreach_index([&](const int curve_i) {
70 const IndexRange points = points_by_curve[curve_i];
71 selected_ranges.append({&info.drawing, points});
72 r_total_points_selected += points.size();
73 });
74
75 continue;
76 }
77
78 IndexMask points_selection = retrieve_editable_and_selected_points(
79 object, info.drawing, info.layer_index, memory);
80 if (points_selection.is_empty()) {
81 continue;
82 }
83 r_total_points_selected += points_selection.size();
84
85 const Vector<IndexRange> initial_ranges = points_selection.to_ranges();
86
92 const Array<int> points_map = info.drawing.strokes().point_to_curve_map();
93 for (const IndexRange initial_range : initial_ranges) {
94 if (points_map[initial_range.first()] == points_map[initial_range.last()]) {
95 selected_ranges.append({&info.drawing, initial_range});
96 continue;
97 }
98
99 IndexRange range = {initial_range.start(), 1};
100 int previous_curve = points_map[range.start()];
101 for (const int64_t index : initial_range.drop_front(1)) {
102 const int current_curve = points_map[index];
103 if (previous_curve != current_curve) {
104 selected_ranges.append({&info.drawing, range});
105 range = {index, 1};
106 previous_curve = current_curve;
107 }
108 else {
109 range = {range.start(), range.size() + 1};
110 }
111 }
112
113 selected_ranges.append({&info.drawing, range});
114 }
115 }
116
117 return selected_ranges;
118}
119
120template<typename T> void reverse_point_data(const IndexRange point_range, MutableSpan<T> data)
121{
122 data.slice(point_range.first(), point_range.size()).reverse();
123}
124
125template<typename T>
126void swap_handle_attributes(MutableSpan<T> handles_left, MutableSpan<T> handles_right)
127{
128 BLI_assert(handles_left.size() == handles_right.size());
129 threading::parallel_for(handles_left.index_range(), 8192, [&](const IndexRange range) {
130 for (const int point : range) {
131 std::swap(handles_left[point], handles_right[point]);
132 }
133 });
134};
135
140void reverse_points_of(bke::CurvesGeometry &dst_curves, const IndexRange points_to_reverse)
141{
142 bke::MutableAttributeAccessor attributes = dst_curves.attributes_for_write();
143
144 attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
145 if (iter.domain != bke::AttrDomain::Point) {
146 return;
147 }
148 if (iter.data_type == bke::AttrType::String) {
149 return;
150 }
151
152 bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(iter.name);
153 bke::attribute_math::convert_to_static_type(attribute.span.type(), [&](auto dummy) {
154 using T = decltype(dummy);
155 reverse_point_data<T>(points_to_reverse, attribute.span.typed<T>());
156 });
157 attribute.finish();
158 });
159
160 /* Also needs to swap left/right bezier handles if handle attributes exist. */
161 if (attributes.contains("handle_left") && attributes.contains("handle_right")) {
162 MutableSpan<float3> handles_left = dst_curves.handle_positions_left_for_write().slice(
163 points_to_reverse);
164 MutableSpan<float3> handles_right = dst_curves.handle_positions_right_for_write().slice(
165 points_to_reverse);
166 swap_handle_attributes<float3>(handles_left, handles_right);
167 }
168 if (attributes.contains(".selection_handle_left") &&
169 attributes.contains(".selection_handle_right"))
170 {
171 bke::SpanAttributeWriter<bool> writer_left = attributes.lookup_for_write_span<bool>(
172 ".selection_handle_left");
173 bke::SpanAttributeWriter<bool> writer_right = attributes.lookup_for_write_span<bool>(
174 ".selection_handle_right");
175 const MutableSpan<bool> selection_left = writer_left.span.slice(points_to_reverse);
176 const MutableSpan<bool> selection_right = writer_right.span.slice(points_to_reverse);
177 swap_handle_attributes<bool>(selection_left, selection_right);
178 writer_left.finish();
179 writer_right.finish();
180 }
181 if (attributes.contains("handle_type_left") && attributes.contains("handle_type_right")) {
182 MutableSpan<int8_t> types_left = dst_curves.handle_types_left_for_write().slice(
183 points_to_reverse);
184 MutableSpan<int8_t> types_right = dst_curves.handle_types_right_for_write().slice(
185 points_to_reverse);
186 swap_handle_attributes<int8_t>(types_left, types_right);
187 }
188}
189
190void apply_action(ActionOnNextRange action,
191 const IndexRange working_range,
192 const IndexRange adding_range,
193 bke::CurvesGeometry &dst_curves)
194{
206
207 switch (action) {
208 case ActionOnNextRange::Nothing:
209 return;
210 case ActionOnNextRange::ReverseExisting: {
211 reverse_points_of(dst_curves, working_range);
212 break;
213 }
214 case ActionOnNextRange::ReverseAddition: {
215 const IndexRange src_range_on_dst = {working_range.last() + 1, adding_range.size()};
216 reverse_points_of(dst_curves, src_range_on_dst);
217 break;
218 }
219 case ActionOnNextRange::ReverseBoth: {
220 apply_action(ActionOnNextRange::ReverseExisting, working_range, adding_range, dst_curves);
221 apply_action(ActionOnNextRange::ReverseAddition, working_range, adding_range, dst_curves);
222 break;
223 }
224 }
225}
226
234int64_t compute_closest_range_to(PointsRange &range,
235 const Span<PointsRange> &ranges,
236 int64_t starting_from,
237 ActionOnNextRange &r_action)
238{
239 auto get_range_begin_end = [](const PointsRange &points_range) -> std::pair<float3, float3> {
240 const Span<float3> current_range_positions = points_range.from_drawing->strokes().positions();
241 const float3 range_begin = current_range_positions[points_range.range.first()];
242 const float3 range_end = current_range_positions[points_range.range.last()];
243
244 return {range_begin, range_end};
245 };
246
247 const auto [cur_range_begin, cur_range_end] = get_range_begin_end(range);
248 float min_dist = FLT_MAX;
249
250 int64_t ret_value = starting_from;
251 ActionOnNextRange action = ActionOnNextRange::Nothing;
252
253 const int64_t iterations = ranges.size() - starting_from;
254 for (const int64_t i : IndexRange(starting_from, iterations)) {
255 const auto [range_begin, range_end] = get_range_begin_end(ranges[i]);
256
257 float dist = math::distance_squared(cur_range_end, range_begin);
258 if (dist < min_dist) {
259 action = ActionOnNextRange::Nothing;
260 ret_value = i;
261 min_dist = dist;
262 }
263
264 dist = math::distance_squared(cur_range_begin, range_begin);
265 if (dist < min_dist) {
266 action = ActionOnNextRange::ReverseExisting;
267 ret_value = i;
268 min_dist = dist;
269 }
270
271 dist = math::distance_squared(cur_range_end, range_end);
272 if (dist < min_dist) {
273 action = ActionOnNextRange::ReverseAddition;
274 ret_value = i;
275 min_dist = dist;
276 }
277
278 dist = math::distance_squared(cur_range_begin, range_end);
279 if (dist < min_dist) {
280 action = ActionOnNextRange::ReverseBoth;
281 ret_value = i;
282 min_dist = dist;
283 }
284 }
285
286 r_action = action;
287 return ret_value;
288}
289
290void copy_range_to_dst(const PointsRange &points_range,
291 int &dst_starting_point,
292 bke::CurvesGeometry &dst_curves)
293{
294 Array<int> src_raw_offsets(2);
295 Array<int> dst_raw_offsets(2);
296
297 const int64_t selection_size = points_range.range.size();
298 src_raw_offsets[0] = points_range.range.first();
299 src_raw_offsets[1] = points_range.range.last() + 1;
300
301 dst_raw_offsets[0] = dst_starting_point;
302 dst_starting_point += selection_size;
303 dst_raw_offsets[1] = dst_starting_point;
304
305 OffsetIndices<int> src_offsets{src_raw_offsets};
306 OffsetIndices<int> dst_offsets{dst_raw_offsets};
307
308 copy_attributes_group_to_group(points_range.from_drawing->strokes().attributes(),
310 {},
311 {},
312 src_offsets,
313 dst_offsets,
314 IndexMask{1},
315 dst_curves.attributes_for_write());
316}
317
318PointsRange copy_point_attributes(MutableSpan<PointsRange> selected_ranges,
319 bke::CurvesGeometry &dst_curves,
320 bke::greasepencil::Drawing &dst_drawing)
321{
322 /* The algorithm for joining the points goes as follows:
323 * 1. Pick the first range of the selected ranges of points, which will be the working range
324 * 2. Copy the attributes of this range to dst_curves
325 * 3. Lookup in the remaining ranges for the one closer to the working range
326 * 4. Copy its attributes
327 * 5. In order to minimize the length of the stroke connecting them, reverse their points as
328 * needed
329 * 6. Extend the working range with the new range
330 * 7. Remove the new range from the list of remaining ranges. Lookup for the next one and
331 * continue
332 */
333
334 const PointsRange &first_range = selected_ranges.first();
335 PointsRange working_range = {&dst_drawing, {0, first_range.range.size()}};
336
337 int next_point_index = 0;
338 copy_range_to_dst(first_range, next_point_index, dst_curves);
339
340 const int64_t ranges = selected_ranges.size() - 1;
341 for (const int64_t i : IndexRange(1, ranges)) {
342 ActionOnNextRange action;
343 const int64_t closest_range = compute_closest_range_to(
344 working_range, selected_ranges, i, action);
345 std::swap(selected_ranges[i], selected_ranges[closest_range]);
346 PointsRange &next_range = selected_ranges[i];
347 copy_range_to_dst(next_range, next_point_index, dst_curves);
348 apply_action(action, working_range.range, next_range.range, dst_curves);
349 working_range.range = {0, next_point_index};
350 }
351
352 return working_range;
353}
354
355void copy_curve_attributes(Span<PointsRange> ranges_selected,
356 bke::CurvesGeometry &dst_curves,
357 bke::greasepencil::Drawing &dst_drawing)
358{
359 /* The decision of which stroke use to copy the curve attributes is a bit arbitrary, since the
360 * original selection may embrace several strokes. The criteria is as follows:
361 * - If the selection contained points from the active layer, the first selected stroke from it
362 * is used.
363 * - Otherwise, the first selected stroke is used.
364 * Reasoning behind is that the user will probably want to keep similar curve parameters for
365 * all the strokes in a layer.
366 * Also, the "cyclic" attribute is deliberately set to false, since user
367 * probably wants to set it manually
368 */
369
370 auto src_range = [&]() -> const PointsRange & {
371 const auto *it = std::find_if(
372 ranges_selected.begin(), ranges_selected.end(), [dst_drawing](const PointsRange &range) {
373 return range.from_drawing == &dst_drawing;
374 });
375
376 return it != ranges_selected.end() ? *it : ranges_selected.first();
377 }();
378
379 const bke::CurvesGeometry &src_curves = src_range.from_drawing->strokes();
380 const Array<int> points_map = src_curves.point_to_curve_map();
381 const int first_selected_curve = points_map[src_range.range.first()];
382
383 const int final_curve_index = dst_curves.curves_num() - 1;
384 const Array<int> dst_curves_raw_offsets = {final_curve_index, dst_curves.curves_num()};
385 const OffsetIndices<int> dst_curve_offsets{dst_curves_raw_offsets};
386
387 gather_attributes_to_groups(src_curves.attributes(),
391 dst_curve_offsets,
392 IndexMask({first_selected_curve, 1}),
393 dst_curves.attributes_for_write());
394 dst_curves.cyclic_for_write().first() = false;
395}
396
401void clear_selection_attribute(Span<PointsRange> ranges_selected,
402 const bke::AttrDomain selection_domain)
403{
404 for (const PointsRange &range : ranges_selected) {
405 bke::CurvesGeometry &curves = range.from_drawing->strokes_for_write();
406 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
407 if (bke::SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
408 ".selection", selection_domain))
409 {
410 selection.span.fill(false);
411 selection.finish();
412 }
413 if (bke::GSpanAttributeWriter selection = attributes.lookup_for_write_span(".selection_left"))
414 {
415 ed::curves::fill_selection_false(selection.span);
416 selection.finish();
417 }
418 if (bke::GSpanAttributeWriter selection = attributes.lookup_for_write_span(".selection_right"))
419 {
420 ed::curves::fill_selection_false(selection.span);
421 selection.finish();
422 }
423 }
424}
425
426void remove_selected_points(Span<PointsRange> ranges_selected)
427{
428 /* Removing points from a drawing invalidates subsequent ranges for the same drawing.
429 * Combine all ranges for the same drawings first to prevent removing the wrong points. */
430 using RangesMap = Map<bke::greasepencil::Drawing *, Vector<IndexMask>>;
431 RangesMap ranges_by_drawing;
432 for (const PointsRange &points_range : ranges_selected) {
433 BLI_assert(points_range.from_drawing != nullptr);
434 Vector<IndexMask> &ranges = ranges_by_drawing.lookup_or_add(points_range.from_drawing, {});
435 ranges.append(points_range.range);
436 }
437
438 for (const RangesMap::Item &item : ranges_by_drawing.items()) {
439 bke::CurvesGeometry &dst_curves = item.key->strokes_for_write();
440 IndexMaskMemory memory;
441 const IndexMask combined_mask = IndexMask::from_union(item.value, memory);
442 dst_curves.remove_points(combined_mask, {});
443 item.key->tag_topology_changed();
444 }
445}
446
447void append_strokes_from(bke::CurvesGeometry &&other, bke::CurvesGeometry &dst)
448{
449 const int initial_points_num = dst.points_num();
450 const int initial_curves_num = dst.curves_num();
451 const int other_points_num = other.points_num();
452 const int other_curves_num = other.curves_num();
453
454 dst.resize(initial_points_num + other_points_num, initial_curves_num + other_curves_num);
455
456 Array<int> other_raw_offsets{0, other_points_num};
457 Array<int> dst_raw_offsets{initial_points_num, initial_points_num + other_points_num};
458
459 OffsetIndices<int> other_point_offsets{other_raw_offsets};
460 OffsetIndices<int> dst_point_offsets{dst_raw_offsets};
461
462 copy_attributes_group_to_group(other.attributes(),
465 {},
466 other_point_offsets,
467 dst_point_offsets,
468 IndexMask{1},
469 dst.attributes_for_write());
470
471 other_raw_offsets = {0, other_curves_num};
472 dst_raw_offsets = {initial_curves_num, initial_curves_num + other_curves_num};
473
474 OffsetIndices<int> other_curve_offsets{other_raw_offsets};
475 OffsetIndices<int> dst_curve_offsets{dst_raw_offsets};
476
477 copy_attributes_group_to_group(other.attributes(),
480 {},
481 other_curve_offsets,
482 dst_curve_offsets,
483 IndexMask{1},
484 dst.attributes_for_write());
485}
486
487/* -------------------------------------------------------------------- */
490
495wmOperatorStatus grease_pencil_join_selection_exec(bContext *C, wmOperator *op)
496{
497 using namespace bke::greasepencil;
498
499 const Scene *scene = CTX_data_scene(C);
502 scene->toolsettings, object);
503 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
504 if (!grease_pencil.has_active_layer()) {
505 BKE_report(op->reports, RPT_ERROR, "No active layer");
506 return OPERATOR_CANCELLED;
507 }
508
509 const ActiveLayerBehavior active_layer_behavior = static_cast<ActiveLayerBehavior>(
510 RNA_enum_get(op->ptr, "type"));
511 const Layer &active_layer = *grease_pencil.get_active_layer();
512
513 Drawing *dst_drawing = grease_pencil.get_editable_drawing_at(active_layer, scene->r.cfra);
514 if (dst_drawing == nullptr) {
515 return OPERATOR_CANCELLED;
516 }
517
518 IndexMaskMemory memory;
519 int64_t selected_points_count;
520 const Vector<MutableDrawingInfo> editable_drawings = retrieve_editable_drawings(*scene,
521 grease_pencil);
522 Vector<PointsRange> ranges_selected = retrieve_selection_ranges(
523 *object, editable_drawings, active_layer_behavior, selected_points_count, memory);
524 if (ranges_selected.size() <= 1) {
525 /* Nothing to join */
526 return OPERATOR_FINISHED;
527 }
528
529 /* Temporary geometry where to perform the logic
530 * Once it gets stable, it is appended all at once to the destination curves */
531 Drawing tmp_drawing;
532 tmp_drawing.strokes_for_write() = bke::CurvesGeometry(selected_points_count, 1);
533 bke::CurvesGeometry &tmp_curves = tmp_drawing.strokes_for_write();
534
535 const PointsRange working_range = copy_point_attributes(
536 ranges_selected, tmp_curves, tmp_drawing);
537 copy_curve_attributes(ranges_selected, tmp_curves, *dst_drawing);
538
539 clear_selection_attribute(ranges_selected, selection_domain);
540
541 Array<PointsRange> working_range_collection = {working_range};
542 clear_selection_attribute(working_range_collection, selection_domain);
543
544 bke::CurvesGeometry &dst_curves = dst_drawing->strokes_for_write();
545 if (ELEM(active_layer_behavior,
546 ActiveLayerBehavior::SplitPoints,
547 ActiveLayerBehavior::JoinStrokes))
548 {
549 remove_selected_points(ranges_selected);
550 }
551
552 append_strokes_from(std::move(tmp_curves), dst_curves);
553
554 if (active_layer_behavior != ActiveLayerBehavior::JoinStrokes) {
555 bke::GSpanAttributeWriter selection = ed::curves::ensure_selection_attribute(
556 dst_curves, selection_domain, bke::AttrType::Bool);
557
558 if (selection_domain == bke::AttrDomain::Curve) {
559 ed::curves::fill_selection_true(selection.span.take_back(tmp_curves.curves_num()));
560 }
561 else {
562 ed::curves::fill_selection_true(selection.span.take_back(tmp_curves.points_num()));
563 }
564 selection.finish();
565 }
566
567 dst_curves.update_curve_types();
568 dst_curves.tag_topology_changed();
569 dst_drawing->tag_topology_changed();
570
571 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
572 WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
573
574 return OPERATOR_FINISHED;
575}
576
577void GREASE_PENCIL_OT_join_selection(wmOperatorType *ot)
578{
579 static const EnumPropertyItem active_layer_behavior[] = {
580 {int(ActiveLayerBehavior::JoinStrokes),
581 "JOINSTROKES",
582 0,
583 "Join Strokes",
584 "Join the selected strokes into one stroke"},
585 {int(ActiveLayerBehavior::SplitAndCopy),
586 "SPLITCOPY",
587 0,
588 "Split and Copy",
589 "Copy the selected points to a new stroke"},
590 {int(ActiveLayerBehavior::SplitPoints),
591 "SPLIT",
592 0,
593 "Split",
594 "Split the selected point to a new stroke"},
595 {0, nullptr, 0, nullptr, nullptr},
596 };
597
598 /* identifiers. */
599 ot->name = "Join Selection";
600 ot->idname = "GREASE_PENCIL_OT_join_selection";
601 ot->description = "New stroke from selected points/strokes";
602
603 /* callbacks. */
605 ot->exec = grease_pencil_join_selection_exec;
607
609
611 ot->srna,
612 "type",
613 active_layer_behavior,
614 int(ActiveLayerBehavior::JoinStrokes),
615 "Type",
616 "Defines how the operator will behave on the selection in the active layer");
617}
618
619} // namespace
620
622
623} // namespace blender::ed::greasepencil
624
626{
627 using namespace blender::ed::greasepencil;
628
629 WM_operatortype_append(GREASE_PENCIL_OT_join_selection);
630}
Object * CTX_data_active_object(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
Low-level operations for grease pencil.
@ RPT_ERROR
Definition BKE_report.hh:39
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
#define BLI_assert(a)
Definition BLI_assert.h:46
#define ELEM(...)
void DEG_id_tag_update(ID *id, unsigned int flags)
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:1074
struct GreasePencil GreasePencil
struct Object Object
struct Scene Scene
@ OPERATOR_CANCELLED
@ OPERATOR_FINISHED
struct wmOperator wmOperator
#define C
Definition RandGen.cpp:29
#define NC_GEOM
Definition WM_types.hh:393
#define ND_DATA
Definition WM_types.hh:509
@ OPTYPE_UNDO
Definition WM_types.hh:182
@ OPTYPE_REGISTER
Definition WM_types.hh:180
BMesh const char void * data
long long int int64_t
bke::CurvesGeometry & strokes_for_write()
void tag_topology_changed()
Vector< IndexRange > to_ranges() const
int64_t size() const
bool is_empty() const
void foreach_index(Fn &&fn) const
constexpr int64_t first() const
constexpr int64_t last(const int64_t n=0) const
constexpr int64_t size() const
constexpr int64_t start() const
constexpr int64_t size() const
Definition BLI_span.hh:493
constexpr T & first() const
Definition BLI_span.hh:679
constexpr IndexRange index_range() const
Definition BLI_span.hh:670
constexpr const T & first() const
Definition BLI_span.hh:315
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr const T * end() const
Definition BLI_span.hh:224
constexpr const T * begin() const
Definition BLI_span.hh:220
int64_t size() const
void append(const T &value)
static IndexMask from_union(const IndexMask &mask_a, const IndexMask &mask_b, IndexMaskMemory &memory)
void ED_operatortypes_grease_pencil_join()
blender::bke::AttrDomain ED_grease_pencil_selection_domain_get(const ToolSettings *tool_settings, const Object *object)
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
auto attribute_filter_from_skip_ref(const Span< StringRef > skip)
void gather_attributes_to_groups(AttributeAccessor src_attributes, AttrDomain src_domain, AttrDomain dst_domain, const AttributeFilter &attribute_filter, OffsetIndices< int > dst_offsets, const IndexMask &src_selection, MutableAttributeAccessor dst_attributes)
void copy_attributes_group_to_group(AttributeAccessor src_attributes, AttrDomain src_domain, AttrDomain dst_domain, const AttributeFilter &attribute_filter, OffsetIndices< int > src_offsets, OffsetIndices< int > dst_offsets, const IndexMask &selection, MutableAttributeAccessor dst_attributes)
void fill_selection_false(GMutableSpan selection)
void fill_selection_true(GMutableSpan selection)
bke::GSpanAttributeWriter ensure_selection_attribute(bke::CurvesGeometry &curves, bke::AttrDomain selection_domain, bke::AttrType create_type, StringRef attribute_name)
IndexMask retrieve_editable_and_selected_strokes(Object &object, const bke::greasepencil::Drawing &drawing, int layer_index, IndexMaskMemory &memory)
bool editable_grease_pencil_poll(bContext *C)
IndexMask retrieve_editable_and_selected_points(Object &object, const bke::greasepencil::Drawing &drawing, int layer_index, IndexMaskMemory &memory)
Vector< MutableDrawingInfo > retrieve_editable_drawings(const Scene &scene, GreasePencil &grease_pencil)
T distance_squared(const VecBase< T, Size > &a, const VecBase< T, Size > &b)
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
VecBase< float, 3 > float3
int RNA_enum_get(PointerRNA *ptr, const char *name)
PropertyRNA * RNA_def_enum(StructOrFunctionRNA *cont_, const char *identifier, const EnumPropertyItem *items, const int default_value, const char *ui_name, const char *ui_description)
#define FLT_MAX
Definition stdcycles.h:14
struct ToolSettings * toolsettings
struct RenderData r
const char * name
Definition WM_types.hh:1033
wmOperatorStatus(* exec)(bContext *C, wmOperator *op) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1049
bool(* poll)(bContext *C) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1089
const char * idname
Definition WM_types.hh:1035
const char * description
Definition WM_types.hh:1039
wmOperatorStatus(* invoke)(bContext *C, wmOperator *op, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1065
PropertyRNA * prop
Definition WM_types.hh:1139
StructRNA * srna
Definition WM_types.hh:1127
struct ReportList * reports
struct PointerRNA * ptr
i
Definition text_draw.cc:230
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
wmOperatorType * ot
Definition wm_files.cc:4237
void WM_operatortype_append(void(*opfunc)(wmOperatorType *))
wmOperatorStatus WM_menu_invoke(bContext *C, wmOperator *op, const wmEvent *)