GPv3: Add stroke_reorder operator

This implements the `stroke_arrange` operator for grease pencil v3.
This behaves the same as `GPENCIL_OT_stroke_arrange` but renamed to `stroke_reorder`.
It should be more clear what the operator does.

Note: This also adds new key binds.

Co-authored-by: Falk David <filedescriptor@noreply.localhost>
Pull Request: https://projects.blender.org/blender/blender/pulls/116682
This commit is contained in:
casey bianco-davis
2024-02-01 13:15:23 +01:00
committed by Falk David
parent 7042db684f
commit 1b6e3d46a9
5 changed files with 187 additions and 3 deletions

View File

@@ -4644,6 +4644,16 @@ def km_grease_pencil_edit_mode(params):
# Context menu
*_template_items_context_menu("VIEW3D_MT_greasepencil_edit_context_menu", params.context_menu_event),
# Reorder
("grease_pencil.reorder", {"type": 'UP_ARROW', "value": 'PRESS',
"ctrl": True, "shift": True}, {"properties": [("direction", "TOP")]}),
("grease_pencil.reorder", {"type": 'UP_ARROW', "value": 'PRESS',
"ctrl": True, "repeat": True}, {"properties": [("direction", "UP")]}),
("grease_pencil.reorder", {"type": 'DOWN_ARROW', "value": 'PRESS',
"ctrl": True, "repeat": True}, {"properties": [("direction", "DOWN")]}),
("grease_pencil.reorder", {"type": 'DOWN_ARROW', "value": 'PRESS',
"ctrl": True, "shift": True}, {"properties": [("direction", "BOTTOM")]}),
])
return keymap

View File

@@ -5846,6 +5846,8 @@ class VIEW3D_MT_edit_greasepencil_stroke(Menu):
layout.operator("grease_pencil.set_uniform_thickness")
layout.operator("grease_pencil.set_uniform_opacity")
layout.operator_menu_enum("grease_pencil.reorder", text="Reorder", property="direction")
class VIEW3D_MT_edit_greasepencil_point(Menu):
bl_label = "Point"

View File

@@ -35,6 +35,7 @@
#include "ED_curves.hh"
#include "ED_grease_pencil.hh"
#include "GEO_reorder.hh"
#include "GEO_smooth_curves.hh"
#include "GEO_subdivide_curves.hh"
@@ -1537,6 +1538,161 @@ static void GREASE_PENCIL_OT_stroke_subdivide(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reorder Operator
* \{ */
enum class ReorderDirection : int8_t {
/** Move the selected strokes to be drawn in front. */
TOP = 0,
/** Increase the draw order of the selected strokes. */
UP = 1,
/** Decrease the draw order of the selected strokes. */
DOWN = 2,
/** Move the selected strokes to be drawn behind. */
BOTTOM = 3,
};
static Array<int> get_reordered_indices(const IndexRange universe,
const IndexMask &selected,
const ReorderDirection direction)
{
Array<int> indices(universe.size());
if (ELEM(direction, ReorderDirection::UP, ReorderDirection::DOWN)) {
/* Initialize the indices. */
array_utils::fill_index_range<int>(indices);
}
if (ELEM(direction, ReorderDirection::TOP, ReorderDirection::BOTTOM)) {
/*
* Take the selected indices and move them to the start for `Bottom` or the end for `Top`
* And fill the reset with the unselected indices.
*
* Here's a diagram:
*
* Input
* 0 1 2 3 4 5 6 7 8 9
* ^ ^ ^
*
* Top
* |-----A-----| |-B-|
* 0 1 3 6 7 8 9 2 4 5
* ^ ^ ^
*
* Bottom
* |-A-| |-----B-----|
* 2 4 5 0 1 3 6 7 8 9
* ^ ^ ^
*/
IndexMaskMemory memory;
const IndexMask unselected = selected.complement(universe, memory);
const IndexMask &A = (direction == ReorderDirection::BOTTOM) ? selected : unselected;
const IndexMask &B = (direction == ReorderDirection::BOTTOM) ? unselected : selected;
A.to_indices(indices.as_mutable_span().take_front(A.size()));
B.to_indices(indices.as_mutable_span().take_back(B.size()));
}
else if (direction == ReorderDirection::DOWN) {
selected.foreach_index_optimized<int>([&](const int curve_i, const int pos) {
/* Check if the curve index is touching the beginning without any gaps. */
if (curve_i != pos) {
/* Move a index down by flipping it with the one below it. */
std::swap(indices[curve_i], indices[curve_i - 1]);
}
});
}
else if (direction == ReorderDirection::UP) {
Array<int> selected_indices(selected.size());
selected.to_indices(selected_indices.as_mutable_span());
/* Because each index is moving up we need to loop through the indices backwards,
* starting at the largest. */
for (const int i : selected_indices.index_range()) {
const int pos = selected_indices.index_range().last(i);
const int curve_i = selected_indices[pos];
/* Check if the curve index is touching the end without any gaps. */
if (curve_i != universe.last(i)) {
/* Move a index up by flipping it with the one above it. */
std::swap(indices[curve_i], indices[curve_i + 1]);
}
}
}
return indices;
}
static int grease_pencil_stroke_reorder_exec(bContext *C, wmOperator *op)
{
const Scene *scene = CTX_data_scene(C);
Object *object = CTX_data_active_object(C);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
const ReorderDirection direction = ReorderDirection(RNA_enum_get(op->ptr, "direction"));
std::atomic<bool> changed = false;
const Array<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
IndexMaskMemory memory;
const IndexMask strokes = ed::greasepencil::retrieve_editable_and_selected_strokes(
*object, info.drawing, memory);
if (strokes.is_empty()) {
return;
}
bke::CurvesGeometry &curves = info.drawing.strokes_for_write();
/* Return if everything is selected. */
if (strokes.size() == curves.curves_num()) {
return;
}
const Array<int> indices = get_reordered_indices(curves.curves_range(), strokes, direction);
curves = geometry::reorder_curves_geometry(curves, indices, {});
info.drawing.tag_topology_changed();
changed.store(true, std::memory_order_relaxed);
});
if (changed) {
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
}
return OPERATOR_FINISHED;
}
static void GREASE_PENCIL_OT_stroke_reorder(wmOperatorType *ot)
{
static const EnumPropertyItem prop_reorder_direction[] = {
{int(ReorderDirection::TOP), "TOP", 0, "Bring to Front", ""},
{int(ReorderDirection::UP), "UP", 0, "Bring Forward", ""},
RNA_ENUM_ITEM_SEPR,
{int(ReorderDirection::DOWN), "DOWN", 0, "Send Backward", ""},
{int(ReorderDirection::BOTTOM), "BOTTOM", 0, "Send to Back", ""},
{0, nullptr, 0, nullptr, nullptr},
};
/* Identifiers. */
ot->name = "Reorder";
ot->idname = "GREASE_PENCIL_OT_reorder";
ot->description = "Change the display order of the selected strokes";
/* Callbacks. */
ot->exec = grease_pencil_stroke_reorder_exec;
ot->poll = editable_grease_pencil_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* Simplify parameters. */
ot->prop = RNA_def_enum(
ot->srna, "direction", prop_reorder_direction, int(ReorderDirection::TOP), "Direction", "");
}
/** \} */
} // namespace blender::ed::greasepencil
void ED_operatortypes_grease_pencil_edit()
@@ -1558,4 +1714,5 @@ void ED_operatortypes_grease_pencil_edit()
WM_operatortype_append(GREASE_PENCIL_OT_set_material);
WM_operatortype_append(GREASE_PENCIL_OT_clean_loose);
WM_operatortype_append(GREASE_PENCIL_OT_stroke_subdivide);
WM_operatortype_append(GREASE_PENCIL_OT_stroke_reorder);
}

View File

@@ -25,6 +25,11 @@ PointCloud *reorder_points(const PointCloud &src_pointcloud,
Span<int> old_by_new_map,
const bke::AnonymousAttributePropagationInfo &propagation_info);
bke::CurvesGeometry reorder_curves_geometry(
const bke::CurvesGeometry &src_curves,
Span<int> old_by_new_map,
const bke::AnonymousAttributePropagationInfo &propagation_info);
Curves *reorder_curves(const Curves &src_curves,
Span<int> old_by_new_map,
const bke::AnonymousAttributePropagationInfo &propagation_info);

View File

@@ -261,15 +261,25 @@ PointCloud *reorder_points(const PointCloud &src_pointcloud,
return dst_pointcloud;
}
bke::CurvesGeometry reorder_curves_geometry(
const bke::CurvesGeometry &src_curves,
Span<int> old_by_new_map,
const bke::AnonymousAttributePropagationInfo &propagation_info)
{
bke::CurvesGeometry dst_curves = bke::CurvesGeometry(src_curves);
clean_unused_attributes(propagation_info, dst_curves.attributes_for_write());
reorder_curves_exec(src_curves, old_by_new_map, dst_curves);
return dst_curves;
}
Curves *reorder_curves(const Curves &src_curves,
Span<int> old_by_new_map,
const bke::AnonymousAttributePropagationInfo &propagation_info)
{
const bke::CurvesGeometry src_curve_geometry = src_curves.geometry.wrap();
Curves *dst_curves = BKE_curves_copy_for_eval(&src_curves);
bke::CurvesGeometry &dst_curve_geometry = dst_curves->geometry.wrap();
clean_unused_attributes(propagation_info, dst_curve_geometry.attributes_for_write());
reorder_curves_exec(src_curve_geometry, old_by_new_map, dst_curve_geometry);
dst_curves->geometry.wrap() = reorder_curves_geometry(
src_curve_geometry, old_by_new_map, propagation_info);
return dst_curves;
}