From 4dfed520a3902c38434ceff6dffbac37e9bb4b2d Mon Sep 17 00:00:00 2001 From: casey bianco-davis Date: Fri, 17 Nov 2023 10:35:56 +0100 Subject: [PATCH] GPv3: Delete operator Adds the delete operator to edit mode grease pencil, and also adds it to the delete menu. Pull Request: https://projects.blender.org/blender/blender/pulls/111571 --- scripts/startup/bl_ui/space_view3d.py | 4 + .../intern/grease_pencil_edit.cc | 145 ++++++++++++++++++ 2 files changed, 149 insertions(+) diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index 8e7e63a871d..288c945658c 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -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() diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc index 9710321f581..e8bdac9b8a0 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc @@ -568,6 +568,150 @@ static void GREASE_PENCIL_OT_stroke_simplify(wmOperatorType *ot) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Delete Operator + * \{ */ + +static bke::CurvesGeometry remove_points_and_split(const bke::CurvesGeometry &curves, + const VArray &selection) +{ + const OffsetIndices points_by_curve = curves.points_by_curve(); + const VArray src_cyclic = curves.cyclic(); + const VArraySpan points_to_delete = VArraySpan(selection); + const int total_points = points_to_delete.count(false); + + /* Return if deleting everything. */ + if (total_points == 0) { + return {}; + } + + int curr_dst_point_id = 0; + Array dst_to_src_point(total_points); + Vector dst_curve_counts; + Vector dst_to_src_curve; + Vector dst_cyclic; + + for (const int curve_i : curves.curves_range()) { + const IndexRange points = points_by_curve[curve_i]; + const Span curve_points_to_delete = points_to_delete.slice(points); + const bool curve_cyclic = src_cyclic[curve_i]; + + /* Note, these ranges start at zero and needed to be shifted by `points.first()` */ + const Vector ranges_to_keep = array_utils::find_all_ranges(curve_points_to_delete, + false); + + if (ranges_to_keep.size() == 0) { + continue; + } + + 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 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); + + 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); + + 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(object->data); + + const eAttrDomain domain = ED_grease_pencil_selection_domain_get(scene->toolsettings); + + bool changed = false; + const Array 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; + curves.remove_curves(ed::curves::retrieve_selected_curves(curves, memory)); + } + else if (domain == ATTR_DOMAIN_POINT) { + const VArray selection = *curves.attributes().lookup_or_default( + ".selection", ATTR_DOMAIN_POINT, true); + curves = remove_points_and_split(curves, selection); + } + info.drawing.tag_topology_changed(); + changed = true; + }); + + 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_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 * \{ */ @@ -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);