diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index b85d5ce54df..d29be350325 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -4562,6 +4562,12 @@ def km_grease_pencil_edit(params): ("grease_pencil.select_linked", {"type": 'L', "value": 'PRESS', "ctrl": True}, None), ("grease_pencil.select_more", {"type": 'NUMPAD_PLUS', "value": 'PRESS', "ctrl": True, "repeat": True}, None), ("grease_pencil.select_less", {"type": 'NUMPAD_MINUS', "value": 'PRESS', "ctrl": True, "repeat": True}, None), + # Delete menu + op_menu("VIEW3D_MT_edit_greasepencil_delete", {"type": 'X', "value": 'PRESS'}), + op_menu("VIEW3D_MT_edit_greasepencil_delete", {"type": 'DEL', "value": 'PRESS'}), + # Dissolve + ("grease_pencil.dissolve", {"type": 'X', "value": 'PRESS', "ctrl": True}, None), + ("grease_pencil.dissolve", {"type": 'DEL', "value": 'PRESS', "ctrl": True}, None), ]) return keymap diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index 10d3bf2127a..275a51a3d8a 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -4806,6 +4806,15 @@ class VIEW3D_MT_edit_gpencil_delete(Menu): layout.operator("gpencil.delete", text="Delete Active Keyframe (Active Layer)").type = 'FRAME' layout.operator("gpencil.active_frames_delete_all", text="Delete Active Keyframes (All Layers)") + +class VIEW3D_MT_edit_greasepencil_delete(Menu): + bl_label = "Delete" + + def draw(self, _context): + layout = self.layout + + layout.operator_enum("grease_pencil.dissolve", "type") + # Edit Curve # draw_curve is used by VIEW3D_MT_edit_curve and VIEW3D_MT_edit_surface @@ -5572,7 +5581,8 @@ class VIEW3D_MT_edit_greasepencil(Menu): bl_label = "Grease Pencil" def draw(self, _context): - pass + layout = self.layout + layout.menu("VIEW3D_MT_edit_greasepencil_delete") class VIEW3D_MT_edit_greasepencil_stroke(Menu): @@ -8476,6 +8486,7 @@ classes = ( VIEW3D_MT_gpencil_autoweights, VIEW3D_MT_gpencil_edit_context_menu, VIEW3D_MT_edit_greasepencil, + VIEW3D_MT_edit_greasepencil_delete, VIEW3D_MT_edit_greasepencil_stroke, VIEW3D_MT_edit_curve, VIEW3D_MT_edit_curve_ctrlpoints, 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 bad4104bb7f..e4b78046c1c 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc @@ -558,6 +558,160 @@ static void GREASE_PENCIL_OT_stroke_simplify(wmOperatorType *ot) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Dissolve Points Operator + * \{ */ + +enum class DissolveMode : int8_t { + /* Dissolve all selected points. */ + POINTS, + /* Dissolve between selected points. */ + BETWEEN, + /* Dissolve unselected points. */ + UNSELECT, +}; + +static const EnumPropertyItem prop_dissolve_types[] = { + {int(DissolveMode::POINTS), "POINTS", 0, "Dissolve", "Dissolve selected points"}, + {int(DissolveMode::BETWEEN), + "BETWEEN", + 0, + "Dissolve Between", + "Dissolve points between selected points"}, + {int(DissolveMode::UNSELECT), + "UNSELECT", + 0, + "Dissolve Unselect", + "Dissolve all unselected points"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static Array get_points_to_dissolve(bke::CurvesGeometry &curves, const DissolveMode mode) +{ + const VArray selection = *curves.attributes().lookup_or_default( + ".selection", ATTR_DOMAIN_POINT, true); + + Array points_to_dissolve(curves.points_num()); + selection.materialize(points_to_dissolve); + + if (mode == DissolveMode::POINTS) { + return points_to_dissolve; + } + + /* Both `between` and `unselect` have the unselected point being the ones dissolved so we need to + * invert. */ + BLI_assert(ELEM(mode, DissolveMode::BETWEEN, DissolveMode::UNSELECT)); + + const OffsetIndices points_by_curve = curves.points_by_curve(); + /* Because we are going to invert, these become the points to keep. */ + MutableSpan points_to_keep = points_to_dissolve.as_mutable_span(); + + threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) { + for (const int64_t curve_i : range) { + const IndexRange points = points_by_curve[curve_i]; + const Span curve_selection = points_to_dissolve.as_span().slice(points); + /* The unselected curves should not be dissolved. */ + if (!curve_selection.contains(true)) { + points_to_keep.slice(points).fill(true); + continue; + } + + /* `between` is just `unselect` but with the first and last segments not geting + * dissolved. */ + if (mode != DissolveMode::BETWEEN) { + continue; + } + + const Vector deselection_ranges = array_utils::find_all_ranges(curve_selection, + false); + + if (deselection_ranges.size() != 0) { + const IndexRange first_range = deselection_ranges.first().shift(points.first()); + const IndexRange last_range = deselection_ranges.last().shift(points.first()); + + /* Ranges should only be fill if the first/last point matchs the start/end point + * of the segment. */ + if (first_range.first() == points.first()) { + points_to_keep.slice(first_range).fill(true); + } + if (last_range.last() == points.last()) { + points_to_keep.slice(last_range).fill(true); + } + } + } + }); + + array_utils::invert_booleans(points_to_dissolve); + + return points_to_dissolve; +} + +static int grease_pencil_dissolve_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 DissolveMode mode = DissolveMode(RNA_enum_get(op->ptr, "type")); + + bool changed = false; + grease_pencil.foreach_editable_drawing( + scene->r.cfra, [&](int /*drawing_index*/, bke::greasepencil::Drawing &drawing) { + bke::CurvesGeometry &curves = drawing.strokes_for_write(); + if (curves.points_num() == 0) { + return; + } + + if (!ed::curves::has_anything_selected(curves)) { + return; + } + + const Array points_to_dissolve = get_points_to_dissolve(curves, mode); + + if (points_to_dissolve.as_span().contains(true)) { + IndexMaskMemory memory; + curves.remove_points(IndexMask::from_bools(points_to_dissolve, memory)); + 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_dissolve(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* Identifiers. */ + ot->name = "Dissolve"; + ot->idname = "GREASE_PENCIL_OT_dissolve"; + ot->description = "Delete selected points without splitting strokes"; + + /* Callbacks. */ + ot->invoke = WM_menu_invoke; + ot->exec = grease_pencil_dissolve_exec; + ot->poll = editable_grease_pencil_point_selection_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* Simplify parameters. */ + ot->prop = prop = RNA_def_enum(ot->srna, + "type", + prop_dissolve_types, + 0, + "Type", + "Method used for dissolving stroke points"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +/** \} */ + } // namespace blender::ed::greasepencil void ED_operatortypes_grease_pencil_edit() @@ -565,6 +719,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_dissolve); } void ED_keymap_grease_pencil(wmKeyConfig *keyconf)