37 bke::greasepencil::Drawing *from_drawing;
41enum class ActionOnNextRange { Nothing, ReverseExisting, ReverseAddition, ReverseBoth };
43enum class ActiveLayerBehavior { JoinStrokes, SplitAndCopy, SplitPoints };
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)
57 Vector<PointsRange> selected_ranges{};
58 r_total_points_selected = 0;
61 if (active_layer_behavior == ActiveLayerBehavior::JoinStrokes) {
63 object, info.drawing, info.layer_index, memory);
68 const OffsetIndices<int> points_by_curve = info.drawing.strokes().points_by_curve();
70 const IndexRange points = points_by_curve[curve_i];
71 selected_ranges.
append({&info.drawing, points});
72 r_total_points_selected += points.
size();
79 object, info.drawing, info.layer_index, memory);
83 r_total_points_selected += points_selection.
size();
85 const Vector<IndexRange> initial_ranges = points_selection.
to_ranges();
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});
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});
106 previous_curve = current_curve;
109 range = {range.
start(), range.
size() + 1};
113 selected_ranges.
append({&info.drawing, range});
117 return selected_ranges;
120template<
typename T>
void reverse_point_data(
const IndexRange point_range, MutableSpan<T>
data)
122 data.slice(point_range.
first(), point_range.
size()).reverse();
126void swap_handle_attributes(MutableSpan<T> handles_left, MutableSpan<T> handles_right)
130 for (const int point : range) {
131 std::swap(handles_left[point], handles_right[point]);
140void reverse_points_of(bke::CurvesGeometry &dst_curves,
const IndexRange points_to_reverse)
142 bke::MutableAttributeAccessor attributes = dst_curves.attributes_for_write();
144 attributes.foreach_attribute([&](
const bke::AttributeIter &iter) {
152 bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(iter.name);
154 using T = decltype(dummy);
155 reverse_point_data<T>(points_to_reverse, attribute.span.typed<T>());
161 if (attributes.contains(
"handle_left") && attributes.contains(
"handle_right")) {
162 MutableSpan<float3> handles_left = dst_curves.handle_positions_left_for_write().slice(
164 MutableSpan<float3> handles_right = dst_curves.handle_positions_right_for_write().slice(
166 swap_handle_attributes<float3>(handles_left, handles_right);
168 if (attributes.contains(
".selection_handle_left") &&
169 attributes.contains(
".selection_handle_right"))
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();
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(
184 MutableSpan<int8_t> types_right = dst_curves.handle_types_right_for_write().slice(
186 swap_handle_attributes<int8_t>(types_left, types_right);
190void apply_action(ActionOnNextRange action,
191 const IndexRange working_range,
192 const IndexRange adding_range,
193 bke::CurvesGeometry &dst_curves)
208 case ActionOnNextRange::Nothing:
210 case ActionOnNextRange::ReverseExisting: {
211 reverse_points_of(dst_curves, working_range);
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);
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);
234int64_t compute_closest_range_to(PointsRange &range,
235 const Span<PointsRange> &ranges,
237 ActionOnNextRange &r_action)
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()];
244 return {range_begin, range_end};
247 const auto [cur_range_begin, cur_range_end] = get_range_begin_end(range);
250 int64_t ret_value = starting_from;
251 ActionOnNextRange action = ActionOnNextRange::Nothing;
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]);
258 if (dist < min_dist) {
259 action = ActionOnNextRange::Nothing;
265 if (dist < min_dist) {
266 action = ActionOnNextRange::ReverseExisting;
272 if (dist < min_dist) {
273 action = ActionOnNextRange::ReverseAddition;
279 if (dist < min_dist) {
280 action = ActionOnNextRange::ReverseBoth;
290void copy_range_to_dst(
const PointsRange &points_range,
291 int &dst_starting_point,
292 bke::CurvesGeometry &dst_curves)
294 Array<int> src_raw_offsets(2);
295 Array<int> dst_raw_offsets(2);
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;
301 dst_raw_offsets[0] = dst_starting_point;
302 dst_starting_point += selection_size;
303 dst_raw_offsets[1] = dst_starting_point;
305 OffsetIndices<int> src_offsets{src_raw_offsets};
306 OffsetIndices<int> dst_offsets{dst_raw_offsets};
315 dst_curves.attributes_for_write());
318PointsRange copy_point_attributes(MutableSpan<PointsRange> selected_ranges,
319 bke::CurvesGeometry &dst_curves,
320 bke::greasepencil::Drawing &dst_drawing)
334 const PointsRange &first_range = selected_ranges.
first();
335 PointsRange working_range = {&dst_drawing, {0, first_range.range.size()}};
337 int next_point_index = 0;
338 copy_range_to_dst(first_range, next_point_index, dst_curves);
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};
352 return working_range;
355void copy_curve_attributes(Span<PointsRange> ranges_selected,
356 bke::CurvesGeometry &dst_curves,
357 bke::greasepencil::Drawing &dst_drawing)
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;
376 return it != ranges_selected.
end() ? *it : ranges_selected.
first();
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()];
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};
392 IndexMask({first_selected_curve, 1}),
393 dst_curves.attributes_for_write());
394 dst_curves.cyclic_for_write().first() =
false;
401void clear_selection_attribute(Span<PointsRange> ranges_selected,
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))
410 selection.span.fill(
false);
413 if (bke::GSpanAttributeWriter selection = attributes.lookup_for_write_span(
".selection_left"))
418 if (bke::GSpanAttributeWriter selection = attributes.lookup_for_write_span(
".selection_right"))
426void remove_selected_points(Span<PointsRange> ranges_selected)
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);
438 for (
const RangesMap::Item &item : ranges_by_drawing.items()) {
439 bke::CurvesGeometry &dst_curves = item.key->strokes_for_write();
440 IndexMaskMemory memory;
442 dst_curves.remove_points(combined_mask, {});
443 item.key->tag_topology_changed();
447void append_strokes_from(bke::CurvesGeometry &&other, bke::CurvesGeometry &dst)
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();
454 dst.resize(initial_points_num + other_points_num, initial_curves_num + other_curves_num);
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};
459 OffsetIndices<int> other_point_offsets{other_raw_offsets};
460 OffsetIndices<int> dst_point_offsets{dst_raw_offsets};
469 dst.attributes_for_write());
471 other_raw_offsets = {0, other_curves_num};
472 dst_raw_offsets = {initial_curves_num, initial_curves_num + other_curves_num};
474 OffsetIndices<int> other_curve_offsets{other_raw_offsets};
475 OffsetIndices<int> dst_curve_offsets{dst_raw_offsets};
484 dst.attributes_for_write());
497 using namespace bke::greasepencil;
504 if (!grease_pencil.has_active_layer()) {
509 const ActiveLayerBehavior active_layer_behavior =
static_cast<ActiveLayerBehavior
>(
511 const Layer &active_layer = *grease_pencil.get_active_layer();
513 Drawing *dst_drawing = grease_pencil.get_editable_drawing_at(active_layer, scene->
r.
cfra);
514 if (dst_drawing ==
nullptr) {
518 IndexMaskMemory memory;
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) {
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);
539 clear_selection_attribute(ranges_selected, selection_domain);
541 Array<PointsRange> working_range_collection = {working_range};
542 clear_selection_attribute(working_range_collection, selection_domain);
545 if (
ELEM(active_layer_behavior,
546 ActiveLayerBehavior::SplitPoints,
547 ActiveLayerBehavior::JoinStrokes))
549 remove_selected_points(ranges_selected);
552 append_strokes_from(std::move(tmp_curves), dst_curves);
554 if (active_layer_behavior != ActiveLayerBehavior::JoinStrokes) {
567 dst_curves.update_curve_types();
568 dst_curves.tag_topology_changed();
577void GREASE_PENCIL_OT_join_selection(wmOperatorType *
ot)
579 static const EnumPropertyItem active_layer_behavior[] = {
580 {int(ActiveLayerBehavior::JoinStrokes),
584 "Join the selected strokes into one stroke"},
585 {int(ActiveLayerBehavior::SplitAndCopy),
589 "Copy the selected points to a new stroke"},
590 {int(ActiveLayerBehavior::SplitPoints),
594 "Split the selected point to a new stroke"},
595 {0,
nullptr, 0,
nullptr,
nullptr},
599 ot->
name =
"Join Selection";
600 ot->
idname =
"GREASE_PENCIL_OT_join_selection";
605 ot->
exec = grease_pencil_join_selection_exec;
613 active_layer_behavior,
614 int(ActiveLayerBehavior::JoinStrokes),
616 "Defines how the operator will behave on the selection in the active layer");
Object * CTX_data_active_object(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
Low-level operations for grease pencil.
void BKE_report(ReportList *reports, eReportType type, const char *message)
void DEG_id_tag_update(ID *id, unsigned int flags)
struct GreasePencil GreasePencil
struct wmOperator wmOperator
BMesh const char void * data
bke::CurvesGeometry & strokes_for_write()
void tag_topology_changed()
Vector< IndexRange > to_ranges() 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
constexpr T & first() const
constexpr IndexRange index_range() const
constexpr const T & first() const
constexpr int64_t size() const
constexpr const T * end() const
constexpr const T * begin() 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))
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)
struct ToolSettings * toolsettings
wmOperatorStatus(* exec)(bContext *C, wmOperator *op) ATTR_WARN_UNUSED_RESULT
bool(* poll)(bContext *C) ATTR_WARN_UNUSED_RESULT
wmOperatorStatus(* invoke)(bContext *C, wmOperator *op, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
struct ReportList * reports
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
void WM_operatortype_append(void(*opfunc)(wmOperatorType *))
wmOperatorStatus WM_menu_invoke(bContext *C, wmOperator *op, const wmEvent *)