Curves: Separate to object operator
Use the existing "remove_points_and_split" utility added
for grease pencil to implement the operator for the curves
object type. The whole structure is similar to the recently
added point cloud separate operator (4cd3540579).
Pull Request: https://projects.blender.org/blender/blender/pulls/134763
This commit is contained in:
@@ -5778,6 +5778,7 @@ def km_edit_curves(params):
|
||||
"shift": True}, {"properties": [("deselect", True)]}),
|
||||
("curves.delete", {"type": 'X', "value": 'PRESS'}, None),
|
||||
("curves.delete", {"type": 'DEL', "value": 'PRESS'}, None),
|
||||
("curves.separate", {"type": 'P', "value": 'PRESS'}, None),
|
||||
("curves.select_more", {"type": 'NUMPAD_PLUS', "value": 'PRESS', "ctrl": True, "repeat": True}, None),
|
||||
("curves.select_less", {"type": 'NUMPAD_MINUS', "value": 'PRESS', "ctrl": True, "repeat": True}, None),
|
||||
*_template_items_proportional_editing(
|
||||
|
||||
@@ -25,6 +25,7 @@ set(SRC
|
||||
intern/curves_selection.cc
|
||||
intern/curves_undo.cc
|
||||
intern/select_linked_pick.cc
|
||||
intern/separate.cc
|
||||
)
|
||||
|
||||
set(LIB
|
||||
|
||||
@@ -1771,6 +1771,7 @@ void operatortypes_curves()
|
||||
WM_operatortype_append(CURVES_OT_select_linked_pick);
|
||||
WM_operatortype_append(CURVES_OT_select_more);
|
||||
WM_operatortype_append(CURVES_OT_select_less);
|
||||
WM_operatortype_append(CURVES_OT_separate);
|
||||
WM_operatortype_append(CURVES_OT_surface_set);
|
||||
WM_operatortype_append(CURVES_OT_delete);
|
||||
WM_operatortype_append(CURVES_OT_duplicate);
|
||||
|
||||
128
source/blender/editors/curves/intern/separate.cc
Normal file
128
source/blender/editors/curves/intern/separate.cc
Normal file
@@ -0,0 +1,128 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_index_mask.hh"
|
||||
#include "BLI_task.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_layer.hh"
|
||||
#include "BKE_lib_id.hh"
|
||||
|
||||
#include "BKE_curves.hh"
|
||||
|
||||
#include "ED_curves.hh"
|
||||
#include "ED_object.hh"
|
||||
|
||||
#include "DNA_layer_types.h"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
#include "DEG_depsgraph_build.hh"
|
||||
|
||||
#include "GEO_curves_remove_and_split.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
|
||||
namespace blender::ed::curves {
|
||||
|
||||
static int separate_exec(bContext *C, wmOperator * /*op*/)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
|
||||
Vector<Base *> bases = BKE_view_layer_array_from_bases_in_edit_mode(
|
||||
scene, view_layer, CTX_wm_view3d(C));
|
||||
|
||||
VectorSet<Curves *> src_curves;
|
||||
for (Base *base_src : bases) {
|
||||
src_curves.add(static_cast<Curves *>(base_src->object->data));
|
||||
}
|
||||
|
||||
/* Modify new curves and generate new curves in parallel. */
|
||||
Array<std::optional<bke::CurvesGeometry>> dst_geometry(src_curves.size());
|
||||
threading::parallel_for(dst_geometry.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
Curves &src = *src_curves[i];
|
||||
IndexMaskMemory memory;
|
||||
switch (bke::AttrDomain(src.selection_domain)) {
|
||||
case bke::AttrDomain::Point: {
|
||||
const IndexMask selection = retrieve_selected_points(src, memory);
|
||||
if (selection.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
dst_geometry[i] = bke::curves_copy_point_selection(src.geometry.wrap(), selection, {});
|
||||
src.geometry.wrap() = geometry::remove_points_and_split(src.geometry.wrap(), selection);
|
||||
break;
|
||||
}
|
||||
case bke::AttrDomain::Curve: {
|
||||
const IndexMask selection = retrieve_selected_curves(src, memory);
|
||||
if (selection.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
dst_geometry[i] = bke::curves_copy_curve_selection(src.geometry.wrap(), selection, {});
|
||||
src.geometry.wrap().remove_curves(selection, {});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Move new curves into main data-base. */
|
||||
Array<Curves *> dst_curves(src_curves.size(), nullptr);
|
||||
for (const int i : dst_curves.index_range()) {
|
||||
if (std::optional<bke::CurvesGeometry> &dst = dst_geometry[i]) {
|
||||
dst_curves[i] = BKE_curves_add(bmain, BKE_id_name(src_curves[i]->id));
|
||||
dst_curves[i]->geometry.wrap() = std::move(*dst);
|
||||
bke::curves_copy_parameters(*src_curves[i], *dst_curves[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Skip processing objects with no selected elements. */
|
||||
bases.remove_if([&](Base *base) {
|
||||
Curves *curves = static_cast<Curves *>(base->object->data);
|
||||
return dst_curves[src_curves.index_of(curves)] == nullptr;
|
||||
});
|
||||
|
||||
if (bases.is_empty()) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
/* Add new objects for the new curves. */
|
||||
for (Base *base_src : bases) {
|
||||
Curves *src = static_cast<Curves *>(base_src->object->data);
|
||||
Curves *dst = dst_curves[src_curves.index_of(src)];
|
||||
|
||||
Base *base_dst = object::add_duplicate(
|
||||
bmain, scene, view_layer, base_src, eDupli_ID_Flags(U.dupflag) & USER_DUP_ACT);
|
||||
Object *object_dst = base_dst->object;
|
||||
object_dst->mode = OB_MODE_OBJECT;
|
||||
object_dst->data = dst;
|
||||
|
||||
DEG_id_tag_update(&src->id, ID_RECALC_GEOMETRY);
|
||||
DEG_id_tag_update(&dst->id, ID_RECALC_GEOMETRY);
|
||||
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, base_src->object);
|
||||
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, object_dst);
|
||||
}
|
||||
|
||||
DEG_relations_tag_update(bmain);
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
void CURVES_OT_separate(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "Separate";
|
||||
ot->idname = "CURVES_OT_separate";
|
||||
ot->description = "Separate selected geometry into a new object";
|
||||
|
||||
ot->exec = separate_exec;
|
||||
ot->poll = editable_curves_in_edit_mode_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
} // namespace blender::ed::curves
|
||||
@@ -62,6 +62,7 @@
|
||||
#include "ED_transform_snap_object_context.hh"
|
||||
#include "ED_view3d.hh"
|
||||
|
||||
#include "GEO_curves_remove_and_split.hh"
|
||||
#include "GEO_join_geometries.hh"
|
||||
#include "GEO_realize_instances.hh"
|
||||
#include "GEO_reorder.hh"
|
||||
@@ -418,106 +419,6 @@ static void GREASE_PENCIL_OT_stroke_simplify(wmOperatorType *ot)
|
||||
/** \name Delete Operator
|
||||
* \{ */
|
||||
|
||||
bke::CurvesGeometry remove_points_and_split(const bke::CurvesGeometry &curves,
|
||||
const IndexMask &mask)
|
||||
{
|
||||
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
|
||||
const VArray<bool> src_cyclic = curves.cyclic();
|
||||
|
||||
Array<bool> points_to_delete(curves.points_num());
|
||||
mask.to_bools(points_to_delete.as_mutable_span());
|
||||
const int total_points = points_to_delete.as_span().count(false);
|
||||
|
||||
/* 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.as_span().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<IndexRange> ranges_to_keep = array_utils::find_all_ranges(curve_points_to_delete,
|
||||
false);
|
||||
|
||||
if (ranges_to_keep.is_empty()) {
|
||||
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);
|
||||
|
||||
BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &curves.vertex_group_names);
|
||||
|
||||
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);
|
||||
|
||||
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
|
||||
const bke::AttributeAccessor src_attributes = curves.attributes();
|
||||
|
||||
/* Transfer curve attributes. */
|
||||
gather_attributes(src_attributes,
|
||||
bke::AttrDomain::Curve,
|
||||
bke::AttrDomain::Curve,
|
||||
bke::attribute_filter_from_skip_ref({"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,
|
||||
bke::AttrDomain::Point,
|
||||
bke::AttrDomain::Point,
|
||||
{},
|
||||
dst_to_src_point,
|
||||
dst_attributes);
|
||||
|
||||
dst_curves.update_curve_types();
|
||||
dst_curves.remove_attributes_based_on_types();
|
||||
|
||||
return dst_curves;
|
||||
}
|
||||
|
||||
static int grease_pencil_delete_exec(bContext *C, wmOperator * /*op*/)
|
||||
{
|
||||
const Scene *scene = CTX_data_scene(C);
|
||||
@@ -542,7 +443,7 @@ static int grease_pencil_delete_exec(bContext *C, wmOperator * /*op*/)
|
||||
curves.remove_curves(elements, {});
|
||||
}
|
||||
else if (selection_domain == bke::AttrDomain::Point) {
|
||||
curves = remove_points_and_split(curves, elements);
|
||||
curves = geometry::remove_points_and_split(curves, elements);
|
||||
}
|
||||
info.drawing.tag_topology_changed();
|
||||
changed = true;
|
||||
@@ -2165,7 +2066,7 @@ static bool grease_pencil_separate_selected(bContext &C,
|
||||
/* Copy strokes to new CurvesGeometry. */
|
||||
drawing_dst->strokes_for_write() = bke::curves_copy_point_selection(
|
||||
curves_src, selected_points, {});
|
||||
curves_src = remove_points_and_split(curves_src, selected_points);
|
||||
curves_src = geometry::remove_points_and_split(curves_src, selected_points);
|
||||
|
||||
info.drawing.tag_topology_changed();
|
||||
drawing_dst->tag_topology_changed();
|
||||
@@ -2592,7 +2493,7 @@ static int grease_pencil_copy_strokes_exec(bContext *C, wmOperator *op)
|
||||
}
|
||||
else if (selection_domain == bke::AttrDomain::Point) {
|
||||
const IndexMask selected_points = ed::curves::retrieve_selected_points(curves, memory);
|
||||
copied_curves = remove_points_and_split(
|
||||
copied_curves = geometry::remove_points_and_split(
|
||||
curves, selected_points.complement(curves.points_range(), memory));
|
||||
num_elements_copied += copied_curves.points_num();
|
||||
}
|
||||
|
||||
@@ -140,6 +140,7 @@ void CURVES_OT_attribute_set(wmOperatorType *ot);
|
||||
void CURVES_OT_draw(wmOperatorType *ot);
|
||||
void CURVES_OT_extrude(wmOperatorType *ot);
|
||||
void CURVES_OT_select_linked_pick(wmOperatorType *ot);
|
||||
void CURVES_OT_separate(wmOperatorType *ot);
|
||||
|
||||
/** \} */
|
||||
|
||||
|
||||
@@ -914,13 +914,6 @@ GreasePencilLineartModifierData *get_first_lineart_modifier(const Object &ob);
|
||||
|
||||
GreasePencil *from_context(bContext &C);
|
||||
|
||||
/**
|
||||
* Remove the points in the \a point_mask and split each curve at the points that are removed (if
|
||||
* necessary).
|
||||
*/
|
||||
bke::CurvesGeometry remove_points_and_split(const bke::CurvesGeometry &curves,
|
||||
const IndexMask &point_mask);
|
||||
|
||||
/* Make sure selection domain is updated to match the current selection mode. */
|
||||
bool ensure_selection_domain(ToolSettings *ts, Object *object);
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "GEO_curves_remove_and_split.hh"
|
||||
#include "GEO_join_geometries.hh"
|
||||
#include "GEO_smooth_curves.hh"
|
||||
|
||||
@@ -1800,8 +1801,8 @@ static bool remove_points_and_split_from_drawings(
|
||||
if (Drawing *drawing = get_current_drawing_or_duplicate_for_autokey(
|
||||
scene, grease_pencil, info.layer_index))
|
||||
{
|
||||
drawing->strokes_for_write() = ed::greasepencil::remove_points_and_split(drawing->strokes(),
|
||||
points_to_remove);
|
||||
drawing->strokes_for_write() = geometry::remove_points_and_split(drawing->strokes(),
|
||||
points_to_remove);
|
||||
drawing->tag_topology_changed();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ set(INC_SYS
|
||||
set(SRC
|
||||
intern/add_curves_on_mesh.cc
|
||||
intern/curve_constraints.cc
|
||||
intern/curves_remove_and_split.cc
|
||||
intern/extend_curves.cc
|
||||
intern/extract_elements.cc
|
||||
intern/fillet_curves.cc
|
||||
|
||||
18
source/blender/geometry/GEO_curves_remove_and_split.hh
Normal file
18
source/blender/geometry/GEO_curves_remove_and_split.hh
Normal file
@@ -0,0 +1,18 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_curves.hh"
|
||||
|
||||
namespace blender::geometry {
|
||||
|
||||
/**
|
||||
* Remove the points in the \a point_mask and split each curve at the points that are removed (if
|
||||
* necessary).
|
||||
*/
|
||||
bke::CurvesGeometry remove_points_and_split(const bke::CurvesGeometry &curves,
|
||||
const IndexMask &mask);
|
||||
|
||||
} // namespace blender::geometry
|
||||
115
source/blender/geometry/intern/curves_remove_and_split.cc
Normal file
115
source/blender/geometry/intern/curves_remove_and_split.cc
Normal file
@@ -0,0 +1,115 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_array_utils.hh"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_deform.hh"
|
||||
|
||||
#include "GEO_curves_remove_and_split.hh"
|
||||
|
||||
namespace blender::geometry {
|
||||
|
||||
bke::CurvesGeometry remove_points_and_split(const bke::CurvesGeometry &curves,
|
||||
const IndexMask &mask)
|
||||
{
|
||||
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
|
||||
const VArray<bool> src_cyclic = curves.cyclic();
|
||||
|
||||
Array<bool> points_to_delete(curves.points_num());
|
||||
mask.to_bools(points_to_delete.as_mutable_span());
|
||||
const int total_points = points_to_delete.as_span().count(false);
|
||||
|
||||
/* 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.as_span().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<IndexRange> ranges_to_keep = array_utils::find_all_ranges(curve_points_to_delete,
|
||||
false);
|
||||
|
||||
if (ranges_to_keep.is_empty()) {
|
||||
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);
|
||||
|
||||
BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &curves.vertex_group_names);
|
||||
|
||||
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);
|
||||
|
||||
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
|
||||
const bke::AttributeAccessor src_attributes = curves.attributes();
|
||||
|
||||
/* Transfer curve attributes. */
|
||||
gather_attributes(src_attributes,
|
||||
bke::AttrDomain::Curve,
|
||||
bke::AttrDomain::Curve,
|
||||
bke::attribute_filter_from_skip_ref({"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,
|
||||
bke::AttrDomain::Point,
|
||||
bke::AttrDomain::Point,
|
||||
{},
|
||||
dst_to_src_point,
|
||||
dst_attributes);
|
||||
|
||||
dst_curves.update_curve_types();
|
||||
dst_curves.remove_attributes_based_on_types();
|
||||
|
||||
return dst_curves;
|
||||
}
|
||||
|
||||
} // namespace blender::geometry
|
||||
Reference in New Issue
Block a user