Curves: Add select linked #104569
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
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
|
||||
Hans Goudey
commented
Might be nice to replace these three lines with a utility function like 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.
Falk David
commented
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
Hans Goudey
commented
Best to avoid reusing the word "linked" in the description. How about 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
Hans Goudey
commented
For proper float support, this should probably check for anything greater than 0, right? 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,
|
||||
|
|
|
@ -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
Hans Goudey
commented
`const GSpan selection` -> `GSpan selection`
|
||||
bool has_anything_selected(GSpan selection);
|
||||
filedescriptor marked this conversation as resolved
Hans Goudey
commented
Not that it's better, but maybe using the same name 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.
|
||||
|
|
(I know, copied code) but the
curves::
namespace specification shouldn't be necessary here, since we're already in that namespace.