Curves: add Split operator
Adds Split operator to curves. It should have the same behavior as the corresponding operator for legacy curves. Pull Request: https://projects.blender.org/blender/blender/pulls/131788
This commit is contained in:
committed by
Jacques Lucke
parent
fc6a15b2eb
commit
2c42294557
@@ -5781,6 +5781,7 @@ def km_edit_curves(params):
|
||||
("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),
|
||||
("curves.split", {"type": 'Y', "value": 'PRESS'}, None),
|
||||
*_template_items_proportional_editing(
|
||||
params, connected=True, toggle_data_path="tool_settings.use_proportional_edit"),
|
||||
("curves.tilt_clear", {"type": 'T', "value": 'PRESS', "alt": True}, None),
|
||||
|
||||
@@ -5901,6 +5901,10 @@ class VIEW3D_MT_edit_curves_context_menu(Menu):
|
||||
|
||||
layout.operator_menu_enum("curves.handle_type_set", "type")
|
||||
|
||||
layout.separator()
|
||||
|
||||
layout.operator("curves.split")
|
||||
|
||||
|
||||
class VIEW3D_MT_edit_pointcloud(Menu):
|
||||
bl_label = "Point Cloud"
|
||||
|
||||
@@ -507,6 +507,36 @@ void foreach_curve_by_type(const VArray<int8_t> &types,
|
||||
FunctionRef<void(IndexMask)> poly_fn,
|
||||
FunctionRef<void(IndexMask)> bezier_fn,
|
||||
FunctionRef<void(IndexMask)> nurbs_fn);
|
||||
|
||||
using SelectedCallback = FunctionRef<void(
|
||||
int curve_i, IndexRange curve_points, Span<IndexRange> selected_point_ranges)>;
|
||||
using UnselectedCallback = FunctionRef<void(IndexRange curves, IndexRange unselected_points)>;
|
||||
|
||||
/**
|
||||
* Calls callback function for each curve having selected points.
|
||||
*
|
||||
* \param mask: selected points.
|
||||
* \param points_by_curve: The offsets of every curve into arrays on the points domain.
|
||||
* \param selected_fn: callback function called for each curve with at least one point selected.
|
||||
*/
|
||||
void foreach_selected_point_ranges_per_curve(const IndexMask &mask,
|
||||
const OffsetIndices<int> points_by_curve,
|
||||
SelectedCallback selected_fn);
|
||||
|
||||
/**
|
||||
* Calls callback function for each curve having selected points.
|
||||
* Calls second callback for groups of curves with no points selected.
|
||||
*
|
||||
* \param mask: selected points.
|
||||
* \param points_by_curve: The offsets of every curve into arrays on the points domain.
|
||||
* \param selected_fn: callback function called for each curve with at least one point selected.
|
||||
* \param unselected_fn: callback function called for groups of curves with no selected points.
|
||||
*/
|
||||
void foreach_selected_point_ranges_per_curve(const IndexMask &mask,
|
||||
const OffsetIndices<int> points_by_curve,
|
||||
SelectedCallback selected_fn,
|
||||
UnselectedCallback unselected_fn);
|
||||
|
||||
namespace bezier {
|
||||
|
||||
/**
|
||||
|
||||
@@ -83,6 +83,81 @@ void foreach_curve_by_type(const VArray<int8_t> &types,
|
||||
call_if_not_empty(CURVE_TYPE_NURBS, nurbs_fn);
|
||||
}
|
||||
|
||||
static void if_has_data_call_callback(const Span<int> offset_data,
|
||||
const int begin,
|
||||
const int end,
|
||||
UnselectedCallback callback)
|
||||
{
|
||||
if (begin < end) {
|
||||
const IndexRange curves = IndexRange::from_begin_end(begin, end);
|
||||
const IndexRange points = IndexRange::from_begin_end(offset_data[begin], offset_data[end]);
|
||||
callback(curves, points);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Fn>
|
||||
static void foreach_selected_point_ranges_per_curve_(const IndexMask &mask,
|
||||
const OffsetIndices<int> points_by_curve,
|
||||
SelectedCallback selected_fn,
|
||||
Fn unselected_fn)
|
||||
{
|
||||
Vector<IndexRange> ranges;
|
||||
Span<int> offset_data = points_by_curve.data();
|
||||
|
||||
int curve_i = mask.is_empty() ? -1 : 0;
|
||||
|
||||
int range_first = mask.is_empty() ? 0 : mask.first();
|
||||
int range_last = range_first - 1;
|
||||
|
||||
mask.foreach_index([&](const int64_t index) {
|
||||
if (offset_data[curve_i + 1] <= index) {
|
||||
int first_unselected_curve = curve_i;
|
||||
if (range_last >= range_first) {
|
||||
ranges.append(IndexRange::from_begin_end_inclusive(range_first, range_last));
|
||||
selected_fn(curve_i, points_by_curve[curve_i], ranges);
|
||||
ranges.clear();
|
||||
first_unselected_curve++;
|
||||
}
|
||||
do {
|
||||
++curve_i;
|
||||
} while (offset_data[curve_i + 1] <= index);
|
||||
if constexpr (std::is_invocable_r_v<void, Fn, IndexRange, IndexRange>) {
|
||||
if_has_data_call_callback(offset_data, first_unselected_curve, curve_i, unselected_fn);
|
||||
}
|
||||
range_first = index;
|
||||
}
|
||||
else if (range_last + 1 != index) {
|
||||
ranges.append(IndexRange::from_begin_end_inclusive(range_first, range_last));
|
||||
range_first = index;
|
||||
}
|
||||
range_last = index;
|
||||
});
|
||||
|
||||
if (range_last - range_first >= 0) {
|
||||
ranges.append(IndexRange::from_begin_end_inclusive(range_first, range_last));
|
||||
selected_fn(curve_i, points_by_curve[curve_i], ranges);
|
||||
}
|
||||
if constexpr (std::is_invocable_r_v<void, Fn, IndexRange, IndexRange>) {
|
||||
if_has_data_call_callback(offset_data, curve_i + 1, points_by_curve.size(), unselected_fn);
|
||||
}
|
||||
}
|
||||
|
||||
void foreach_selected_point_ranges_per_curve(const IndexMask &mask,
|
||||
const OffsetIndices<int> offset_indices,
|
||||
SelectedCallback selected_fn)
|
||||
{
|
||||
foreach_selected_point_ranges_per_curve_<void()>(mask, offset_indices, selected_fn, nullptr);
|
||||
}
|
||||
|
||||
void foreach_selected_point_ranges_per_curve(const IndexMask &mask,
|
||||
const OffsetIndices<int> offset_indices,
|
||||
SelectedCallback selected_fn,
|
||||
UnselectedCallback unselected_fn)
|
||||
{
|
||||
foreach_selected_point_ranges_per_curve_<UnselectedCallback>(
|
||||
mask, offset_indices, selected_fn, unselected_fn);
|
||||
}
|
||||
|
||||
namespace bezier {
|
||||
|
||||
Array<float3> retrieve_all_positions(const bke::CurvesGeometry &curves,
|
||||
|
||||
@@ -41,44 +41,6 @@ bool remove_selection(bke::CurvesGeometry &curves, const bke::AttrDomain selecti
|
||||
return attributes.domain_size(selection_domain) != domain_size_orig;
|
||||
}
|
||||
|
||||
static void foreach_content_slice_by_offsets(
|
||||
const IndexMask &mask,
|
||||
const OffsetIndices<int> offset_indices,
|
||||
FunctionRef<void(Span<IndexRange> selected_points, IndexRange slice_points, int slice)> fn)
|
||||
{
|
||||
Vector<IndexRange> ranges;
|
||||
Span<int> offset_data = offset_indices.data();
|
||||
|
||||
int slice = 0;
|
||||
|
||||
int range_first = mask.first();
|
||||
int range_last = mask.first() - 1;
|
||||
|
||||
mask.foreach_index([&](const int64_t index) {
|
||||
if (offset_data[slice + 1] <= index) {
|
||||
if (range_last - range_first >= 0) {
|
||||
ranges.append(IndexRange::from_begin_end_inclusive(range_first, range_last));
|
||||
fn(ranges, offset_indices[slice], slice);
|
||||
ranges.clear();
|
||||
}
|
||||
do {
|
||||
++slice;
|
||||
} while (offset_data[slice + 1] <= index);
|
||||
range_first = index;
|
||||
}
|
||||
else if (range_last + 1 != index) {
|
||||
ranges.append(IndexRange::from_begin_end_inclusive(range_first, range_last));
|
||||
range_first = index;
|
||||
}
|
||||
range_last = index;
|
||||
});
|
||||
|
||||
if (range_last - range_first >= 0) {
|
||||
ranges.append(IndexRange::from_begin_end_inclusive(range_first, range_last));
|
||||
fn(ranges, offset_indices[slice], slice);
|
||||
}
|
||||
}
|
||||
|
||||
static void curve_offsets_from_selection(const Span<IndexRange> selected_points,
|
||||
const IndexRange points,
|
||||
const int curve,
|
||||
@@ -131,10 +93,10 @@ void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask)
|
||||
dst_cyclic.reserve(curves.curves_num());
|
||||
|
||||
/* Add the duplicated curves and points. */
|
||||
foreach_content_slice_by_offsets(
|
||||
bke::curves::foreach_selected_point_ranges_per_curve(
|
||||
mask,
|
||||
points_by_curve,
|
||||
[&](Span<IndexRange> ranges_to_duplicate, IndexRange points, int curve) {
|
||||
[&](const int curve, const IndexRange points, Span<IndexRange> ranges_to_duplicate) {
|
||||
curve_offsets_from_selection(ranges_to_duplicate,
|
||||
points,
|
||||
curve,
|
||||
@@ -269,6 +231,189 @@ void duplicate_curves(bke::CurvesGeometry &curves, const IndexMask &mask)
|
||||
}
|
||||
}
|
||||
|
||||
static void invert_ranges(const IndexRange universe,
|
||||
const Span<IndexRange> ranges,
|
||||
Array<IndexRange> &inverted)
|
||||
{
|
||||
const bool contains_first = ranges.first().first() == universe.first();
|
||||
const bool contains_last = ranges.last().last() == universe.last();
|
||||
inverted.reinitialize(ranges.size() - 1 + !contains_first + !contains_last);
|
||||
|
||||
int64_t start = contains_first ? ranges.first().one_after_last() : universe.first();
|
||||
int i = 0;
|
||||
for (const IndexRange range : ranges.drop_front(contains_first)) {
|
||||
inverted[i++] = IndexRange::from_begin_end(start, range.first());
|
||||
start = range.one_after_last();
|
||||
}
|
||||
if (!contains_last) {
|
||||
inverted.last() = IndexRange::from_begin_end(start, universe.one_after_last());
|
||||
}
|
||||
}
|
||||
|
||||
static IndexRange extend_range(const IndexRange range, const IndexRange universe)
|
||||
{
|
||||
return IndexRange::from_begin_end_inclusive(math::max(range.start() - 1, universe.start()),
|
||||
math::min(range.one_after_last(), universe.last()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends each range by one point at both ends of it. Merges adjacent ranges if intersections
|
||||
* occur.
|
||||
*/
|
||||
static void extend_range_by_1_within_bounds(const IndexRange universe,
|
||||
const bool cyclic,
|
||||
const Span<IndexRange> ranges,
|
||||
Vector<IndexRange> &extended_ranges)
|
||||
{
|
||||
extended_ranges.clear();
|
||||
if (ranges.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool first_match = ranges.first().first() == universe.first();
|
||||
const bool last_match = ranges.last().last() == universe.last();
|
||||
const bool add_first = cyclic && last_match && !first_match;
|
||||
const bool add_last = cyclic && first_match && !last_match;
|
||||
|
||||
IndexRange current = add_first ? IndexRange::from_single(universe.first()) :
|
||||
extend_range(ranges.first(), universe);
|
||||
for (const IndexRange range : ranges.drop_front(!add_first)) {
|
||||
const IndexRange extended = extend_range(range, universe);
|
||||
if (extended.first() <= current.last()) {
|
||||
current = IndexRange::from_begin_end_inclusive(current.start(), extended.last());
|
||||
}
|
||||
else {
|
||||
extended_ranges.append(current);
|
||||
current = extended;
|
||||
}
|
||||
}
|
||||
extended_ranges.append(current);
|
||||
if (add_last) {
|
||||
extended_ranges.append(IndexRange::from_single(universe.last()));
|
||||
}
|
||||
}
|
||||
|
||||
static void copy_data_to_geometry(const bke::CurvesGeometry &src_curves,
|
||||
const Span<int> dst_to_src_curve,
|
||||
const Span<int> offsets,
|
||||
const Span<bool> cyclic,
|
||||
const Span<IndexRange> src_ranges,
|
||||
const OffsetIndices<int> dst_offsets,
|
||||
bke::CurvesGeometry &dst_curves)
|
||||
{
|
||||
dst_curves.resize(offsets.last(), dst_to_src_curve.size());
|
||||
|
||||
array_utils::copy(offsets, dst_curves.offsets_for_write());
|
||||
dst_curves.cyclic_for_write().copy_from(cyclic);
|
||||
|
||||
const bke::AttributeAccessor src_attributes = src_curves.attributes();
|
||||
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
|
||||
|
||||
bke::gather_attributes(src_attributes,
|
||||
bke::AttrDomain::Curve,
|
||||
bke::AttrDomain::Curve,
|
||||
bke::attribute_filter_from_skip_ref({"cyclic"}),
|
||||
dst_to_src_curve,
|
||||
dst_attributes);
|
||||
|
||||
for (auto &attribute : bke::retrieve_attributes_for_transfer(
|
||||
src_attributes,
|
||||
dst_attributes,
|
||||
ATTR_DOMAIN_MASK_POINT,
|
||||
bke::attribute_filter_from_skip_ref(
|
||||
ed::curves::get_curves_selection_attribute_names(src_curves))))
|
||||
{
|
||||
bke::attribute_math::gather_ranges_to_groups(
|
||||
src_ranges, dst_offsets, attribute.src, attribute.dst.span);
|
||||
attribute.dst.finish();
|
||||
};
|
||||
|
||||
dst_curves.update_curve_types();
|
||||
dst_curves.tag_topology_changed();
|
||||
}
|
||||
|
||||
bke::CurvesGeometry split_points(const bke::CurvesGeometry &curves,
|
||||
const IndexMask &points_to_split)
|
||||
{
|
||||
const OffsetIndices points_by_curve = curves.points_by_curve();
|
||||
const VArray<bool> cyclic = curves.cyclic();
|
||||
|
||||
Vector<int> curve_map;
|
||||
Vector<int> new_offsets({0});
|
||||
|
||||
Vector<IndexRange> src_ranges;
|
||||
Vector<int> dst_offsets({0});
|
||||
Vector<bool> new_cyclic;
|
||||
|
||||
Vector<IndexRange> deselect;
|
||||
|
||||
Array<IndexRange> unselected_curve_points;
|
||||
Vector<IndexRange> curve_points_to_preserve;
|
||||
|
||||
bke::curves::foreach_selected_point_ranges_per_curve(
|
||||
points_to_split,
|
||||
points_by_curve,
|
||||
[&](const int curve, const IndexRange points, const Span<IndexRange> selected_curve_points) {
|
||||
const int points_start = new_offsets.last();
|
||||
curve_offsets_from_selection(selected_curve_points,
|
||||
points,
|
||||
curve,
|
||||
cyclic[curve],
|
||||
new_offsets,
|
||||
new_cyclic,
|
||||
src_ranges,
|
||||
dst_offsets,
|
||||
curve_map);
|
||||
const int split_points_num = new_offsets.last() - points_start;
|
||||
/* Invert ranges to get non selected points. */
|
||||
invert_ranges(points, selected_curve_points, unselected_curve_points);
|
||||
/* Extended every range to left and right by one point. Any resulting intersection is
|
||||
* merged. */
|
||||
extend_range_by_1_within_bounds(
|
||||
points, cyclic[curve], unselected_curve_points, curve_points_to_preserve);
|
||||
const int size_before = curve_map.size();
|
||||
curve_offsets_from_selection(curve_points_to_preserve,
|
||||
points,
|
||||
curve,
|
||||
cyclic[curve] &&
|
||||
(split_points_num <= curve_points_to_preserve.size()),
|
||||
new_offsets,
|
||||
new_cyclic,
|
||||
src_ranges,
|
||||
dst_offsets,
|
||||
curve_map);
|
||||
deselect.append(IndexRange::from_begin_end(size_before, curve_map.size()));
|
||||
},
|
||||
[&](const IndexRange curves, const IndexRange points) {
|
||||
deselect.append(IndexRange::from_begin_size(curve_map.size(), curves.size()));
|
||||
src_ranges.append(points);
|
||||
dst_offsets.append(dst_offsets.last() + points.size());
|
||||
int last_offset = new_offsets.last();
|
||||
for (const int curve : curves) {
|
||||
last_offset += points_by_curve[curve].size();
|
||||
new_offsets.append(last_offset);
|
||||
curve_map.append(curve);
|
||||
new_cyclic.append(cyclic[curve]);
|
||||
}
|
||||
});
|
||||
|
||||
bke::CurvesGeometry new_curves;
|
||||
copy_data_to_geometry(
|
||||
curves, curve_map, new_offsets, new_cyclic, src_ranges, dst_offsets.as_span(), new_curves);
|
||||
|
||||
OffsetIndices<int> new_points_by_curve = new_curves.points_by_curve();
|
||||
foreach_selection_attribute_writer(
|
||||
new_curves, bke::AttrDomain::Point, [&](bke::GSpanAttributeWriter &selection) {
|
||||
for (const IndexRange curves : deselect) {
|
||||
for (const int curve : curves) {
|
||||
fill_selection_false(selection.span.slice(new_points_by_curve[curve]));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return new_curves;
|
||||
}
|
||||
|
||||
void add_curves(bke::CurvesGeometry &curves, const Span<int> new_sizes)
|
||||
{
|
||||
const int orig_points_num = curves.points_num();
|
||||
|
||||
@@ -1137,6 +1137,43 @@ static void CURVES_OT_select_less(wmOperatorType *ot)
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
namespace split {
|
||||
|
||||
static int split_exec(bContext *C, wmOperator * /*op*/)
|
||||
{
|
||||
VectorSet<Curves *> unique_curves = get_unique_editable_curves(*C);
|
||||
for (Curves *curves_id : unique_curves) {
|
||||
CurvesGeometry &curves = curves_id->geometry.wrap();
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask points_to_split = retrieve_all_selected_points(curves, memory);
|
||||
if (points_to_split.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
curves = split_points(curves, points_to_split);
|
||||
|
||||
curves.calculate_bezier_auto_handles();
|
||||
|
||||
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 split
|
||||
|
||||
static void CURVES_OT_split(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "Split";
|
||||
ot->idname = __func__;
|
||||
ot->description = "Split selected points";
|
||||
|
||||
ot->exec = split::split_exec;
|
||||
ot->poll = editable_curves_point_domain_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
namespace surface_set {
|
||||
|
||||
static bool surface_set_poll(bContext *C)
|
||||
@@ -1771,6 +1808,7 @@ void operatortypes_curves()
|
||||
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_split);
|
||||
WM_operatortype_append(CURVES_OT_surface_set);
|
||||
WM_operatortype_append(CURVES_OT_delete);
|
||||
WM_operatortype_append(CURVES_OT_duplicate);
|
||||
|
||||
@@ -66,6 +66,16 @@ IndexMask retrieve_selected_points(const bke::CurvesGeometry &curves, IndexMaskM
|
||||
return retrieve_selected_points(curves, ".selection", memory);
|
||||
}
|
||||
|
||||
IndexMask retrieve_all_selected_points(const bke::CurvesGeometry &curves, IndexMaskMemory &memory)
|
||||
{
|
||||
Vector<IndexMask> selection_by_attribute;
|
||||
for (const StringRef selection_name : ed::curves::get_curves_selection_attribute_names(curves)) {
|
||||
selection_by_attribute.append(
|
||||
ed::curves::retrieve_selected_points(curves, selection_name, memory));
|
||||
}
|
||||
return IndexMask::from_union(selection_by_attribute, memory);
|
||||
}
|
||||
|
||||
IndexMask retrieve_selected_points(const bke::CurvesGeometry &curves,
|
||||
StringRef attribute_name,
|
||||
IndexMaskMemory &memory)
|
||||
|
||||
@@ -47,6 +47,19 @@ static bke::CurvesGeometry create_curves(const Vector<float3> positions,
|
||||
return create_curves(Span<Vector<float3>>(&positions, 1), order, is_cyclic);
|
||||
}
|
||||
|
||||
static void validate_positions(const Span<Vector<float3>> expected_positions,
|
||||
const OffsetIndices<int> points_by_curve,
|
||||
const Span<float3> positions)
|
||||
{
|
||||
for (const int curve : expected_positions.index_range()) {
|
||||
const Span<float3> expected_curve_positions = expected_positions[curve];
|
||||
const IndexRange points = points_by_curve[curve];
|
||||
for (const int point : expected_curve_positions.index_range()) {
|
||||
EXPECT_EQ(positions[points[point]], expected_curve_positions[point]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(curves_editors, DuplicatePointsTwoSingle)
|
||||
{
|
||||
/* Two points from single curve. */
|
||||
@@ -135,4 +148,104 @@ TEST(curves_editors, DuplicatePointsTwoCyclic)
|
||||
EXPECT_TRUE(positions[15] == expected_positions[2][0]);
|
||||
}
|
||||
|
||||
TEST(curves_editors, SplitPointsTwoSingle)
|
||||
{
|
||||
/* Split two points from single curve. */
|
||||
const Vector<float3> positions = {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}};
|
||||
|
||||
bke::CurvesGeometry curves = create_curves(positions, 4, {});
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices<int>({1, 2}, memory);
|
||||
|
||||
bke::CurvesGeometry new_curves = split_points(curves, mask);
|
||||
|
||||
const Vector<Vector<float3>> expected_positions = {
|
||||
{{-1, 1, 0}, {1, 1, 0}}, {{-1.5, 0, 0}, {-1, 1, 0}}, {{1, 1, 0}, {1.5, 0, 0}}};
|
||||
|
||||
EXPECT_EQ(new_curves.curves_num(), expected_positions.size());
|
||||
validate_positions(expected_positions, new_curves.points_by_curve(), new_curves.positions());
|
||||
}
|
||||
|
||||
TEST(curves_editors, SplitPointsFourThree)
|
||||
{
|
||||
/* Four points from three curves. One curve has one point. */
|
||||
const Vector<Vector<float3>> positions = {
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
|
||||
{{0, 0, 0}},
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
|
||||
|
||||
bke::CurvesGeometry curves = create_curves(positions, 4, {});
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices(Array<int>{0, 1, 4, 9}.as_span(), memory);
|
||||
|
||||
bke::CurvesGeometry new_curves = split_points(curves, mask);
|
||||
|
||||
const Vector<Vector<float3>> expected_positions = {
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}},
|
||||
{{-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
|
||||
{{0, 0, 0}},
|
||||
{{1, -1, 0}},
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
|
||||
|
||||
EXPECT_EQ(new_curves.curves_num(), expected_positions.size());
|
||||
validate_positions(expected_positions, new_curves.points_by_curve(), new_curves.positions());
|
||||
}
|
||||
|
||||
TEST(curves_editors, SplitPointsTwoCyclic)
|
||||
{
|
||||
/* Two points from cyclic curve. Points are on cycle. */
|
||||
const Vector<Vector<float3>> positions = {
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
|
||||
{{0, 0, 0}},
|
||||
{{1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {-1, 1, 0}},
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
|
||||
|
||||
bke::CurvesGeometry curves = create_curves(positions, 4, {2});
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices(Array<int>{5, 8}.as_span(), memory);
|
||||
|
||||
bke::CurvesGeometry new_curves = split_points(curves, mask);
|
||||
|
||||
const Vector<Vector<float3>> expected_positions = {
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
|
||||
{{0, 0, 0}},
|
||||
{{-1, 1, 0}, {1, 1, 0}},
|
||||
{{1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {-1, 1, 0}},
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
|
||||
|
||||
EXPECT_EQ(new_curves.curves_num(), expected_positions.size());
|
||||
validate_positions(expected_positions, new_curves.points_by_curve(), new_curves.positions());
|
||||
Array<bool> expected_cyclic = {false, false, false, false, false};
|
||||
VArray<bool> cyclic = new_curves.cyclic();
|
||||
for (const int i : expected_cyclic.index_range()) {
|
||||
EXPECT_EQ(expected_cyclic[i], cyclic[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(curves_editors, SplitPointsTwoTouchCyclic)
|
||||
{
|
||||
/* Two points from cyclic curve. Points are touching cycle. */
|
||||
const Vector<Vector<float3>> positions = {
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
|
||||
{{0, 0, 0}},
|
||||
{{1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {-1, 1, 0}},
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
|
||||
|
||||
bke::CurvesGeometry curves = create_curves(positions, 4, {2});
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices(Array<int>{5, 6}.as_span(), memory);
|
||||
|
||||
bke::CurvesGeometry new_curves = split_points(curves, mask);
|
||||
|
||||
const Vector<Vector<float3>> expected_positions = {
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
|
||||
{{0, 0, 0}},
|
||||
{{1, 1, 0}, {1, -1, 0}},
|
||||
{{1, -1, 0}, {-1, -1, 0}, {-1, 1, 0}, {1, 1, 0}},
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
|
||||
|
||||
EXPECT_EQ(new_curves.curves_num(), expected_positions.size());
|
||||
validate_positions(expected_positions, new_curves.points_by_curve(), new_curves.positions());
|
||||
}
|
||||
|
||||
} // namespace blender::ed::curves::tests
|
||||
|
||||
@@ -230,6 +230,12 @@ IndexMask retrieve_selected_points(const bke::CurvesGeometry &curves,
|
||||
IndexMaskMemory &memory);
|
||||
IndexMask retrieve_selected_points(const Curves &curves_id, IndexMaskMemory &memory);
|
||||
|
||||
/**
|
||||
* Find points that are selected (a selection factor greater than zero) or have
|
||||
* any of their Bezier handle selected.
|
||||
*/
|
||||
IndexMask retrieve_all_selected_points(const bke::CurvesGeometry &curves, IndexMaskMemory &memory);
|
||||
|
||||
/**
|
||||
* If the selection_id attribute doesn't exist, create it with the requested type (bool or float).
|
||||
*/
|
||||
@@ -419,6 +425,9 @@ bool remove_selection(bke::CurvesGeometry &curves, bke::AttrDomain selection_dom
|
||||
void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask);
|
||||
void duplicate_curves(bke::CurvesGeometry &curves, const IndexMask &mask);
|
||||
|
||||
bke::CurvesGeometry split_points(const bke::CurvesGeometry &curves,
|
||||
const IndexMask &points_to_split);
|
||||
|
||||
/**
|
||||
* Adds new curves to \a curves.
|
||||
* \param new_sizes: The new size for each curve. Sizes must be > 0.
|
||||
|
||||
Reference in New Issue
Block a user