Curves: Bezier handle selection support

Adds ".selection_handle_left" and ".selection_handle_right" attributes to
`CurvesGeometry` and their support in curve's select all, pick, box, circle
and lasso select tools.

Pull Request: https://projects.blender.org/blender/blender/pulls/119712
This commit is contained in:
Laurynas Duburas
2024-04-03 16:40:36 +02:00
committed by Jacques Lucke
parent 20e07f0102
commit 4889aed8ac
7 changed files with 618 additions and 263 deletions

View File

@@ -407,9 +407,9 @@ static void create_edit_points_selection(const bke::CurvesGeometry &curves,
}
const VArray<float> attribute_left = *curves.attributes().lookup_or_default<float>(
".selection_handle_left", bke::AttrDomain::Point, 0.0f);
".selection_handle_left", bke::AttrDomain::Point, 1.0f);
const VArray<float> attribute_right = *curves.attributes().lookup_or_default<float>(
".selection_handle_right", bke::AttrDomain::Point, 0.0f);
".selection_handle_right", bke::AttrDomain::Point, 1.0f);
const OffsetIndices<int> points_by_curve = curves.points_by_curve();

View File

@@ -773,8 +773,8 @@ static int curves_draw_exec(bContext *C, wmOperator *op)
(cps->radius_taper_end != 0.0f));
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
attributes.remove(".selection");
Span<StringRef> selection_attribute_names = get_curves_selection_attribute_names(curves);
remove_selection_attributes(attributes, selection_attribute_names);
if (cdd->curve_type == CU_BEZIER) {
/* Allow to interpolate multiple channels */
@@ -918,10 +918,19 @@ static int curves_draw_exec(bContext *C, wmOperator *op)
curves.nurbs_orders_for_write()[curve_index] = order;
curves.fill_curve_types(IndexRange(curve_index, 1), curve_type);
bke::AttributeWriter<bool> selection = attributes.lookup_or_add_for_write<bool>(
".selection", bke::AttrDomain::Curve);
selection.varray.set(curve_index, true);
selection.finish();
/* If Bezier curve is being added, loop through all three names, otherwise through ones in
* `selection_attribute_names`. */
for (const StringRef selection_name :
(bezier_as_nurbs ? selection_attribute_names :
get_curves_all_selection_attribute_names()))
{
bke::AttributeWriter<bool> selection = attributes.lookup_or_add_for_write<bool>(
selection_name, bke::AttrDomain::Curve);
if (selection_name == ".selection" || !bezier_as_nurbs) {
selection.varray.set(curve_index, true);
}
selection.finish();
}
if (attributes.contains("resolution")) {
curves.resolution_for_write()[curve_index] = 12;
@@ -935,13 +944,21 @@ static int curves_draw_exec(bContext *C, wmOperator *op)
"handle_type_left",
"handle_type_right",
"nurbs_weight",
".selection"},
".selection",
".selection_handle_left",
".selection_handle_right"},
curves.points_by_curve()[curve_index]);
bke::fill_attribute_range_default(
attributes,
bke::AttrDomain::Curve,
{"curve_type", "resolution", "cyclic", "nurbs_order", "knots_mode", ".selection"},
IndexRange(curve_index, 1));
bke::fill_attribute_range_default(attributes,
bke::AttrDomain::Curve,
{"curve_type",
"resolution",
"cyclic",
"nurbs_order",
"knots_mode",
".selection",
".selection_handle_left",
".selection_handle_right"},
IndexRange(curve_index, 1));
}
if (corners_index) {
@@ -986,12 +1003,24 @@ static int curves_draw_exec(bContext *C, wmOperator *op)
selection.varray.set(curve_index, true);
selection.finish();
/* Creates ".selection_handle_left" and ".selection_handle_right" attributes, otherwise all
* existing Bezier handles would be treated as selected. */
for (const StringRef selection_name : get_curves_bezier_selection_attribute_names(curves)) {
bke::AttributeWriter<bool> selection = attributes.lookup_or_add_for_write<bool>(
selection_name, bke::AttrDomain::Curve);
selection.finish();
}
bke::fill_attribute_range_default(
attributes, bke::AttrDomain::Point, {"position", "radius", ".selection"}, new_points);
bke::fill_attribute_range_default(attributes,
bke::AttrDomain::Curve,
{"curve_type", ".selection"},
IndexRange(curve_index, 1));
attributes,
bke::AttrDomain::Point,
{"position", "radius", ".selection", ".selection_handle_left", ".selection_handle_right"},
new_points);
bke::fill_attribute_range_default(
attributes,
bke::AttrDomain::Curve,
{"curve_type", ".selection", ".selection_handle_left", ".selection_handle_right"},
IndexRange(curve_index, 1));
}
if (is_cyclic) {

View File

@@ -105,7 +105,7 @@ void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask)
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
/* Delete selection attribute so that it will not have to be resized. */
attributes.remove(".selection");
remove_selection_attributes(attributes);
curves.resize(old_points_num + num_points_to_add, old_curves_num + num_curves_to_add);
@@ -160,10 +160,12 @@ void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask)
curves.update_curve_types();
curves.tag_topology_changed();
bke::SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
".selection", bke::AttrDomain::Point);
selection.span.take_back(num_points_to_add).fill(true);
selection.finish();
for (const StringRef selection_name : get_curves_selection_attribute_names(curves)) {
bke::SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
selection_name, bke::AttrDomain::Point);
selection.span.take_back(num_points_to_add).fill(true);
selection.finish();
}
}
void duplicate_curves(bke::CurvesGeometry &curves, const IndexMask &mask)
@@ -173,7 +175,7 @@ void duplicate_curves(bke::CurvesGeometry &curves, const IndexMask &mask)
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
/* Delete selection attribute so that it will not have to be resized. */
attributes.remove(".selection");
remove_selection_attributes(attributes);
/* Resize the curves and copy the offsets of duplicated curves into the new offsets. */
curves.resize(curves.points_num(), orig_curves_num + mask.size());
@@ -215,10 +217,12 @@ void duplicate_curves(bke::CurvesGeometry &curves, const IndexMask &mask)
curves.update_curve_types();
curves.tag_topology_changed();
bke::SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
".selection", bke::AttrDomain::Curve);
selection.span.take_back(mask.size()).fill(true);
selection.finish();
for (const StringRef selection_name : get_curves_selection_attribute_names(curves)) {
bke::SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
selection_name, bke::AttrDomain::Curve);
selection.span.take_back(mask.size()).fill(true);
selection.finish();
}
}
} // namespace blender::ed::curves

View File

@@ -806,26 +806,29 @@ static int curves_set_selection_domain_exec(bContext *C, wmOperator *op)
*
* This would be unnecessary if the active attribute were stored as a string on the ID. */
std::string active_attribute;
if (const CustomDataLayer *layer = BKE_id_attributes_active_get(&curves_id->id)) {
const CustomDataLayer *layer = BKE_id_attributes_active_get(&curves_id->id);
if (layer) {
active_attribute = layer->name;
}
for (const StringRef selection_name : get_curves_selection_attribute_names(curves)) {
if (const GVArray src = *attributes.lookup(selection_name, domain)) {
const CPPType &type = src.type();
void *dst = MEM_malloc_arrayN(attributes.domain_size(domain), type.size(), __func__);
src.materialize(dst);
if (const GVArray src = *attributes.lookup(".selection", domain)) {
const CPPType &type = src.type();
void *dst = MEM_malloc_arrayN(attributes.domain_size(domain), type.size(), __func__);
src.materialize(dst);
attributes.remove(".selection");
if (!attributes.add(".selection",
domain,
bke::cpp_type_to_custom_data_type(type),
bke::AttributeInitMoveArray(dst)))
{
MEM_freeN(dst);
attributes.remove(selection_name);
if (!attributes.add(selection_name,
domain,
bke::cpp_type_to_custom_data_type(type),
bke::AttributeInitMoveArray(dst)))
{
MEM_freeN(dst);
}
}
}
BKE_id_attributes_active_set(&curves_id->id, active_attribute.c_str());
if (!active_attribute.empty()) {
BKE_id_attributes_active_set(&curves_id->id, active_attribute.c_str());
}
/* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
* attribute for now. */

View File

@@ -62,9 +62,16 @@ IndexMask retrieve_selected_curves(const Curves &curves_id, IndexMaskMemory &mem
}
IndexMask retrieve_selected_points(const bke::CurvesGeometry &curves, IndexMaskMemory &memory)
{
return retrieve_selected_points(curves, ".selection", memory);
}
IndexMask retrieve_selected_points(const bke::CurvesGeometry &curves,
StringRef attribute_name,
IndexMaskMemory &memory)
{
return IndexMask::from_bools(
*curves.attributes().lookup_or_default<bool>(".selection", bke::AttrDomain::Point, true),
*curves.attributes().lookup_or_default<bool>(attribute_name, bke::AttrDomain::Point, true),
memory);
}
@@ -74,30 +81,187 @@ IndexMask retrieve_selected_points(const Curves &curves_id, IndexMaskMemory &mem
return retrieve_selected_points(curves, memory);
}
Span<StringRef> get_curves_selection_attribute_names(const bke::CurvesGeometry &curves)
{
static const std::array<StringRef, 1> selection_attribute_names{".selection"};
const bke::AttributeAccessor attributes = curves.attributes();
return (attributes.contains("handle_type_left") && attributes.contains("handle_type_right")) ?
get_curves_all_selection_attribute_names() :
selection_attribute_names;
}
Span<StringRef> get_curves_all_selection_attribute_names()
{
static const std::array<StringRef, 3> selection_attribute_names{
".selection", ".selection_handle_left", ".selection_handle_right"};
return selection_attribute_names;
}
Span<StringRef> get_curves_bezier_selection_attribute_names(const bke::CurvesGeometry &curves)
{
static const std::array<StringRef, 2> selection_attribute_names{".selection_handle_left",
".selection_handle_right"};
const bke::AttributeAccessor attributes = curves.attributes();
return (attributes.contains("handle_type_left") && attributes.contains("handle_type_right")) ?
selection_attribute_names :
Span<StringRef>();
}
void remove_selection_attributes(bke::MutableAttributeAccessor &attributes,
Span<StringRef> selection_attribute_names)
{
for (const StringRef selection_name : selection_attribute_names) {
attributes.remove(selection_name);
}
}
static Vector<bke::GSpanAttributeWriter> init_selection_writers(bke::CurvesGeometry &curves,
bke::AttrDomain selection_domain)
{
const eCustomDataType create_type = CD_PROP_BOOL;
Span<StringRef> selection_attribute_names = get_curves_selection_attribute_names(curves);
Vector<bke::GSpanAttributeWriter> writers;
for (const int i : selection_attribute_names.index_range()) {
writers.append(ensure_selection_attribute(
curves, selection_domain, create_type, selection_attribute_names[i]));
};
return writers;
}
static void finish_attribute_writers(MutableSpan<bke::GSpanAttributeWriter> attribute_writers)
{
for (auto &attribute_writer : attribute_writers) {
attribute_writer.finish();
}
}
static bke::GSpanAttributeWriter &selection_attribute_writer_by_name(
MutableSpan<bke::GSpanAttributeWriter> selections, StringRef attribute_name)
{
Span<StringRef> selection_attribute_names = get_curves_all_selection_attribute_names();
BLI_assert(selection_attribute_names.contains(attribute_name));
for (const int index : selections.index_range()) {
if (attribute_name.size() == selection_attribute_names[index].size()) {
return selections[index];
}
}
BLI_assert_unreachable();
return selections[0];
}
void foreach_selection_attribute_writer(
bke::CurvesGeometry &curves,
bke::AttrDomain selection_domain,
blender::FunctionRef<void(bke::GSpanAttributeWriter &selection)> fn)
{
Vector<bke::GSpanAttributeWriter> selection_writers = init_selection_writers(curves,
selection_domain);
for (bke::GSpanAttributeWriter &selection_writer : selection_writers) {
fn(selection_writer);
}
finish_attribute_writers(selection_writers);
}
static void init_selectable_foreach(const bke::CurvesGeometry &curves,
const bke::crazyspace::GeometryDeformation &deformation,
Span<StringRef> &r_bezier_attribute_names,
Span<float3> &r_positions,
std::array<Span<float3>, 2> &r_bezier_handle_positions,
IndexMaskMemory &r_memory,
IndexMask &r_bezier_curves)
{
r_bezier_attribute_names = get_curves_bezier_selection_attribute_names(curves);
r_positions = deformation.positions;
if (r_bezier_attribute_names.size() > 0) {
r_bezier_handle_positions[0] = curves.handle_positions_left();
r_bezier_handle_positions[1] = curves.handle_positions_right();
r_bezier_curves = curves.indices_for_curve_type(CURVE_TYPE_BEZIER, r_memory);
}
}
void foreach_selectable_point_range(const bke::CurvesGeometry &curves,
const bke::crazyspace::GeometryDeformation &deformation,
SelectionRangeFn range_consumer)
{
Span<StringRef> bezier_attribute_names;
Span<float3> positions;
std::array<Span<float3>, 2> bezier_handle_positions;
IndexMaskMemory memory;
IndexMask bezier_curves;
init_selectable_foreach(curves,
deformation,
bezier_attribute_names,
positions,
bezier_handle_positions,
memory,
bezier_curves);
range_consumer(curves.points_range(), positions, ".selection");
OffsetIndices<int> points_by_curve = curves.points_by_curve();
for (const int attribute_i : bezier_attribute_names.index_range()) {
bezier_curves.foreach_index(GrainSize(512), [&](const int64_t curve_i) {
range_consumer(points_by_curve[curve_i],
bezier_handle_positions[attribute_i],
bezier_attribute_names[attribute_i]);
});
}
}
void foreach_selectable_curve_range(const bke::CurvesGeometry &curves,
const bke::crazyspace::GeometryDeformation &deformation,
SelectionRangeFn range_consumer)
{
Span<StringRef> bezier_attribute_names;
Span<float3> positions;
std::array<Span<float3>, 2> bezier_handle_positions;
IndexMaskMemory memory;
IndexMask bezier_curves;
init_selectable_foreach(curves,
deformation,
bezier_attribute_names,
positions,
bezier_handle_positions,
memory,
bezier_curves);
range_consumer(curves.curves_range(), positions, ".selection");
for (const int attribute_i : bezier_attribute_names.index_range()) {
bezier_curves.foreach_range([&](const IndexRange curves_range) {
range_consumer(
curves_range, bezier_handle_positions[attribute_i], bezier_attribute_names[attribute_i]);
});
}
}
bke::GSpanAttributeWriter ensure_selection_attribute(bke::CurvesGeometry &curves,
const bke::AttrDomain selection_domain,
const eCustomDataType create_type)
bke::AttrDomain selection_domain,
eCustomDataType create_type,
StringRef attribute_name)
{
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
if (attributes.contains(".selection")) {
bke::GSpanAttributeWriter selection_attr = attributes.lookup_for_write_span(".selection");
if (attributes.contains(attribute_name)) {
bke::GSpanAttributeWriter selection_attr = attributes.lookup_for_write_span(attribute_name);
/* Check domain type. */
if (selection_attr.domain == selection_domain) {
return selection_attr;
}
selection_attr.finish();
attributes.remove(".selection");
attributes.remove(attribute_name);
}
const int domain_size = attributes.domain_size(selection_domain);
switch (create_type) {
case CD_PROP_BOOL:
attributes.add(".selection",
attributes.add(attribute_name,
selection_domain,
CD_PROP_BOOL,
bke::AttributeInitVArray(VArray<bool>::ForSingle(true, domain_size)));
break;
case CD_PROP_FLOAT:
attributes.add(".selection",
attributes.add(attribute_name,
selection_domain,
CD_PROP_FLOAT,
bke::AttributeInitVArray(VArray<float>::ForSingle(1.0f, domain_size)));
@@ -105,7 +269,7 @@ bke::GSpanAttributeWriter ensure_selection_attribute(bke::CurvesGeometry &curves
default:
BLI_assert_unreachable();
}
return attributes.lookup_for_write_span(".selection");
return attributes.lookup_for_write_span(attribute_name);
}
void fill_selection_false(GMutableSpan selection)
@@ -239,6 +403,17 @@ bool has_anything_selected(const bke::CurvesGeometry &curves)
return !selection || contains(selection, selection.index_range(), true);
}
bool has_anything_selected(const bke::CurvesGeometry &curves, bke::AttrDomain selection_domain)
{
for (const StringRef selection_name : get_curves_selection_attribute_names(curves)) {
const VArray<bool> selection = *curves.attributes().lookup<bool>(selection_name,
selection_domain);
if (!selection || contains(selection, selection.index_range(), true))
return true;
}
return false;
}
bool has_anything_selected(const bke::CurvesGeometry &curves, const IndexMask &mask)
{
const VArray<bool> selection = *curves.attributes().lookup<bool>(".selection");
@@ -300,29 +475,29 @@ void select_all(bke::CurvesGeometry &curves,
const bke::AttrDomain selection_domain,
int action)
{
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
if (action == SEL_SELECT) {
std::optional<IndexRange> range = mask.to_range();
if (range.has_value() &&
(*range == IndexRange(curves.attributes().domain_size(selection_domain))))
{
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
/* As an optimization, just remove the selection attributes when everything is selected. */
attributes.remove(".selection");
remove_selection_attributes(attributes);
return;
}
}
bke::GSpanAttributeWriter selection = ensure_selection_attribute(
curves, selection_domain, CD_PROP_BOOL);
if (action == SEL_SELECT) {
fill_selection_true(selection.span, mask);
}
else if (action == SEL_DESELECT) {
fill_selection_false(selection.span, mask);
}
else if (action == SEL_INVERT) {
invert_selection(selection.span, mask);
}
selection.finish();
foreach_selection_attribute_writer(
curves, selection_domain, [&](bke::GSpanAttributeWriter &selection) {
if (action == SEL_SELECT) {
fill_selection_true(selection.span, mask);
}
else if (action == SEL_DESELECT) {
fill_selection_false(selection.span, mask);
}
else if (action == SEL_INVERT) {
invert_selection(selection.span, mask);
}
});
}
void select_all(bke::CurvesGeometry &curves, const bke::AttrDomain selection_domain, int action)
@@ -334,17 +509,31 @@ void select_all(bke::CurvesGeometry &curves, const bke::AttrDomain selection_dom
void select_linked(bke::CurvesGeometry &curves, const IndexMask &curves_mask)
{
const OffsetIndices points_by_curve = curves.points_by_curve();
bke::GSpanAttributeWriter selection = ensure_selection_attribute(
curves, bke::AttrDomain::Point, CD_PROP_BOOL);
const VArray<int8_t> curve_types = curves.curve_types();
Vector<bke::GSpanAttributeWriter> selection_writers = init_selection_writers(
curves, bke::AttrDomain::Point);
curves_mask.foreach_index(GrainSize(256), [&](const int64_t curve_i) {
GMutableSpan selection_curve = selection.span.slice(points_by_curve[curve_i]);
if (has_anything_selected(selection_curve)) {
fill_selection_true(selection_curve);
for (const int i : selection_writers.index_range()) {
bke::GSpanAttributeWriter &selection = selection_writers[i];
GMutableSpan selection_curve = selection.span.slice(points_by_curve[curve_i]);
if (has_anything_selected(selection_curve)) {
fill_selection_true(selection_curve);
for (const int j : selection_writers.index_range()) {
if (j == i) {
continue;
}
fill_selection_true(selection_writers[j].span.slice(points_by_curve[curve_i]));
}
return;
}
if (curve_types[curve_i] != CURVE_TYPE_BEZIER) {
return;
}
}
});
selection.finish();
finish_attribute_writers(selection_writers);
}
void select_linked(bke::CurvesGeometry &curves)
@@ -695,68 +884,89 @@ std::optional<FindClosestData> closest_elem_find_screen_space(
bool select_box(const ViewContext &vc,
bke::CurvesGeometry &curves,
const Span<float3> positions,
const bke::crazyspace::GeometryDeformation &deformation,
const float4x4 &projection,
const IndexMask &mask,
const bke::AttrDomain selection_domain,
const rcti &rect,
const eSelectOp sel_op)
{
bke::GSpanAttributeWriter selection = ensure_selection_attribute(
curves, selection_domain, CD_PROP_BOOL);
Vector<bke::GSpanAttributeWriter> selection_writers = init_selection_writers(curves,
selection_domain);
bool changed = false;
if (sel_op == SEL_OP_SET) {
fill_selection_false(selection.span, mask);
for (bke::GSpanAttributeWriter &selection : selection_writers) {
fill_selection_false(selection.span, mask);
};
changed = true;
}
const OffsetIndices points_by_curve = curves.points_by_curve();
if (selection_domain == bke::AttrDomain::Point) {
mask.foreach_index(GrainSize(1024), [&](const int point_i) {
const float2 pos_proj = ED_view3d_project_float_v2_m4(
vc.region, positions[point_i], projection);
if (BLI_rcti_isect_pt_v(&rect, int2(pos_proj))) {
apply_selection_operation_at_index(selection.span, point_i, sel_op);
changed = true;
}
});
foreach_selectable_point_range(
curves,
deformation,
[&](IndexRange range, Span<float3> positions, StringRef selection_attribute_name) {
mask.slice_content(range).foreach_index(GrainSize(1024), [&](const int point_i) {
const float2 pos_proj = ED_view3d_project_float_v2_m4(
vc.region, positions[point_i], projection);
if (BLI_rcti_isect_pt_v(&rect, int2(pos_proj))) {
apply_selection_operation_at_index(
selection_attribute_writer_by_name(selection_writers, selection_attribute_name)
.span,
point_i,
sel_op);
changed = true;
}
});
});
}
else if (selection_domain == bke::AttrDomain::Curve) {
mask.foreach_index(GrainSize(512), [&](const int curve_i) {
const IndexRange points = points_by_curve[curve_i];
if (points.size() == 1) {
const float2 pos_proj = ED_view3d_project_float_v2_m4(
vc.region, positions[points.first()], projection);
if (BLI_rcti_isect_pt_v(&rect, int2(pos_proj))) {
apply_selection_operation_at_index(selection.span, curve_i, sel_op);
changed = true;
}
return;
}
for (const int segment_i : points.drop_back(1)) {
const float3 pos1 = positions[segment_i];
const float3 pos2 = positions[segment_i + 1];
const OffsetIndices points_by_curve = curves.points_by_curve();
foreach_selectable_curve_range(
curves,
deformation,
[&](const IndexRange range,
const Span<float3> positions,
StringRef /* selection_attribute_name */) {
mask.slice_content(range).foreach_index(GrainSize(512), [&](const int curve_i) {
const IndexRange points = points_by_curve[curve_i];
if (points.size() == 1) {
const float2 pos_proj = ED_view3d_project_float_v2_m4(
vc.region, positions[points.first()], projection);
if (BLI_rcti_isect_pt_v(&rect, int2(pos_proj))) {
for (bke::GSpanAttributeWriter &selection : selection_writers) {
apply_selection_operation_at_index(selection.span, curve_i, sel_op);
};
changed = true;
}
return;
}
for (const int segment_i : points.drop_back(1)) {
const float3 pos1 = positions[segment_i];
const float3 pos2 = positions[segment_i + 1];
const float2 pos1_proj = ED_view3d_project_float_v2_m4(vc.region, pos1, projection);
const float2 pos2_proj = ED_view3d_project_float_v2_m4(vc.region, pos2, projection);
const float2 pos1_proj = ED_view3d_project_float_v2_m4(vc.region, pos1, projection);
const float2 pos2_proj = ED_view3d_project_float_v2_m4(vc.region, pos2, projection);
if (BLI_rcti_isect_segment(&rect, int2(pos1_proj), int2(pos2_proj))) {
apply_selection_operation_at_index(selection.span, curve_i, sel_op);
changed = true;
break;
}
}
});
if (BLI_rcti_isect_segment(&rect, int2(pos1_proj), int2(pos2_proj))) {
for (bke::GSpanAttributeWriter &selection : selection_writers) {
apply_selection_operation_at_index(selection.span, curve_i, sel_op);
};
changed = true;
break;
}
}
});
});
}
selection.finish();
finish_attribute_writers(selection_writers);
return changed;
}
bool select_lasso(const ViewContext &vc,
bke::CurvesGeometry &curves,
const Span<float3> positions,
const bke::crazyspace::GeometryDeformation &deformation,
const float4x4 &projection_matrix,
const IndexMask &mask,
const bke::AttrDomain selection_domain,
@@ -765,76 +975,99 @@ bool select_lasso(const ViewContext &vc,
{
rcti bbox;
BLI_lasso_boundbox(&bbox, lasso_coords);
bke::GSpanAttributeWriter selection = ensure_selection_attribute(
curves, selection_domain, CD_PROP_BOOL);
Vector<bke::GSpanAttributeWriter> selection_writers = init_selection_writers(curves,
selection_domain);
bool changed = false;
if (sel_op == SEL_OP_SET) {
fill_selection_false(selection.span, mask);
for (bke::GSpanAttributeWriter &selection : selection_writers) {
fill_selection_false(selection.span, mask);
};
changed = true;
}
const OffsetIndices points_by_curve = curves.points_by_curve();
if (selection_domain == bke::AttrDomain::Point) {
mask.foreach_index(GrainSize(1024), [&](const int point_i) {
const float2 pos_proj = ED_view3d_project_float_v2_m4(
vc.region, positions[point_i], projection_matrix);
/* Check the lasso bounding box first as an optimization. */
if (BLI_rcti_isect_pt_v(&bbox, int2(pos_proj)) &&
BLI_lasso_is_point_inside(lasso_coords, int(pos_proj.x), int(pos_proj.y), IS_CLIPPED))
{
apply_selection_operation_at_index(selection.span, point_i, sel_op);
changed = true;
}
});
foreach_selectable_point_range(
curves,
deformation,
[&](IndexRange range, Span<float3> positions, StringRef selection_attribute_name) {
mask.slice_content(range).foreach_index(GrainSize(1024), [&](const int point_i) {
const float2 pos_proj = ED_view3d_project_float_v2_m4(
vc.region, positions[point_i], projection_matrix);
/* Check the lasso bounding box first as an optimization. */
if (BLI_rcti_isect_pt_v(&bbox, int2(pos_proj)) &&
BLI_lasso_is_point_inside(
lasso_coords, int(pos_proj.x), int(pos_proj.y), IS_CLIPPED))
{
apply_selection_operation_at_index(
selection_attribute_writer_by_name(selection_writers, selection_attribute_name)
.span,
point_i,
sel_op);
changed = true;
}
});
});
}
else if (selection_domain == bke::AttrDomain::Curve) {
mask.foreach_index(GrainSize(512), [&](const int curve_i) {
const IndexRange points = points_by_curve[curve_i];
if (points.size() == 1) {
const float2 pos_proj = ED_view3d_project_float_v2_m4(
vc.region, positions[points.first()], projection_matrix);
/* Check the lasso bounding box first as an optimization. */
if (BLI_rcti_isect_pt_v(&bbox, int2(pos_proj)) &&
BLI_lasso_is_point_inside(lasso_coords, int(pos_proj.x), int(pos_proj.y), IS_CLIPPED))
{
apply_selection_operation_at_index(selection.span, curve_i, sel_op);
changed = true;
}
return;
}
for (const int segment_i : points.drop_back(1)) {
const float3 pos1 = positions[segment_i];
const float3 pos2 = positions[segment_i + 1];
const OffsetIndices points_by_curve = curves.points_by_curve();
foreach_selectable_curve_range(
curves,
deformation,
[&](const IndexRange range,
const Span<float3> positions,
StringRef /* selection_attribute_name */) {
mask.slice_content(range).foreach_index(GrainSize(512), [&](const int curve_i) {
const IndexRange points = points_by_curve[curve_i];
if (points.size() == 1) {
const float2 pos_proj = ED_view3d_project_float_v2_m4(
vc.region, positions[points.first()], projection_matrix);
/* Check the lasso bounding box first as an optimization. */
if (BLI_rcti_isect_pt_v(&bbox, int2(pos_proj)) &&
BLI_lasso_is_point_inside(
lasso_coords, int(pos_proj.x), int(pos_proj.y), IS_CLIPPED))
{
for (bke::GSpanAttributeWriter &selection : selection_writers) {
apply_selection_operation_at_index(selection.span, curve_i, sel_op);
}
changed = true;
}
return;
}
for (const int segment_i : points.drop_back(1)) {
const float3 pos1 = positions[segment_i];
const float3 pos2 = positions[segment_i + 1];
const float2 pos1_proj = ED_view3d_project_float_v2_m4(vc.region, pos1, projection_matrix);
const float2 pos2_proj = ED_view3d_project_float_v2_m4(vc.region, pos2, projection_matrix);
const float2 pos1_proj = ED_view3d_project_float_v2_m4(
vc.region, pos1, projection_matrix);
const float2 pos2_proj = ED_view3d_project_float_v2_m4(
vc.region, pos2, projection_matrix);
/* Check the lasso bounding box first as an optimization. */
if (BLI_rcti_isect_segment(&bbox, int2(pos1_proj), int2(pos2_proj)) &&
BLI_lasso_is_edge_inside(lasso_coords,
int(pos1_proj.x),
int(pos1_proj.y),
int(pos2_proj.x),
int(pos2_proj.y),
IS_CLIPPED))
{
apply_selection_operation_at_index(selection.span, curve_i, sel_op);
changed = true;
break;
}
}
});
/* Check the lasso bounding box first as an optimization. */
if (BLI_rcti_isect_segment(&bbox, int2(pos1_proj), int2(pos2_proj)) &&
BLI_lasso_is_edge_inside(lasso_coords,
int(pos1_proj.x),
int(pos1_proj.y),
int(pos2_proj.x),
int(pos2_proj.y),
IS_CLIPPED))
{
for (bke::GSpanAttributeWriter &selection : selection_writers) {
apply_selection_operation_at_index(selection.span, curve_i, sel_op);
}
changed = true;
break;
}
}
});
});
}
selection.finish();
finish_attribute_writers(selection_writers);
return changed;
}
bool select_circle(const ViewContext &vc,
bke::CurvesGeometry &curves,
const Span<float3> positions,
const bke::crazyspace::GeometryDeformation &deformation,
const float4x4 &projection,
const IndexMask &mask,
const bke::AttrDomain selection_domain,
@@ -843,57 +1076,77 @@ bool select_circle(const ViewContext &vc,
const eSelectOp sel_op)
{
const float radius_sq = pow2f(radius);
bke::GSpanAttributeWriter selection = ensure_selection_attribute(
curves, selection_domain, CD_PROP_BOOL);
Vector<bke::GSpanAttributeWriter> selection_writers = init_selection_writers(curves,
selection_domain);
bool changed = false;
if (sel_op == SEL_OP_SET) {
fill_selection_false(selection.span, mask);
for (bke::GSpanAttributeWriter &selection : selection_writers) {
fill_selection_false(selection.span, mask);
};
changed = true;
}
const OffsetIndices points_by_curve = curves.points_by_curve();
if (selection_domain == bke::AttrDomain::Point) {
mask.foreach_index(GrainSize(1024), [&](const int point_i) {
const float2 pos_proj = ED_view3d_project_float_v2_m4(
vc.region, positions[point_i], projection);
if (math::distance_squared(pos_proj, float2(coord)) <= radius_sq) {
apply_selection_operation_at_index(selection.span, point_i, sel_op);
changed = true;
}
});
foreach_selectable_point_range(
curves,
deformation,
[&](IndexRange range, Span<float3> positions, StringRef selection_attribute_name) {
mask.slice_content(range).foreach_index(GrainSize(1024), [&](const int point_i) {
const float2 pos_proj = ED_view3d_project_float_v2_m4(
vc.region, positions[point_i], projection);
if (math::distance_squared(pos_proj, float2(coord)) <= radius_sq) {
apply_selection_operation_at_index(
selection_attribute_writer_by_name(selection_writers, selection_attribute_name)
.span,
point_i,
sel_op);
changed = true;
}
});
});
}
else if (selection_domain == bke::AttrDomain::Curve) {
mask.foreach_index(GrainSize(512), [&](const int curve_i) {
const IndexRange points = points_by_curve[curve_i];
if (points.size() == 1) {
const float2 pos_proj = ED_view3d_project_float_v2_m4(
vc.region, positions[points.first()], projection);
if (math::distance_squared(pos_proj, float2(coord)) <= radius_sq) {
apply_selection_operation_at_index(selection.span, curve_i, sel_op);
changed = true;
}
return;
}
for (const int segment_i : points.drop_back(1)) {
const float3 pos1 = positions[segment_i];
const float3 pos2 = positions[segment_i + 1];
const OffsetIndices points_by_curve = curves.points_by_curve();
foreach_selectable_curve_range(
curves,
deformation,
[&](const IndexRange range,
const Span<float3> positions,
StringRef /* selection_attribute_name */) {
mask.slice_content(range).foreach_index(GrainSize(512), [&](const int curve_i) {
const IndexRange points = points_by_curve[curve_i];
if (points.size() == 1) {
const float2 pos_proj = ED_view3d_project_float_v2_m4(
vc.region, positions[points.first()], projection);
if (math::distance_squared(pos_proj, float2(coord)) <= radius_sq) {
for (bke::GSpanAttributeWriter &selection : selection_writers) {
apply_selection_operation_at_index(selection.span, curve_i, sel_op);
}
changed = true;
}
return;
}
for (const int segment_i : points.drop_back(1)) {
const float3 pos1 = positions[segment_i];
const float3 pos2 = positions[segment_i + 1];
const float2 pos1_proj = ED_view3d_project_float_v2_m4(vc.region, pos1, projection);
const float2 pos2_proj = ED_view3d_project_float_v2_m4(vc.region, pos2, projection);
const float2 pos1_proj = ED_view3d_project_float_v2_m4(vc.region, pos1, projection);
const float2 pos2_proj = ED_view3d_project_float_v2_m4(vc.region, pos2, projection);
const float distance_proj_sq = dist_squared_to_line_segment_v2(
float2(coord), pos1_proj, pos2_proj);
if (distance_proj_sq <= radius_sq) {
apply_selection_operation_at_index(selection.span, curve_i, sel_op);
changed = true;
break;
}
}
});
const float distance_proj_sq = dist_squared_to_line_segment_v2(
float2(coord), pos1_proj, pos2_proj);
if (distance_proj_sq <= radius_sq) {
for (bke::GSpanAttributeWriter &selection : selection_writers) {
apply_selection_operation_at_index(selection.span, curve_i, sel_op);
}
changed = true;
break;
}
}
});
});
}
selection.finish();
finish_attribute_writers(selection_writers);
return changed;
}

View File

@@ -44,6 +44,52 @@ void keymap_curves(wmKeyConfig *keyconf);
*/
float (*point_normals_array_create(const Curves *curves_id))[3];
/**
* Get selection attribute names need for given curve.
* Possible outcomes: [".selection"] if Bezier curves are present,
* [".selection", ".selection_handle_left", ".selection_handle_right"] otherwise. */
Span<StringRef> get_curves_selection_attribute_names(const bke::CurvesGeometry &curves);
/* Get all possible curve selection attribute names. */
Span<StringRef> get_curves_all_selection_attribute_names();
/**
* Returns [".selection_handle_left", ".selection_handle_right"] if argument contains Bezier
* curves, empty span otherwise.
*/
Span<StringRef> get_curves_bezier_selection_attribute_names(const bke::CurvesGeometry &curves);
/**
* Used to select everything or to delete selection attribute so that it will not have to be
* resized.
*/
void remove_selection_attributes(
bke::MutableAttributeAccessor &attributes,
Span<StringRef> selection_attribute_names = get_curves_all_selection_attribute_names());
using SelectionRangeFn = FunctionRef<void(
IndexRange range, Span<float3> positions, StringRef selection_attribute_name)>;
/**
* Traverses all ranges of control points possible select. Callback function is provided with a
* range being visited, positions (deformed if possible) referenced by the range and selection
* attribute name positions belongs to:
* curves.positions() belong to ".selection",
* curves.handle_positions_left() belong to ".selection_handle_left",
* curves.handle_positions_right() belong to ".selection_handle_right".
*/
void foreach_selectable_point_range(const bke::CurvesGeometry &curves,
const bke::crazyspace::GeometryDeformation &deformation,
SelectionRangeFn range_consumer);
/**
* Same logic as in foreach_selectable_point_range, just ranges reference curves instead of
* positions directly. Futher positions can be referenced by using curves.points_by_curve() in a
* callback function.
*/
void foreach_selectable_curve_range(const bke::CurvesGeometry &curves,
const bke::crazyspace::GeometryDeformation &deformation,
SelectionRangeFn range_consumer);
bool object_has_editable_curves(const Main &bmain, const Object &object);
bke::CurvesGeometry primitive_random_sphere(int curves_size, int points_per_curve);
VectorSet<Curves *> get_unique_editable_curves(const bContext &C);
@@ -146,6 +192,7 @@ void fill_selection_true(GMutableSpan selection, const IndexMask &mask);
* Return true if any element is selected, on either domain with either type.
*/
bool has_anything_selected(const bke::CurvesGeometry &curves);
bool has_anything_selected(const bke::CurvesGeometry &curves, bke::AttrDomain selection_domain);
bool has_anything_selected(const bke::CurvesGeometry &curves, const IndexMask &mask);
/**
@@ -167,14 +214,23 @@ IndexMask retrieve_selected_curves(const Curves &curves_id, IndexMaskMemory &mem
* or points in curves with a selection factor greater than zero).
*/
IndexMask retrieve_selected_points(const bke::CurvesGeometry &curves, IndexMaskMemory &memory);
IndexMask retrieve_selected_points(const bke::CurvesGeometry &curves,
StringRef attribute_name,
IndexMaskMemory &memory);
IndexMask retrieve_selected_points(const Curves &curves_id, IndexMaskMemory &memory);
/**
* If the ".selection" attribute doesn't exist, create it with the requested type (bool or float).
* If the selection_id attribute doesn't exist, create it with the requested type (bool or float).
*/
bke::GSpanAttributeWriter ensure_selection_attribute(bke::CurvesGeometry &curves,
bke::AttrDomain selection_domain,
eCustomDataType create_type);
eCustomDataType create_type,
StringRef attribute_name = ".selection");
void foreach_selection_attribute_writer(
bke::CurvesGeometry &curves,
bke::AttrDomain selection_domain,
FunctionRef<void(bke::GSpanAttributeWriter &selection)> fn);
/** Apply a change to a single curve or point. Avoid using this when affecting many elements. */
void apply_selection_operation_at_index(GMutableSpan selection, int index, eSelectOp sel_op);
@@ -246,7 +302,7 @@ std::optional<FindClosestData> closest_elem_find_screen_space(const ViewContext
*/
bool select_box(const ViewContext &vc,
bke::CurvesGeometry &curves,
Span<float3> deformed_positions,
const bke::crazyspace::GeometryDeformation &deformation,
const float4x4 &projection,
const IndexMask &mask,
bke::AttrDomain selection_domain,
@@ -258,7 +314,7 @@ bool select_box(const ViewContext &vc,
*/
bool select_lasso(const ViewContext &vc,
bke::CurvesGeometry &curves,
Span<float3> deformed_positions,
const bke::crazyspace::GeometryDeformation &deformation,
const float4x4 &projection_matrix,
const IndexMask &mask,
bke::AttrDomain selection_domain,
@@ -270,7 +326,7 @@ bool select_lasso(const ViewContext &vc,
*/
bool select_circle(const ViewContext &vc,
bke::CurvesGeometry &curves,
Span<float3> deformed_positions,
const bke::crazyspace::GeometryDeformation &deformation,
const float4x4 &projection,
const IndexMask &mask,
bke::AttrDomain selection_domain,

View File

@@ -1210,7 +1210,7 @@ static bool do_lasso_select_grease_pencil(const ViewContext *vc,
const float4x4 projection = ED_view3d_ob_project_mat_get_from_obmat(vc->rv3d, layer_to_world);
changed = ed::curves::select_lasso(*vc,
info.drawing.strokes_for_write(),
deformation.positions,
deformation,
projection,
elements,
selection_domain,
@@ -1440,14 +1440,8 @@ static bool view3d_lasso_select(bContext *C,
const bke::AttrDomain selection_domain = bke::AttrDomain(curves_id.selection_domain);
const IndexRange elements(curves.attributes().domain_size(selection_domain));
const float4x4 projection = ED_view3d_ob_project_mat_get(vc->rv3d, vc->obedit);
changed = ed::curves::select_lasso(*vc,
curves,
deformation.positions,
projection,
elements,
selection_domain,
mcoords,
sel_op);
changed = ed::curves::select_lasso(
*vc, curves, deformation, projection, elements, selection_domain, mcoords, sel_op);
if (changed) {
/* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a
* generic attribute for now. */
@@ -3081,6 +3075,7 @@ static bool ed_wpaint_vertex_select_pick(bContext *C,
}
struct ClosestCurveDataBlock {
blender::StringRef selection_name;
Curves *curves_id = nullptr;
blender::ed::curves::FindClosestData elem = {};
};
@@ -3116,20 +3111,33 @@ static bool ed_curves_select_pick(bContext &C, const int mval[2], const SelectPi
bke::crazyspace::get_evaluated_curves_deformation(*vc.depsgraph, *vc.obedit);
const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
const float4x4 projection = ED_view3d_ob_project_mat_get(vc.rv3d, &curves_ob);
const IndexRange elements(curves.attributes().domain_size(selection_domain));
std::optional<ed::curves::FindClosestData> new_closest_elem =
ed::curves::closest_elem_find_screen_space(vc,
curves.points_by_curve(),
deformation.positions,
projection,
elements,
selection_domain,
mval,
new_closest.elem);
if (new_closest_elem) {
new_closest.elem = *new_closest_elem;
new_closest.curves_id = &curves_id;
const IndexMask elements(curves.attributes().domain_size(selection_domain));
const auto range_consumer =
[&](IndexRange range, Span<float3> positions, StringRef selection_attribute_name) {
IndexMask mask = elements.slice_content(range);
std::optional<ed::curves::FindClosestData> new_closest_elem =
ed::curves::closest_elem_find_screen_space(vc,
curves.points_by_curve(),
positions,
projection,
mask,
selection_domain,
mval,
new_closest.elem);
if (new_closest_elem) {
new_closest.selection_name = selection_attribute_name;
new_closest.elem = *new_closest_elem;
new_closest.curves_id = &curves_id;
}
};
if (selection_domain == bke::AttrDomain::Point) {
ed::curves::foreach_selectable_point_range(curves, deformation, range_consumer);
}
else if (selection_domain == bke::AttrDomain::Curve) {
ed::curves::foreach_selectable_curve_range(curves, deformation, range_consumer);
};
}
return new_closest;
},
@@ -3143,13 +3151,14 @@ static bool ed_curves_select_pick(bContext &C, const int mval[2], const SelectPi
for (Base *base : bases.as_span().slice(range)) {
Curves &curves_id = *static_cast<Curves *>(base->object->data);
bke::CurvesGeometry &curves = curves_id.geometry.wrap();
if (!ed::curves::has_anything_selected(curves)) {
if (!ed::curves::has_anything_selected(curves, selection_domain)) {
continue;
}
bke::GSpanAttributeWriter selection = ed::curves::ensure_selection_attribute(
curves, selection_domain, CD_PROP_BOOL);
ed::curves::fill_selection_false(selection.span);
selection.finish();
ed::curves::foreach_selection_attribute_writer(
curves, selection_domain, [](bke::GSpanAttributeWriter &selection) {
ed::curves::fill_selection_false(selection.span);
});
deselected = true;
/* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a
@@ -3164,11 +3173,25 @@ static bool ed_curves_select_pick(bContext &C, const int mval[2], const SelectPi
return deselected;
}
bke::GSpanAttributeWriter selection = ed::curves::ensure_selection_attribute(
closest.curves_id->geometry.wrap(), selection_domain, CD_PROP_BOOL);
ed::curves::apply_selection_operation_at_index(
selection.span, closest.elem.index, params.sel_op);
selection.finish();
if (selection_domain == bke::AttrDomain::Point) {
bke::GSpanAttributeWriter selection = ed::curves::ensure_selection_attribute(
closest.curves_id->geometry.wrap(),
bke::AttrDomain::Point,
CD_PROP_BOOL,
closest.selection_name);
ed::curves::apply_selection_operation_at_index(
selection.span, closest.elem.index, params.sel_op);
selection.finish();
}
else if (selection_domain == bke::AttrDomain::Curve) {
ed::curves::foreach_selection_attribute_writer(
closest.curves_id->geometry.wrap(),
bke::AttrDomain::Curve,
[&](bke::GSpanAttributeWriter &selection) {
ed::curves::apply_selection_operation_at_index(
selection.span, closest.elem.index, params.sel_op);
});
}
/* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a
* generic attribute for now. */
@@ -4245,7 +4268,7 @@ static bool do_grease_pencil_box_select(const ViewContext *vc,
const float4x4 projection = ED_view3d_ob_project_mat_get_from_obmat(vc->rv3d, layer_to_world);
changed |= ed::curves::select_box(*vc,
info.drawing.strokes_for_write(),
deformation.positions,
deformation,
projection,
elements,
selection_domain,
@@ -4334,14 +4357,8 @@ static int view3d_box_select_exec(bContext *C, wmOperator *op)
const bke::AttrDomain selection_domain = bke::AttrDomain(curves_id.selection_domain);
const float4x4 projection = ED_view3d_ob_project_mat_get(vc.rv3d, vc.obedit);
const IndexRange elements(curves.attributes().domain_size(selection_domain));
changed = ed::curves::select_box(vc,
curves,
deformation.positions,
projection,
elements,
selection_domain,
rect,
sel_op);
changed = ed::curves::select_box(
vc, curves, deformation, projection, elements, selection_domain, rect, sel_op);
if (changed) {
/* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a
* generic attribute for now. */
@@ -5113,7 +5130,7 @@ static bool grease_pencil_circle_select(const ViewContext *vc,
const float4x4 projection = ED_view3d_ob_project_mat_get_from_obmat(vc->rv3d, layer_to_world);
changed = ed::curves::select_circle(*vc,
info.drawing.strokes_for_write(),
deformation.positions,
deformation,
projection,
elements,
selection_domain,
@@ -5173,15 +5190,8 @@ static bool obedit_circle_select(bContext *C,
const bke::AttrDomain selection_domain = bke::AttrDomain(curves_id.selection_domain);
const float4x4 projection = ED_view3d_ob_project_mat_get(vc->rv3d, vc->obedit);
const IndexRange elements(curves.attributes().domain_size(selection_domain));
changed = ed::curves::select_circle(*vc,
curves,
deformation.positions,
projection,
elements,
selection_domain,
mval,
rad,
sel_op);
changed = ed::curves::select_circle(
*vc, curves, deformation, projection, elements, selection_domain, mval, rad, sel_op);
if (changed) {
/* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a
* generic attribute for now. */