diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index f9e77cac29c..55fa564af12 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -5923,6 +5923,7 @@ class VIEW3D_MT_edit_curves_segments(Menu): def draw(self, _context): layout = self.layout + layout.operator("curves.subdivide") layout.operator("curves.switch_direction") diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index ff6465038c6..60d1d5bdf02 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -65,6 +65,7 @@ #include "GEO_reverse_uv_sampler.hh" #include "GEO_set_curve_type.hh" +#include "GEO_subdivide_curves.hh" /** * The code below uses a suffix naming convention to indicate the coordinate space: @@ -1457,6 +1458,73 @@ static void CURVES_OT_switch_direction(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } +namespace subdivide { + +static int exec(bContext *C, wmOperator *op) +{ + const int number_cuts = RNA_int_get(op->ptr, "number_cuts"); + + for (Curves *curves_id : get_unique_editable_curves(*C)) { + bke::CurvesGeometry &curves = curves_id->geometry.wrap(); + const int points_num = curves.points_num(); + IndexMaskMemory memory; + const IndexMask points_selection = retrieve_selected_points(*curves_id, memory); + if (points_selection.is_empty()) { + continue; + } + + Array points_selection_span(points_num); + points_selection.to_bools(points_selection_span); + + Array segment_cuts(points_num, number_cuts); + + const OffsetIndices points_by_curve = curves.points_by_curve(); + threading::parallel_for(points_by_curve.index_range(), 512, [&](const IndexRange range) { + for (const int curve_i : range) { + const IndexRange points = points_by_curve[curve_i]; + if (points.size() <= 1) { + continue; + } + for (const int point_i : points.drop_back(1)) { + if (!points_selection_span[point_i] || !points_selection_span[point_i + 1]) { + segment_cuts[point_i] = 0; + } + } + /* Cyclic segment. Doesn't matter if it is computed even if the curve is not cyclic. */ + if (!points_selection_span[points.last()] || !points_selection_span[points.first()]) { + segment_cuts[points.last()] = 0; + } + } + }); + + curves = geometry::subdivide_curves( + curves, curves.curves_range(), VArray::ForSpan(segment_cuts), {}); + + DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id); + } + return OPERATOR_FINISHED; +} + +} // namespace subdivide + +static void CURVES_OT_subdivide(wmOperatorType *ot) +{ + ot->name = "Subdivide"; + ot->idname = __func__; + ot->description = "Subdivide selected curve segments"; + + ot->exec = subdivide::exec; + ot->poll = editable_curves_in_edit_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + PropertyRNA *prop; + prop = RNA_def_int(ot->srna, "number_cuts", 1, 1, 1000, "Number of Cuts", "", 1, 10); + /* Avoid re-using last value because it can cause an unexpectedly high number of subdivisions. */ + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + void operatortypes_curves() { WM_operatortype_append(CURVES_OT_attribute_set); @@ -1479,6 +1547,7 @@ void operatortypes_curves() WM_operatortype_append(CURVES_OT_cyclic_toggle); WM_operatortype_append(CURVES_OT_curve_type_set); WM_operatortype_append(CURVES_OT_switch_direction); + WM_operatortype_append(CURVES_OT_subdivide); } void operatormacros_curves()