From 8fb12839f254a4331ed24eee84b6241bd751af87 Mon Sep 17 00:00:00 2001 From: YimingWu Date: Thu, 28 Nov 2024 17:00:05 +0100 Subject: [PATCH] Grease Pencil: Add all supported modes to simplify operator The stroke simplify operator was implemented at the beginning of GPv3 migration, which means it lacked a few other modes. This fix reused the same code as in the simplify modifier to provide more simplify modes back to the modifier so it can work just like GPv2. Resolves #130616. Pull Request: https://projects.blender.org/blender/blender/pulls/131076 --- .../bl_ui/properties_grease_pencil_common.py | 13 + scripts/startup/bl_ui/space_view3d.py | 2 +- .../editors/grease_pencil/CMakeLists.txt | 1 + .../intern/grease_pencil_edit.cc | 258 +++++++++++------- 4 files changed, 170 insertions(+), 104 deletions(-) diff --git a/scripts/startup/bl_ui/properties_grease_pencil_common.py b/scripts/startup/bl_ui/properties_grease_pencil_common.py index 95f3ffe6572..89faed1fd82 100644 --- a/scripts/startup/bl_ui/properties_grease_pencil_common.py +++ b/scripts/startup/bl_ui/properties_grease_pencil_common.py @@ -782,6 +782,17 @@ class GREASE_PENCIL_MT_draw_delete(Menu): ).type = 'ALL_FRAMES' +class GREASE_PENCIL_MT_stroke_simplify(Menu): + bl_label = "Simplify Stroke" + + def draw(self, context): + layout = self.layout + layout.operator("grease_pencil.stroke_simplify", text="Fixed").mode = 'FIXED' + layout.operator("grease_pencil.stroke_simplify", text="Adaptive").mode = 'ADAPTIVE' + layout.operator("grease_pencil.stroke_simplify", text="Sample").mode = 'SAMPLE' + layout.operator("grease_pencil.stroke_simplify", text="Merge").mode = 'MERGE' + + classes = ( GPENCIL_UL_annotation_layer, GPENCIL_UL_layer, @@ -795,6 +806,8 @@ classes = ( GREASE_PENCIL_MT_draw_delete, + GREASE_PENCIL_MT_stroke_simplify, + GreasePencilFlipTintColors, ) diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index 7f18d1b83c4..0ae0c9da80d 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -5716,7 +5716,7 @@ class VIEW3D_MT_edit_greasepencil_stroke(Menu): layout.operator("grease_pencil.stroke_subdivide", text="Subdivide") layout.operator("grease_pencil.stroke_subdivide_smooth", text="Subdivide and Smooth") - layout.operator("grease_pencil.stroke_simplify", text="Simplify") + layout.menu("GREASE_PENCIL_MT_stroke_simplify") layout.separator() diff --git a/source/blender/editors/grease_pencil/CMakeLists.txt b/source/blender/editors/grease_pencil/CMakeLists.txt index bee17981bb5..313a686ee25 100644 --- a/source/blender/editors/grease_pencil/CMakeLists.txt +++ b/source/blender/editors/grease_pencil/CMakeLists.txt @@ -15,6 +15,7 @@ set(INC ../../geometry ../sculpt_paint ../../modifiers/intern/lineart + ../../functions # RNA_prototypes.hh ${CMAKE_BINARY_DIR}/source/blender/makesrna ) 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 c6dc509e516..9a91cd26eae 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc @@ -65,7 +65,9 @@ #include "GEO_join_geometries.hh" #include "GEO_realize_instances.hh" #include "GEO_reorder.hh" +#include "GEO_resample_curves.hh" #include "GEO_set_curve_type.hh" +#include "GEO_simplify_curves.hh" #include "GEO_smooth_curves.hh" #include "GEO_subdivide_curves.hh" @@ -203,52 +205,53 @@ static void GREASE_PENCIL_OT_stroke_smooth(wmOperatorType *ot) /** \name Simplify Stroke Operator * \{ */ -static float dist_to_interpolated( - float3 pos, float3 posA, float3 posB, float val, float valA, float valB) +enum class SimplifyMode { + FIXED = 0, + ADAPTIVE = 1, + SAMPLE = 2, + MERGE = 3, +}; + +static const EnumPropertyItem prop_simplify_modes[] = { + {int(SimplifyMode::FIXED), + "FIXED", + 0, + "Fixed", + "Delete alternating vertices in the stroke, except extremes"}, + {int(SimplifyMode::ADAPTIVE), + "ADAPTIVE", + 0, + "Adaptive", + "Use a Ramer-Douglas-Peucker algorithm to simplify the stroke preserving main shape"}, + {int(SimplifyMode::SAMPLE), + "SAMPLE", + 0, + "Sample", + "Re-sample the stroke with segments of the specified length"}, + {int(SimplifyMode::MERGE), + "MERGE", + 0, + "Merge", + "Simplify the stroke by merging vertices closer than a given distance"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static IndexMask simplify_fixed(const bke::CurvesGeometry &curves, + const int step, + IndexMaskMemory &memory) { - float dist1 = math::distance_squared(posA, pos); - float dist2 = math::distance_squared(posB, pos); - - if (dist1 + dist2 > 0) { - float interpolated_val = interpf(valB, valA, dist1 / (dist1 + dist2)); - return math::distance(interpolated_val, val); - } - return 0.0f; -} - -static int64_t stroke_simplify(const IndexRange points, - const bool cyclic, - const float epsilon, - const FunctionRef dist_function, - MutableSpan points_to_delete) -{ - int64_t total_points_to_delete = 0; - const Span curve_selection = points_to_delete.slice(points); - if (!curve_selection.contains(true)) { - return total_points_to_delete; - } - - const bool is_last_segment_selected = (curve_selection.first() && curve_selection.last()); - - const Vector selection_ranges = array_utils::find_all_ranges(curve_selection, true); - threading::parallel_for( - selection_ranges.index_range(), 1024, [&](const IndexRange range_of_ranges) { - for (const IndexRange range : selection_ranges.as_span().slice(range_of_ranges)) { - total_points_to_delete += ramer_douglas_peucker_simplify( - range.shift(points.start()), epsilon, dist_function, points_to_delete); + const OffsetIndices points_by_curve = curves.points_by_curve(); + const Array point_to_curve_map = curves.point_to_curve_map(); + return IndexMask::from_predicate( + curves.points_range(), GrainSize(2048), memory, [&](const int64_t i) { + const int curve_i = point_to_curve_map[i]; + const IndexRange points = points_by_curve[curve_i]; + if (points.size() <= 2) { + return true; } + const int local_i = i - points.start(); + return (local_i % int(math::pow(2.0f, float(step))) == 0) || points.last() == i; }); - - /* For cyclic curves, simplify the last segment. */ - if (cyclic && points.size() > 2 && is_last_segment_selected) { - const float dist = dist_function(points.last(1), points.first(), points.last()); - if (dist <= epsilon) { - points_to_delete[points.last()] = true; - total_points_to_delete++; - } - } - - return total_points_to_delete; } static int grease_pencil_stroke_simplify_exec(bContext *C, wmOperator *op) @@ -257,7 +260,7 @@ static int grease_pencil_stroke_simplify_exec(bContext *C, wmOperator *op) Object *object = CTX_data_active_object(C); GreasePencil &grease_pencil = *static_cast(object->data); - const float epsilon = RNA_float_get(op->ptr, "factor"); + const SimplifyMode mode = SimplifyMode(RNA_enum_get(op->ptr, "mode")); bool changed = false; const Vector drawings = retrieve_editable_drawings(*scene, grease_pencil); @@ -274,65 +277,67 @@ static int grease_pencil_stroke_simplify_exec(bContext *C, wmOperator *op) return; } - const Span positions = curves.positions(); - const VArray radii = info.drawing.radii(); - - /* Distance functions for `ramer_douglas_peucker_simplify`. */ - const auto dist_function_positions = - [positions](int64_t first_index, int64_t last_index, int64_t index) { - const float dist_position = dist_to_line_v3( - positions[index], positions[first_index], positions[last_index]); - return dist_position; - }; - const auto dist_function_positions_and_radii = - [positions, radii](int64_t first_index, int64_t last_index, int64_t index) { - const float dist_position = dist_to_line_v3( - positions[index], positions[first_index], positions[last_index]); - const float dist_radii = dist_to_interpolated(positions[index], - positions[first_index], - positions[last_index], - radii[index], - radii[first_index], - radii[last_index]); - return math::max(dist_position, dist_radii); - }; - - const VArray cyclic = curves.cyclic(); - const OffsetIndices points_by_curve = curves.points_by_curve(); - const VArray selection = *curves.attributes().lookup_or_default( - ".selection", bke::AttrDomain::Point, true); - - /* Mark all points in the editable curves to be deleted. */ - Array points_to_delete(curves.points_num(), false); - bke::curves::fill_points(points_by_curve, strokes, true, points_to_delete.as_mutable_span()); - - std::atomic total_points_to_delete = 0; - if (radii.is_single()) { - strokes.foreach_index([&](const int64_t curve_i) { - const IndexRange points = points_by_curve[curve_i]; - total_points_to_delete += stroke_simplify(points, - cyclic[curve_i], - epsilon, - dist_function_positions, - points_to_delete.as_mutable_span()); - }); - } - else if (radii.is_span()) { - strokes.foreach_index([&](const int64_t curve_i) { - const IndexRange points = points_by_curve[curve_i]; - total_points_to_delete += stroke_simplify(points, - cyclic[curve_i], - epsilon, - dist_function_positions_and_radii, - points_to_delete.as_mutable_span()); - }); - } - - if (total_points_to_delete > 0) { - IndexMaskMemory memory; - curves.remove_points(IndexMask::from_bools(points_to_delete, memory), {}); - info.drawing.tag_topology_changed(); - changed = true; + switch (mode) { + case SimplifyMode::FIXED: { + const int steps = RNA_int_get(op->ptr, "steps"); + const IndexMask points_to_keep = simplify_fixed(curves, steps, memory); + if (points_to_keep.is_empty()) { + info.drawing.strokes_for_write() = {}; + break; + } + if (points_to_keep.size() == curves.points_num()) { + break; + } + info.drawing.strokes_for_write() = bke::curves_copy_point_selection( + curves, points_to_keep, {}); + info.drawing.tag_topology_changed(); + changed = true; + break; + } + case SimplifyMode::ADAPTIVE: { + const float simplify_factor = RNA_float_get(op->ptr, "factor"); + const IndexMask points_to_delete = geometry::simplify_curve_attribute( + curves.positions(), + strokes, + curves.points_by_curve(), + curves.cyclic(), + simplify_factor, + curves.positions(), + memory); + info.drawing.strokes_for_write().remove_points(points_to_delete, {}); + info.drawing.tag_topology_changed(); + changed = true; + break; + } + case SimplifyMode::SAMPLE: { + const float resample_length = RNA_float_get(op->ptr, "length"); + info.drawing.strokes_for_write() = geometry::resample_to_length( + curves, strokes, VArray::ForSingle(resample_length, curves.curves_num()), {}); + info.drawing.tag_topology_changed(); + changed = true; + break; + } + case SimplifyMode::MERGE: { + const OffsetIndices points_by_curve = curves.points_by_curve(); + const Array point_to_curve_map = curves.point_to_curve_map(); + const float merge_distance = RNA_float_get(op->ptr, "distance"); + const IndexMask points = IndexMask::from_predicate( + curves.points_range(), GrainSize(2048), memory, [&](const int64_t i) { + const int curve_i = point_to_curve_map[i]; + const IndexRange points = points_by_curve[curve_i]; + if (points.drop_front(1).drop_back(1).contains(i)) { + return true; + } + return false; + }); + info.drawing.strokes_for_write() = ed::greasepencil::curves_merge_by_distance( + curves, merge_distance, points, {}); + info.drawing.tag_topology_changed(); + changed = true; + break; + } + default: + break; } }); @@ -343,6 +348,38 @@ static int grease_pencil_stroke_simplify_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +static void grease_pencil_simplify_ui(bContext *C, wmOperator *op) +{ + uiLayout *layout = op->layout; + wmWindowManager *wm = CTX_wm_manager(C); + + PointerRNA ptr = RNA_pointer_create(&wm->id, op->type->srna, op->properties); + + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + + uiItemR(layout, &ptr, "mode", UI_ITEM_NONE, nullptr, ICON_NONE); + + const SimplifyMode mode = SimplifyMode(RNA_enum_get(op->ptr, "mode")); + + switch (mode) { + case SimplifyMode::FIXED: + uiItemR(layout, &ptr, "steps", UI_ITEM_NONE, nullptr, ICON_NONE); + break; + case SimplifyMode::ADAPTIVE: + uiItemR(layout, &ptr, "factor", UI_ITEM_NONE, nullptr, ICON_NONE); + break; + case SimplifyMode::SAMPLE: + uiItemR(layout, &ptr, "length", UI_ITEM_NONE, nullptr, ICON_NONE); + break; + case SimplifyMode::MERGE: + uiItemR(layout, &ptr, "distance", UI_ITEM_NONE, nullptr, ICON_NONE); + break; + default: + break; + } +} + static void GREASE_PENCIL_OT_stroke_simplify(wmOperatorType *ot) { PropertyRNA *prop; @@ -356,8 +393,23 @@ static void GREASE_PENCIL_OT_stroke_simplify(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->ui = grease_pencil_simplify_ui; + prop = RNA_def_float(ot->srna, "factor", 0.01f, 0.0f, 100.0f, "Factor", "", 0.0f, 100.0f); RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_float(ot->srna, "length", 0.05f, 0.01f, 100.0f, "Length", "", 0.01f, 1.0f); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_float(ot->srna, "distance", 0.01f, 0.0f, 100.0f, "Distance", "", 0.0f, 1.0f); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_int(ot->srna, "steps", 1, 0, 50, "Steps", "", 0.0f, 10); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_enum(ot->srna, + "mode", + prop_simplify_modes, + 0, + "Mode", + "Method used for simplifying stroke points"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); } /** \} */