GPv3: Delete operator #111571
|
@ -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()
|
||||
|
|
|
@ -568,6 +568,150 @@ static void GREASE_PENCIL_OT_stroke_simplify(wmOperatorType *ot)
|
|||
|
||||
/** \} */
|
||||
casey-bianco-davis marked this conversation as resolved
Outdated
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Delete Operator
|
||||
* \{ */
|
||||
casey-bianco-davis marked this conversation as resolved
Falk David
commented
Although the selection is usually a span, this call can still fail. This should use 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
Falk David
commented
I think we should name this something like I think we should name this something like `remove_points_and_split`.
Pratik Borhade
commented
I guess it would be better to move this function in I guess it would be better to move this function in `curves_geometry.cc`
Falk/Hans ^
Falk David
commented
Maybe at some point, but since 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
Falk David
commented
Since it looks like we're only using 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
Falk David
commented
Usually we like to name the iterator 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
Falk David
commented
There is a new API that we can use here: 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
Falk David
commented
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
Falk David
commented
With the 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
Falk David
commented
In 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).
Falk David
commented
This should use 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
Falk David
commented
`"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);
|
||||
|
|
Should be
const VArray<bool> &selection
. Avoids some smallish overhead when copying theVArray
.