GPv3: Delete operator #111571

Merged
Falk David merged 22 commits from casey-bianco-davis/blender:GPv3-delete into main 2023-11-17 10:36:05 +01:00
2 changed files with 149 additions and 0 deletions

View File

@ -5007,6 +5007,10 @@ class VIEW3D_MT_edit_greasepencil_delete(Menu):
def draw(self, _context):
layout = self.layout
layout.operator("grease_pencil.delete")
layout.separator()
layout.operator_enum("grease_pencil.dissolve", "type")
layout.separator()

View File

@ -568,6 +568,150 @@ static void GREASE_PENCIL_OT_stroke_simplify(wmOperatorType *ot)
/** \} */
casey-bianco-davis marked this conversation as resolved Outdated

Should be const VArray<bool> &selection. Avoids some smallish overhead when copying the VArray.

Should be `const VArray<bool> &selection`. Avoids some smallish overhead when copying the `VArray`.
/* -------------------------------------------------------------------- */
/** \name Delete Operator
* \{ */
casey-bianco-davis marked this conversation as resolved
Review

Although the selection is usually a span, this call can still fail. This should use VArraySpan<bool>.

Although the selection is usually a span, this call can still fail. This should use `VArraySpan<bool>`.
static bke::CurvesGeometry remove_points_and_split(const bke::CurvesGeometry &curves,
const VArray<bool> &selection)
{
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
const VArray<bool> src_cyclic = curves.cyclic();
const VArraySpan<bool> points_to_delete = VArraySpan(selection);
const int total_points = points_to_delete.count(false);
casey-bianco-davis marked this conversation as resolved Outdated

I think we should name this something like remove_points_and_split.

I think we should name this something like `remove_points_and_split`.

I guess it would be better to move this function in curves_geometry.cc
Falk/Hans ^

I guess it would be better to move this function in `curves_geometry.cc` Falk/Hans ^

Maybe at some point, but since Curves don't have this functionality at the moment, it's fine to be a static function here.

Maybe at some point, but since `Curves` don't have this functionality at the moment, it's fine to be a static function here.
/* Return if deleting everything. */
if (total_points == 0) {
return {};
}
int curr_dst_point_id = 0;
Array<int> dst_to_src_point(total_points);
Vector<int> dst_curve_counts;
Vector<int> dst_to_src_curve;
Vector<bool> dst_cyclic;
for (const int curve_i : curves.curves_range()) {
const IndexRange points = points_by_curve[curve_i];
const Span<bool> curve_points_to_delete = points_to_delete.slice(points);
const bool curve_cyclic = src_cyclic[curve_i];
casey-bianco-davis marked this conversation as resolved
Review

Since it looks like we're only using first_range and last_range once, we can write it like this: const bool is_last_segment_selected = curve_cyclic && ranges_to_keep.first().first() == 0 && ranges_to_keep.last().last() == points.size() - 1;

Since it looks like we're only using `first_range` and `last_range` once, we can write it like this: `const bool is_last_segment_selected = curve_cyclic && ranges_to_keep.first().first() == 0 && ranges_to_keep.last().last() == points.size() - 1;`
/* Note, these ranges start at zero and needed to be shifted by `points.first()` */
const Vector<IndexRange> ranges_to_keep = array_utils::find_all_ranges(curve_points_to_delete,
false);
if (ranges_to_keep.size() == 0) {
continue;
casey-bianco-davis marked this conversation as resolved
Review

Usually we like to name the iterator thing_i. Also we can skip over the first element using drop_front:
for (const int range_i : ranges_to_keep.index_range().drop_front(is_curve_self_joined)) {

Usually we like to name the iterator `thing_i`. Also we can skip over the first element using `drop_front`: `for (const int range_i : ranges_to_keep.index_range().drop_front(is_curve_self_joined)) {`
}
const bool is_last_segment_selected = curve_cyclic && ranges_to_keep.first().first() == 0 &&
ranges_to_keep.last().last() == points.size() - 1;
const bool is_curve_self_joined = is_last_segment_selected && ranges_to_keep.size() != 1;
const bool is_cyclic = ranges_to_keep.size() == 1 && is_last_segment_selected;
IndexRange range_ids = ranges_to_keep.index_range();
/* Skip the first range because it is joined to the end of the last range. */
for (const int range_i : ranges_to_keep.index_range().drop_front(is_curve_self_joined)) {
const IndexRange range = ranges_to_keep[range_i];
int count = range.size();
for (const int src_point : range.shift(points.first())) {
dst_to_src_point[curr_dst_point_id++] = src_point;
}
/* Join the first range to the end of the last range. */
if (is_curve_self_joined && range_i == range_ids.last()) {
const IndexRange first_range = ranges_to_keep[range_ids.first()];
for (const int src_point : first_range.shift(points.first())) {
dst_to_src_point[curr_dst_point_id++] = src_point;
}
count += first_range.size();
}
dst_curve_counts.append(count);
dst_to_src_curve.append(curve_i);
dst_cyclic.append(is_cyclic);
}
}
const int total_curves = dst_to_src_curve.size();
bke::CurvesGeometry dst_curves(total_points, total_curves);
MutableSpan<int> new_curve_offsets = dst_curves.offsets_for_write();
array_utils::copy(dst_curve_counts.as_span(), new_curve_offsets.drop_back(1));
offset_indices::accumulate_counts_to_offsets(new_curve_offsets);
casey-bianco-davis marked this conversation as resolved
Review

There is a new API that we can use here: bke::gather_attributes (the one with the Span<int> indices parameter).

There is a new API that we can use here: `bke::gather_attributes` (the one with the `Span<int> indices` parameter).
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
const bke::AttributeAccessor src_attributes = curves.attributes();
/* Transfer curve attributes. */
gather_attributes(
src_attributes, ATTR_DOMAIN_CURVE, {}, {"cyclic"}, dst_to_src_curve, dst_attributes);
array_utils::copy(dst_cyclic.as_span(), dst_curves.cyclic_for_write());
/* Transfer point attributes. */
gather_attributes(src_attributes, ATTR_DOMAIN_POINT, {}, {}, dst_to_src_point, dst_attributes);
casey-bianco-davis marked this conversation as resolved Outdated

Same as above.

Same as above.
dst_curves.remove_attributes_based_on_types();
return dst_curves;
}
static int grease_pencil_delete_exec(bContext *C, wmOperator * /*op*/)
{
using namespace blender;
const Scene *scene = CTX_data_scene(C);
Object *object = CTX_data_active_object(C);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
const eAttrDomain domain = ED_grease_pencil_selection_domain_get(scene->toolsettings);
bool changed = false;
const Array<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
bke::CurvesGeometry &curves = info.drawing.strokes_for_write();
if (!ed::curves::has_anything_selected(curves)) {
return;
}
if (domain == ATTR_DOMAIN_CURVE) {
IndexMaskMemory memory;
casey-bianco-davis marked this conversation as resolved Outdated

With the ed::curves::has_anything_selected(curves) check below, this check is not necessary.

With the `ed::curves::has_anything_selected(curves)` check below, this check is not necessary.
curves.remove_curves(ed::curves::retrieve_selected_curves(curves, memory));
}
else if (domain == ATTR_DOMAIN_POINT) {
const VArray<bool> selection = *curves.attributes().lookup_or_default<bool>(
".selection", ATTR_DOMAIN_POINT, true);
curves = remove_points_and_split(curves, selection);
}
info.drawing.tag_topology_changed();
changed = true;
});
if (changed) {
casey-bianco-davis marked this conversation as resolved Outdated

In main the foreach_editable_drawing drawing function changed. This now gives a layer_index instead of the drawing_index, so the parameter should be renamed (not that it matters for the logic of the code below).

In `main` the `foreach_editable_drawing` drawing function changed. This now gives a `layer_index` instead of the `drawing_index`, so the parameter should be renamed (not that it matters for the logic of the code below).

This should use ed::curves::retrieve_selected_curves in the case of ATTR_DOMAIN_CURVE. This also means the select_linked above is not necessary.

This should use `ed::curves::retrieve_selected_curves` in the case of `ATTR_DOMAIN_CURVE`. This also means the `select_linked` above is not necessary.
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_delete(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Delete";
ot->idname = "GREASE_PENCIL_OT_delete";
ot->description = "Delete selected strokes or points";
/* Callbacks. */
ot->invoke = WM_menu_invoke;
ot->exec = grease_pencil_delete_exec;
ot->poll = editable_grease_pencil_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Dissolve Points Operator
* \{ */
casey-bianco-davis marked this conversation as resolved Outdated

"Delete selected strokes or points"

`"Delete selected strokes or points"`
@ -1305,6 +1449,7 @@ void ED_operatortypes_grease_pencil_edit()
using namespace blender::ed::greasepencil;
WM_operatortype_append(GREASE_PENCIL_OT_stroke_smooth);
WM_operatortype_append(GREASE_PENCIL_OT_stroke_simplify);
WM_operatortype_append(GREASE_PENCIL_OT_delete);
WM_operatortype_append(GREASE_PENCIL_OT_dissolve);
WM_operatortype_append(GREASE_PENCIL_OT_delete_frame);
WM_operatortype_append(GREASE_PENCIL_OT_stroke_material_set);