Curves: Add select linked #104569

Merged
Falk David merged 5 commits from filedescriptor/blender:curves-select-linked into main 2023-02-10 18:58:10 +01:00
5 changed files with 91 additions and 25 deletions

View File

@ -5627,6 +5627,7 @@ def km_curves(params):
("curves.disable_selection", {"type": 'ONE', "value": 'PRESS', "alt": True}, None),
("curves.disable_selection", {"type": 'TWO', "value": 'PRESS', "alt": True}, None),
*_template_items_select_actions(params, "curves.select_all"),
("curves.select_linked", {"type": 'L', "value": 'PRESS', "ctrl": True}, None),
])
return keymap

View File

@ -2052,6 +2052,7 @@ class VIEW3D_MT_select_edit_curves(Menu):
layout.operator("curves.select_all", text="Invert").action = 'INVERT'
layout.operator("curves.select_random", text="Random")
layout.operator("curves.select_end", text="Endpoints")
layout.operator("curves.select_linked", text="Linked")
class VIEW3D_MT_select_sculpt_curves(Menu):

View File

@ -157,6 +157,19 @@ bool curves_poll(bContext *C)
return curves_poll_impl(C, false, false, false);
}
static bool editable_curves_point_domain_poll(bContext *C)
{
if (!curves::editable_curves_poll(C)) {
return false;
}
const Curves *curves_id = static_cast<const Curves *>(CTX_data_active_object(C)->data);
if (curves_id->selection_domain != ATTR_DOMAIN_POINT) {
CTX_wm_operator_poll_msg_set(C, "Only available in point selection mode");
return false;
}
return true;
}
using bke::CurvesGeometry;
namespace convert_to_particle_system {
@ -931,19 +944,6 @@ static void CURVES_OT_select_random(wmOperatorType *ot)
1.0f);
}
static bool select_end_poll(bContext *C)
{
if (!curves::editable_curves_poll(C)) {
return false;
}
const Curves *curves_id = static_cast<const Curves *>(CTX_data_active_object(C)->data);
if (curves_id->selection_domain != ATTR_DOMAIN_POINT) {
CTX_wm_operator_poll_msg_set(C, "Only available in point selection mode");
return false;
}
return true;
}
static int select_end_exec(bContext *C, wmOperator *op)
{
VectorSet<Curves *> unique_curves = curves::get_unique_editable_curves(*C);
@ -952,7 +952,7 @@ static int select_end_exec(bContext *C, wmOperator *op)
for (Curves *curves_id : unique_curves) {
CurvesGeometry &curves = curves_id->geometry.wrap();
select_ends(curves, eAttrDomain(curves_id->selection_domain), amount, end_points);
select_ends(curves, amount, end_points);
/* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
* attribute for now. */
@ -970,7 +970,7 @@ static void CURVES_OT_select_end(wmOperatorType *ot)
ot->description = "Select end points of curves";
ot->exec = select_end_exec;
ot->poll = select_end_poll;
ot->poll = editable_curves_point_domain_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
@ -983,6 +983,33 @@ static void CURVES_OT_select_end(wmOperatorType *ot)
ot->srna, "amount", 1, 0, INT32_MAX, "Amount", "Number of points to select", 0, INT32_MAX);
}
static int select_linked_exec(bContext *C, wmOperator * /*op*/)
{
VectorSet<Curves *> unique_curves = get_unique_editable_curves(*C);
filedescriptor marked this conversation as resolved
Review

(I know, copied code) but the curves:: namespace specification shouldn't be necessary here, since we're already in that namespace.

(I know, copied code) but the `curves::` namespace specification shouldn't be necessary here, since we're already in that namespace.
for (Curves *curves_id : unique_curves) {
CurvesGeometry &curves = curves_id->geometry.wrap();
select_linked(curves);
/* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
Review

Might be nice to replace these three lines with a utility function like update_tag_selection_changed(C, curves_id), especially so that comment doesn't have to be repeated everywhere. A separate commit though obviously.

Might be nice to replace these three lines with a utility function like `update_tag_selection_changed(C, curves_id)`, especially so that comment doesn't have to be repeated everywhere. A separate commit though obviously.
Review

I can create a PR for this after, yes.

I can create a PR for this after, yes.
* 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_linked(wmOperatorType *ot)
{
ot->name = "Select Linked";
ot->idname = __func__;
ot->description = "Select all points in curves with any point selection";
filedescriptor marked this conversation as resolved
Review

Best to avoid reusing the word "linked" in the description. How about Select all points in curves with any point selection?

Best to avoid reusing the word "linked" in the description. How about `Select all points in curves with any point selection`?
ot->exec = select_linked_exec;
ot->poll = editable_curves_point_domain_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
namespace surface_set {
static bool surface_set_poll(bContext *C)
@ -1078,6 +1105,7 @@ void ED_operatortypes_curves()
WM_operatortype_append(CURVES_OT_select_all);
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_surface_set);
}

View File

@ -164,6 +164,21 @@ bool has_anything_selected(const bke::CurvesGeometry &curves)
return !selection || contains(selection, true);
}
bool has_anything_selected(const GSpan selection)
{
if (selection.type().is<bool>()) {
return selection.typed<bool>().contains(true);
}
else if (selection.type().is<float>()) {
for (const float elem : selection.typed<float>()) {
if (elem > 0.0f) {
return true;
}
}
}
return false;
}
static void invert_selection(MutableSpan<float> selection)
{
threading::parallel_for(selection.index_range(), 2048, [&](IndexRange range) {
@ -203,14 +218,12 @@ void select_all(bke::CurvesGeometry &curves, const eAttrDomain selection_domain,
}
}
void select_ends(bke::CurvesGeometry &curves,
const eAttrDomain selection_domain,
int amount,
bool end_points)
void select_ends(bke::CurvesGeometry &curves, int amount, bool end_points)
{
const bool was_anything_selected = has_anything_selected(curves);
const OffsetIndices points_by_curve = curves.points_by_curve();
bke::GSpanAttributeWriter selection = ensure_selection_attribute(
curves, selection_domain, CD_PROP_BOOL);
curves, ATTR_DOMAIN_POINT, CD_PROP_BOOL);
if (!was_anything_selected) {
fill_selection_true(selection.span);
}
@ -223,7 +236,6 @@ void select_ends(bke::CurvesGeometry &curves,
MutableSpan<T> selection_typed = selection.span.typed<T>();
threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange range) {
for (const int curve_i : range) {
const OffsetIndices points_by_curve = curves.points_by_curve();
if (end_points) {
selection_typed.slice(points_by_curve[curve_i].drop_back(amount)).fill(T(0));
}
@ -237,6 +249,23 @@ void select_ends(bke::CurvesGeometry &curves,
selection.finish();
}
void select_linked(bke::CurvesGeometry &curves)
{
filedescriptor marked this conversation as resolved
Review

For proper float support, this should probably check for anything greater than 0, right?
I'd suggest separating that check to a small local function BTW as a way to give it a nice name and to make it easier to generalize in the future if we want to.

For proper float support, this should probably check for anything greater than 0, right? I'd suggest separating that check to a small local function BTW as a way to give it a nice name and to make it easier to generalize in the future if we want to.
const OffsetIndices points_by_curve = curves.points_by_curve();
bke::GSpanAttributeWriter selection = ensure_selection_attribute(
curves, ATTR_DOMAIN_POINT, CD_PROP_BOOL);
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]);
if (has_anything_selected(selection_curve)) {
fill_selection_true(selection_curve);
}
}
});
selection.finish();
}
void select_random(bke::CurvesGeometry &curves,
const eAttrDomain selection_domain,
uint32_t random_seed,

View File

@ -89,6 +89,11 @@ void fill_selection_true(GMutableSpan span);
*/
bool has_anything_selected(const bke::CurvesGeometry &curves);
/**
* Return true if any element in the span is selected, on either domain with either type.
*/
filedescriptor marked this conversation as resolved
Review

const GSpan selection -> GSpan selection

`const GSpan selection` -> `GSpan selection`
bool has_anything_selected(GSpan selection);
filedescriptor marked this conversation as resolved
Review

Not that it's better, but maybe using the same name has_anything_selected would be better for consistency?

Not that it's better, but maybe using the same name `has_anything_selected` would be better for consistency?
/**
* Find curves that have any point selected (a selection factor greater than zero),
* or curves that have their own selection factor greater than zero.
@ -123,10 +128,12 @@ void select_all(bke::CurvesGeometry &curves, const eAttrDomain selection_domain,
* \param amount: The amount of points to select from the front or back.
* \param end_points: If true, select the last point(s), if false, select the first point(s).
*/
void select_ends(bke::CurvesGeometry &curves,
const eAttrDomain selection_domain,
int amount,
bool end_points);
void select_ends(bke::CurvesGeometry &curves, int amount, bool end_points);
/**
* Select the points of all curves that have at least one point selected.
*/
void select_linked(bke::CurvesGeometry &curves);
/**
* Select random points or curves.