Curves: Add select more/less #104626

Merged
Falk David merged 7 commits from filedescriptor/blender:curves-select-more-less into main 2023-02-16 17:02:54 +01:00
3 changed files with 151 additions and 40 deletions
Showing only changes of commit 9119c22d28 - Show all commits

View File

@ -1008,6 +1008,60 @@ static void CURVES_OT_select_linked(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
static int select_more_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();
select_adjacent(curves, false);
/* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
* attribute for now. */
DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id);
}
return OPERATOR_FINISHED;
}
static void CURVES_OT_select_more(wmOperatorType *ot)
{
ot->name = "Select More";
ot->idname = __func__;
ot->description = "Grow the selection by one point.";
ot->exec = select_more_exec;
ot->poll = editable_curves_point_domain_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
static int select_less_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();
select_adjacent(curves, true);
/* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
* attribute for now. */
DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id);
}
return OPERATOR_FINISHED;
}
static void CURVES_OT_select_less(wmOperatorType *ot)
{
ot->name = "Select Less";
ot->idname = __func__;
ot->description = "Shrink the selection by one point";
ot->exec = select_less_exec;
ot->poll = editable_curves_point_domain_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
namespace surface_set {
static bool surface_set_poll(bContext *C)
@ -1133,6 +1187,8 @@ void ED_operatortypes_curves()
WM_operatortype_append(CURVES_OT_select_random);
WM_operatortype_append(CURVES_OT_select_end);
WM_operatortype_append(CURVES_OT_select_linked);
WM_operatortype_append(CURVES_OT_select_more);
WM_operatortype_append(CURVES_OT_select_less);
WM_operatortype_append(CURVES_OT_surface_set);
WM_operatortype_append(CURVES_OT_delete);
}

View File

@ -199,6 +199,46 @@ static void invert_selection(GMutableSpan selection)
}
}
static void apply_selection_operation_at_index(GMutableSpan selection,
filedescriptor marked this conversation as resolved
Review

Looks like this doesn't need to move in this patch anymore? It looks like the type could still be abstracted, but duplicating things twice isn't so bad in return for making the type-interaction more obvious.

Looks like this doesn't need to move in this patch anymore? It looks like the type could still be abstracted, but duplicating things twice isn't so bad in return for making the type-interaction more obvious.
const int index,
const eSelectOp sel_op)
{
if (selection.type().is<bool>()) {
MutableSpan<bool> selection_typed = selection.typed<bool>();
switch (sel_op) {
case SEL_OP_ADD:
case SEL_OP_SET:
selection_typed[index] = true;
break;
case SEL_OP_SUB:
selection_typed[index] = false;
break;
case SEL_OP_XOR:
selection_typed[index] = !selection_typed[index];
break;
default:
break;
}
}
else if (selection.type().is<float>()) {
MutableSpan<float> selection_typed = selection.typed<float>();
switch (sel_op) {
case SEL_OP_ADD:
case SEL_OP_SET:
selection_typed[index] = 1.0f;
break;
case SEL_OP_SUB:
selection_typed[index] = 0.0f;
break;
case SEL_OP_XOR:
selection_typed[index] = 1.0f - selection_typed[index];
break;
default:
break;
}
}
}
void select_all(bke::CurvesGeometry &curves, const eAttrDomain selection_domain, int action)
{
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
@ -267,6 +307,56 @@ void select_linked(bke::CurvesGeometry &curves)
selection.finish();
}
static bool check_adjacent_selection(GMutableSpan selection, const int current, const int next)
{
if (selection.type().is<bool>()) {
MutableSpan<bool> selection_typed = selection.typed<bool>();
return !selection_typed[current] && selection_typed[next];
}
else if (selection.type().is<float>()) {
filedescriptor marked this conversation as resolved
Review

else after return

else after return
MutableSpan<float> selection_typed = selection.typed<float>();
return (selection_typed[current] == 0.0f) && (selection_typed[next] > 0.0f);
}
return false;
}
void select_adjacent(bke::CurvesGeometry &curves, const bool deselect)
{
const OffsetIndices points_by_curve = curves.points_by_curve();
bke::GSpanAttributeWriter selection = ensure_selection_attribute(
filedescriptor marked this conversation as resolved
Review

I think a local const IndexRange points variable to hold the curve points range would make this slightly more readable.

I think a local `const IndexRange points` variable to hold the curve points range would make this slightly more readable.
curves, ATTR_DOMAIN_POINT, CD_PROP_BOOL);
if (deselect) {
invert_selection(selection.span);
}
threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange range) {
for (const int curve_i : range) {
GMutableSpan selection_curve = selection.span.slice(points_by_curve[curve_i]);
/* Handle all cases in the forward direction. */
for (int point_i = 0; point_i < points_by_curve.size(curve_i) - 1; point_i++) {
if (check_adjacent_selection(selection_curve, point_i, point_i + 1)) {
apply_selection_operation_at_index(selection_curve, point_i, SEL_OP_ADD);
}
}
filedescriptor marked this conversation as resolved
Review

Nice, that's simpler than I expected. Only needing to propagate it by one element helps!

Nice, that's simpler than I expected. Only needing to propagate it by one element helps!
/* Handle all cases in the backwards direction. */
for (int point_i = points_by_curve.size(curve_i) - 1; point_i > 0; point_i--) {
if (check_adjacent_selection(selection_curve, point_i, point_i - 1)) {
apply_selection_operation_at_index(selection_curve, point_i, SEL_OP_ADD);
}
}
filedescriptor marked this conversation as resolved
Review

Looks like this doesn't handle cyclic curves. For some similar logic, ControlPointNeighborFieldInput might be worth checking out.

IMO, this is pushing the boundaries of what should be done with dynamic types per index, since there's 2 reads per index. But I strongly doubt the selection operators will noticeably non-instantaneous, and it can always be improved later.

Looks like this doesn't handle cyclic curves. For some similar logic, `ControlPointNeighborFieldInput` _might_ be worth checking out. IMO, this is pushing the boundaries of what should be done with dynamic types per index, since there's 2 reads per index. But I strongly doubt the selection operators will noticeably non-instantaneous, and it can always be improved later.
}
});
if (deselect) {
invert_selection(selection.span);
}
selection.finish();
}
void select_random(bke::CurvesGeometry &curves,
const eAttrDomain selection_domain,
uint32_t random_seed,
@ -316,46 +406,6 @@ void select_random(bke::CurvesGeometry &curves,
selection.finish();
}
static void apply_selection_operation_at_index(GMutableSpan selection,
const int index,
const eSelectOp sel_op)
{
if (selection.type().is<bool>()) {
MutableSpan<bool> selection_typed = selection.typed<bool>();
switch (sel_op) {
case SEL_OP_ADD:
case SEL_OP_SET:
selection_typed[index] = true;
break;
case SEL_OP_SUB:
selection_typed[index] = false;
break;
case SEL_OP_XOR:
selection_typed[index] = !selection_typed[index];
break;
default:
break;
}
}
else if (selection.type().is<float>()) {
MutableSpan<float> selection_typed = selection.typed<float>();
switch (sel_op) {
case SEL_OP_ADD:
case SEL_OP_SET:
selection_typed[index] = 1.0f;
break;
case SEL_OP_SUB:
selection_typed[index] = 0.0f;
break;
case SEL_OP_XOR:
selection_typed[index] = 1.0f - selection_typed[index];
break;
default:
break;
}
}
}
/**
* Helper struct for `select_pick`.
*/

View File

@ -135,6 +135,11 @@ void select_ends(bke::CurvesGeometry &curves, int amount, bool end_points);
*/
void select_linked(bke::CurvesGeometry &curves);
/**
* (De)select all the adjacent points of the current selected points.
*/
void select_adjacent(bke::CurvesGeometry &curves, bool deselect);
/**
* Select random points or curves.
*