Curves: Edit mode selection operators
This adds the following operators to edit mode: - `select_all` - `select_random` - `select_end` Differential Revision: https://developer.blender.org/D17047
This commit is contained in:
parent
d650162ecd
commit
70ca15670d
|
@ -55,6 +55,7 @@ _km_hierarchy = [
|
|||
('Curve', 'EMPTY', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('VIEW_3D', 'EDIT_CURVE'),
|
||||
]),
|
||||
('Curves', 'EMPTY', 'WINDOW', []),
|
||||
('Armature', 'EMPTY', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('VIEW_3D', 'EDIT_ARMATURE'),
|
||||
]),
|
||||
|
|
|
@ -5615,6 +5615,14 @@ def km_curves(params):
|
|||
{"items": items},
|
||||
)
|
||||
|
||||
items.extend([
|
||||
("curves.set_selection_domain", {"type": 'ONE', "value": 'PRESS'}, {"properties": [("domain", 'POINT')]}),
|
||||
("curves.set_selection_domain", {"type": 'TWO', "value": 'PRESS'}, {"properties": [("domain", 'CURVE')]}),
|
||||
("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"),
|
||||
])
|
||||
|
||||
return keymap
|
||||
|
||||
|
||||
|
|
|
@ -718,7 +718,7 @@ class VIEW3D_HT_header(Header):
|
|||
if object_mode == 'PARTICLE_EDIT':
|
||||
row = layout.row()
|
||||
row.prop(tool_settings.particle_edit, "select_mode", text="", expand=True)
|
||||
elif object_mode == 'SCULPT_CURVES' and obj.type == 'CURVES':
|
||||
elif object_mode in {'EDIT', 'SCULPT_CURVES'} and obj.type == 'CURVES':
|
||||
curves = obj.data
|
||||
|
||||
row = layout.row(align=True)
|
||||
|
@ -2044,7 +2044,13 @@ class VIEW3D_MT_select_edit_curves(Menu):
|
|||
bl_label = "Select"
|
||||
|
||||
def draw(self, _context):
|
||||
pass
|
||||
layout = self.layout
|
||||
|
||||
layout.operator("curves.select_all", text="All").action = 'SELECT'
|
||||
layout.operator("curves.select_all", text="None").action = 'DESELECT'
|
||||
layout.operator("curves.select_all", text="Invert").action = 'INVERT'
|
||||
layout.operator("curves.select_random", text="Random")
|
||||
layout.operator("curves.select_end", text="Endpoints")
|
||||
|
||||
|
||||
class VIEW3D_MT_select_sculpt_curves(Menu):
|
||||
|
@ -2057,7 +2063,7 @@ class VIEW3D_MT_select_sculpt_curves(Menu):
|
|||
layout.operator("curves.select_all", text="None").action = 'DESELECT'
|
||||
layout.operator("curves.select_all", text="Invert").action = 'INVERT'
|
||||
layout.operator("sculpt_curves.select_random", text="Random")
|
||||
layout.operator("sculpt_curves.select_end", text="Endpoints")
|
||||
layout.operator("curves.select_end", text="Endpoints")
|
||||
layout.operator("sculpt_curves.select_grow", text="Grow")
|
||||
|
||||
|
||||
|
|
|
@ -7,7 +7,10 @@
|
|||
#include <atomic>
|
||||
|
||||
#include "BLI_array_utils.hh"
|
||||
#include "BLI_devirtualize_parameters.hh"
|
||||
#include "BLI_index_mask_ops.hh"
|
||||
#include "BLI_kdtree.h"
|
||||
#include "BLI_rand.hh"
|
||||
#include "BLI_utildefines.h"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
|
@ -15,6 +18,7 @@
|
|||
#include "ED_object.h"
|
||||
#include "ED_screen.h"
|
||||
#include "ED_select_utils.h"
|
||||
#include "ED_view3d.h"
|
||||
|
||||
#include "WM_api.h"
|
||||
|
||||
|
@ -48,6 +52,9 @@
|
|||
#include "RNA_enum_types.h"
|
||||
#include "RNA_prototypes.h"
|
||||
|
||||
#include "UI_interface.h"
|
||||
#include "UI_resources.h"
|
||||
|
||||
#include "GEO_reverse_uv_sampler.hh"
|
||||
|
||||
/**
|
||||
|
@ -820,77 +827,13 @@ static void CURVES_OT_set_selection_domain(wmOperatorType *ot)
|
|||
RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_SKIP_SAVE));
|
||||
}
|
||||
|
||||
static bool contains(const VArray<bool> &varray, const bool value)
|
||||
{
|
||||
const CommonVArrayInfo info = varray.common_info();
|
||||
if (info.type == CommonVArrayInfo::Type::Single) {
|
||||
return *static_cast<const bool *>(info.data) == value;
|
||||
}
|
||||
if (info.type == CommonVArrayInfo::Type::Span) {
|
||||
const Span<bool> span(static_cast<const bool *>(info.data), varray.size());
|
||||
return threading::parallel_reduce(
|
||||
span.index_range(),
|
||||
4096,
|
||||
false,
|
||||
[&](const IndexRange range, const bool init) {
|
||||
return init || span.slice(range).contains(value);
|
||||
},
|
||||
[&](const bool a, const bool b) { return a || b; });
|
||||
}
|
||||
return threading::parallel_reduce(
|
||||
varray.index_range(),
|
||||
2048,
|
||||
false,
|
||||
[&](const IndexRange range, const bool init) {
|
||||
if (init) {
|
||||
return init;
|
||||
}
|
||||
/* Alternatively, this could use #materialize to retrieve many values at once. */
|
||||
for (const int64_t i : range) {
|
||||
if (varray[i] == value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[&](const bool a, const bool b) { return a || b; });
|
||||
}
|
||||
|
||||
bool has_anything_selected(const Curves &curves_id)
|
||||
{
|
||||
const CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry);
|
||||
const VArray<bool> selection = curves.attributes().lookup<bool>(".selection");
|
||||
return !selection || contains(selection, true);
|
||||
}
|
||||
|
||||
static bool has_anything_selected(const Span<Curves *> curves_ids)
|
||||
{
|
||||
return std::any_of(curves_ids.begin(), curves_ids.end(), [](const Curves *curves_id) {
|
||||
return has_anything_selected(*curves_id);
|
||||
return has_anything_selected(CurvesGeometry::wrap(curves_id->geometry));
|
||||
});
|
||||
}
|
||||
|
||||
namespace select_all {
|
||||
|
||||
static void invert_selection(MutableSpan<float> selection)
|
||||
{
|
||||
threading::parallel_for(selection.index_range(), 2048, [&](IndexRange range) {
|
||||
for (const int i : range) {
|
||||
selection[i] = 1.0f - selection[i];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void invert_selection(GMutableSpan selection)
|
||||
{
|
||||
if (selection.type().is<bool>()) {
|
||||
array_utils::invert_booleans(selection.typed<bool>());
|
||||
}
|
||||
else if (selection.type().is<float>()) {
|
||||
invert_selection(selection.typed<float>());
|
||||
}
|
||||
}
|
||||
|
||||
static int select_all_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
int action = RNA_enum_get(op->ptr, "action");
|
||||
|
@ -902,31 +845,10 @@ static int select_all_exec(bContext *C, wmOperator *op)
|
|||
}
|
||||
|
||||
for (Curves *curves_id : unique_curves) {
|
||||
CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry);
|
||||
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
|
||||
if (action == SEL_SELECT) {
|
||||
/* As an optimization, just remove the selection attributes when everything is selected. */
|
||||
attributes.remove(".selection");
|
||||
}
|
||||
else if (!attributes.contains(".selection")) {
|
||||
BLI_assert(ELEM(action, SEL_INVERT, SEL_DESELECT));
|
||||
/* If the attribute doesn't exist and it's either deleted or inverted, create
|
||||
* it with nothing selected, since that means everything was selected before. */
|
||||
attributes.add(".selection",
|
||||
eAttrDomain(curves_id->selection_domain),
|
||||
CD_PROP_BOOL,
|
||||
bke::AttributeInitDefaultValue());
|
||||
}
|
||||
else {
|
||||
bke::GSpanAttributeWriter selection = attributes.lookup_for_write_span(".selection");
|
||||
if (action == SEL_DESELECT) {
|
||||
fill_selection_false(selection.span);
|
||||
}
|
||||
else if (action == SEL_INVERT) {
|
||||
invert_selection(selection.span);
|
||||
}
|
||||
selection.finish();
|
||||
}
|
||||
/* (De)select all the curves. */
|
||||
select_all(CurvesGeometry::wrap(curves_id->geometry),
|
||||
eAttrDomain(curves_id->selection_domain),
|
||||
action);
|
||||
|
||||
/* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
|
||||
* attribute for now. */
|
||||
|
@ -937,15 +859,13 @@ static int select_all_exec(bContext *C, wmOperator *op)
|
|||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
} // namespace select_all
|
||||
|
||||
static void CURVES_OT_select_all(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "(De)select All";
|
||||
ot->idname = "CURVES_OT_select_all";
|
||||
ot->description = "(De)select all control points";
|
||||
|
||||
ot->exec = select_all::select_all_exec;
|
||||
ot->exec = select_all_exec;
|
||||
ot->poll = editable_curves_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
@ -953,6 +873,117 @@ static void CURVES_OT_select_all(wmOperatorType *ot)
|
|||
WM_operator_properties_select_all(ot);
|
||||
}
|
||||
|
||||
static int select_random_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
VectorSet<Curves *> unique_curves = curves::get_unique_editable_curves(*C);
|
||||
|
||||
const int seed = RNA_int_get(op->ptr, "seed");
|
||||
const float probability = RNA_float_get(op->ptr, "probability");
|
||||
|
||||
for (Curves *curves_id : unique_curves) {
|
||||
CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry);
|
||||
select_random(curves, eAttrDomain(curves_id->selection_domain), uint32_t(seed), probability);
|
||||
|
||||
/* 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 select_random_ui(bContext * /*C*/, wmOperator *op)
|
||||
{
|
||||
uiLayout *layout = op->layout;
|
||||
|
||||
uiItemR(layout, op->ptr, "seed", 0, nullptr, ICON_NONE);
|
||||
uiItemR(layout, op->ptr, "probability", UI_ITEM_R_SLIDER, "Probability", ICON_NONE);
|
||||
}
|
||||
|
||||
static void CURVES_OT_select_random(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "Select Random";
|
||||
ot->idname = __func__;
|
||||
ot->description = "Randomizes existing selection or create new random selection";
|
||||
|
||||
ot->exec = select_random_exec;
|
||||
ot->poll = curves::editable_curves_poll;
|
||||
ot->ui = select_random_ui;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
RNA_def_int(ot->srna,
|
||||
"seed",
|
||||
0,
|
||||
INT32_MIN,
|
||||
INT32_MAX,
|
||||
"Seed",
|
||||
"Source of randomness",
|
||||
INT32_MIN,
|
||||
INT32_MAX);
|
||||
RNA_def_float(ot->srna,
|
||||
"probability",
|
||||
0.5f,
|
||||
0.0f,
|
||||
1.0f,
|
||||
"Probability",
|
||||
"Chance of every point or curve being included in the selection",
|
||||
0.0f,
|
||||
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);
|
||||
const bool end_points = RNA_boolean_get(op->ptr, "end_points");
|
||||
const int amount = RNA_int_get(op->ptr, "amount");
|
||||
|
||||
for (Curves *curves_id : unique_curves) {
|
||||
CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry);
|
||||
select_ends(curves, eAttrDomain(curves_id->selection_domain), amount, end_points);
|
||||
|
||||
/* 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_end(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "Select End";
|
||||
ot->idname = __func__;
|
||||
ot->description = "Select end points of curves";
|
||||
|
||||
ot->exec = select_end_exec;
|
||||
ot->poll = select_end_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
RNA_def_boolean(ot->srna,
|
||||
"end_points",
|
||||
true,
|
||||
"End Points",
|
||||
"Select points at the end of the curve as opposed to the beginning");
|
||||
RNA_def_int(
|
||||
ot->srna, "amount", 1, 0, INT32_MAX, "Amount", "Number of points to select", 0, INT32_MAX);
|
||||
}
|
||||
|
||||
namespace surface_set {
|
||||
|
||||
static bool surface_set_poll(bContext *C)
|
||||
|
@ -1046,5 +1077,15 @@ void ED_operatortypes_curves()
|
|||
WM_operatortype_append(CURVES_OT_snap_curves_to_surface);
|
||||
WM_operatortype_append(CURVES_OT_set_selection_domain);
|
||||
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_surface_set);
|
||||
}
|
||||
|
||||
void ED_keymap_curves(wmKeyConfig *keyconf)
|
||||
{
|
||||
using namespace blender::ed::curves;
|
||||
/* Only set in editmode curves, by space_view3d listener. */
|
||||
wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Curves", 0, 0);
|
||||
keymap->poll = editable_curves_poll;
|
||||
}
|
||||
|
|
|
@ -4,13 +4,16 @@
|
|||
* \ingroup edcurves
|
||||
*/
|
||||
|
||||
#include "BLI_array_utils.hh"
|
||||
#include "BLI_index_mask_ops.hh"
|
||||
#include "BLI_rand.hh"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_curves.hh"
|
||||
|
||||
#include "ED_curves.h"
|
||||
#include "ED_object.h"
|
||||
#include "ED_select_utils.h"
|
||||
|
||||
namespace blender::ed::curves {
|
||||
|
||||
|
@ -69,31 +72,32 @@ IndexMask retrieve_selected_points(const Curves &curves_id, Vector<int64_t> &r_i
|
|||
return retrieve_selected_points(curves, r_indices);
|
||||
}
|
||||
|
||||
void ensure_selection_attribute(Curves &curves_id, const eCustomDataType create_type)
|
||||
bke::GSpanAttributeWriter ensure_selection_attribute(bke::CurvesGeometry &curves,
|
||||
const eAttrDomain selection_domain,
|
||||
const eCustomDataType create_type)
|
||||
{
|
||||
bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry);
|
||||
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
|
||||
if (attributes.contains(".selection")) {
|
||||
return;
|
||||
return attributes.lookup_for_write_span(".selection");
|
||||
}
|
||||
const eAttrDomain domain = eAttrDomain(curves_id.selection_domain);
|
||||
const int domain_size = attributes.domain_size(domain);
|
||||
const int domain_size = attributes.domain_size(selection_domain);
|
||||
switch (create_type) {
|
||||
case CD_PROP_BOOL:
|
||||
attributes.add(".selection",
|
||||
domain,
|
||||
selection_domain,
|
||||
CD_PROP_BOOL,
|
||||
bke::AttributeInitVArray(VArray<bool>::ForSingle(true, domain_size)));
|
||||
break;
|
||||
case CD_PROP_FLOAT:
|
||||
attributes.add(".selection",
|
||||
domain,
|
||||
selection_domain,
|
||||
CD_PROP_FLOAT,
|
||||
bke::AttributeInitVArray(VArray<float>::ForSingle(1.0f, domain_size)));
|
||||
break;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
return attributes.lookup_for_write_span(".selection");
|
||||
}
|
||||
|
||||
void fill_selection_false(GMutableSpan selection)
|
||||
|
@ -105,6 +109,7 @@ void fill_selection_false(GMutableSpan selection)
|
|||
selection.typed<float>().fill(0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void fill_selection_true(GMutableSpan selection)
|
||||
{
|
||||
if (selection.type().is<bool>()) {
|
||||
|
@ -115,4 +120,168 @@ void fill_selection_true(GMutableSpan selection)
|
|||
}
|
||||
}
|
||||
|
||||
static bool contains(const VArray<bool> &varray, const bool value)
|
||||
{
|
||||
const CommonVArrayInfo info = varray.common_info();
|
||||
if (info.type == CommonVArrayInfo::Type::Single) {
|
||||
return *static_cast<const bool *>(info.data) == value;
|
||||
}
|
||||
if (info.type == CommonVArrayInfo::Type::Span) {
|
||||
const Span<bool> span(static_cast<const bool *>(info.data), varray.size());
|
||||
return threading::parallel_reduce(
|
||||
span.index_range(),
|
||||
4096,
|
||||
false,
|
||||
[&](const IndexRange range, const bool init) {
|
||||
return init || span.slice(range).contains(value);
|
||||
},
|
||||
[&](const bool a, const bool b) { return a || b; });
|
||||
}
|
||||
return threading::parallel_reduce(
|
||||
varray.index_range(),
|
||||
2048,
|
||||
false,
|
||||
[&](const IndexRange range, const bool init) {
|
||||
if (init) {
|
||||
return init;
|
||||
}
|
||||
/* Alternatively, this could use #materialize to retrieve many values at once. */
|
||||
for (const int64_t i : range) {
|
||||
if (varray[i] == value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[&](const bool a, const bool b) { return a || b; });
|
||||
}
|
||||
|
||||
bool has_anything_selected(const bke::CurvesGeometry &curves)
|
||||
{
|
||||
const VArray<bool> selection = curves.attributes().lookup<bool>(".selection");
|
||||
return !selection || contains(selection, true);
|
||||
}
|
||||
|
||||
static void invert_selection(MutableSpan<float> selection)
|
||||
{
|
||||
threading::parallel_for(selection.index_range(), 2048, [&](IndexRange range) {
|
||||
for (const int i : range) {
|
||||
selection[i] = 1.0f - selection[i];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void invert_selection(GMutableSpan selection)
|
||||
{
|
||||
if (selection.type().is<bool>()) {
|
||||
array_utils::invert_booleans(selection.typed<bool>());
|
||||
}
|
||||
else if (selection.type().is<float>()) {
|
||||
invert_selection(selection.typed<float>());
|
||||
}
|
||||
}
|
||||
|
||||
void select_all(bke::CurvesGeometry &curves, const eAttrDomain selection_domain, int action)
|
||||
{
|
||||
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
|
||||
if (action == SEL_SELECT) {
|
||||
/* As an optimization, just remove the selection attributes when everything is selected. */
|
||||
attributes.remove(".selection");
|
||||
}
|
||||
else {
|
||||
bke::GSpanAttributeWriter selection = ensure_selection_attribute(
|
||||
curves, selection_domain, CD_PROP_BOOL);
|
||||
if (action == SEL_DESELECT) {
|
||||
fill_selection_false(selection.span);
|
||||
}
|
||||
else if (action == SEL_INVERT) {
|
||||
invert_selection(selection.span);
|
||||
}
|
||||
selection.finish();
|
||||
}
|
||||
}
|
||||
|
||||
void select_ends(bke::CurvesGeometry &curves,
|
||||
const eAttrDomain selection_domain,
|
||||
int amount,
|
||||
bool end_points)
|
||||
{
|
||||
const bool was_anything_selected = has_anything_selected(curves);
|
||||
bke::GSpanAttributeWriter selection = ensure_selection_attribute(
|
||||
curves, selection_domain, CD_PROP_BOOL);
|
||||
if (!was_anything_selected) {
|
||||
fill_selection_true(selection.span);
|
||||
}
|
||||
selection.span.type().to_static_type_tag<bool, float>([&](auto type_tag) {
|
||||
using T = typename decltype(type_tag)::type;
|
||||
if constexpr (std::is_void_v<T>) {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
else {
|
||||
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));
|
||||
}
|
||||
else {
|
||||
selection_typed.slice(points_by_curve[curve_i].drop_front(amount)).fill(T(0));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
selection.finish();
|
||||
}
|
||||
|
||||
void select_random(bke::CurvesGeometry &curves,
|
||||
const eAttrDomain selection_domain,
|
||||
uint32_t random_seed,
|
||||
float probability)
|
||||
{
|
||||
RandomNumberGenerator rng{random_seed};
|
||||
const auto next_bool_random_value = [&]() { return rng.get_float() <= probability; };
|
||||
|
||||
const bool was_anything_selected = has_anything_selected(curves);
|
||||
bke::GSpanAttributeWriter selection = ensure_selection_attribute(
|
||||
curves, selection_domain, CD_PROP_BOOL);
|
||||
if (!was_anything_selected) {
|
||||
curves::fill_selection_true(selection.span);
|
||||
}
|
||||
selection.span.type().to_static_type_tag<bool, float>([&](auto type_tag) {
|
||||
using T = typename decltype(type_tag)::type;
|
||||
if constexpr (std::is_void_v<T>) {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
else {
|
||||
MutableSpan<T> selection_typed = selection.span.typed<T>();
|
||||
switch (selection_domain) {
|
||||
case ATTR_DOMAIN_POINT: {
|
||||
for (const int point_i : selection_typed.index_range()) {
|
||||
const bool random_value = next_bool_random_value();
|
||||
if (!random_value) {
|
||||
selection_typed[point_i] = T(0);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ATTR_DOMAIN_CURVE: {
|
||||
for (const int curve_i : curves.curves_range()) {
|
||||
const bool random_value = next_bool_random_value();
|
||||
if (!random_value) {
|
||||
selection_typed[curve_i] = T(0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
});
|
||||
selection.finish();
|
||||
}
|
||||
|
||||
} // namespace blender::ed::curves
|
||||
|
|
|
@ -19,6 +19,7 @@ extern "C" {
|
|||
|
||||
void ED_operatortypes_curves(void);
|
||||
void ED_curves_undosys_type(struct UndoType *ut);
|
||||
void ED_keymap_curves(struct wmKeyConfig *keyconf);
|
||||
|
||||
/**
|
||||
* Return an owning pointer to an array of point normals the same size as the number of control
|
||||
|
@ -80,7 +81,7 @@ void fill_selection_true(GMutableSpan span);
|
|||
/**
|
||||
* Return true if any element is selected, on either domain with either type.
|
||||
*/
|
||||
bool has_anything_selected(const Curves &curves_id);
|
||||
bool has_anything_selected(const bke::CurvesGeometry &curves);
|
||||
|
||||
/**
|
||||
* Find curves that have any point selected (a selection factor greater than zero),
|
||||
|
@ -97,8 +98,40 @@ IndexMask retrieve_selected_points(const Curves &curves_id, Vector<int64_t> &r_i
|
|||
/**
|
||||
* If the ".selection" attribute doesn't exist, create it with the requested type (bool or float).
|
||||
*/
|
||||
void ensure_selection_attribute(Curves &curves_id, const eCustomDataType create_type);
|
||||
bke::GSpanAttributeWriter ensure_selection_attribute(bke::CurvesGeometry &curves,
|
||||
const eAttrDomain selection_domain,
|
||||
const eCustomDataType create_type);
|
||||
|
||||
/**
|
||||
* (De)select all the curves.
|
||||
*
|
||||
* \param action: One of SEL_TOGGLE, SEL_SELECT, SEL_DESELECT, or SEL_INVERT. See
|
||||
* "ED_select_utils.h".
|
||||
*/
|
||||
void select_all(bke::CurvesGeometry &curves, const eAttrDomain selection_domain, int action);
|
||||
|
||||
/**
|
||||
* Select the ends (front or back) of all the curves.
|
||||
*
|
||||
* \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);
|
||||
|
||||
/**
|
||||
* Select random points or curves.
|
||||
*
|
||||
* \param random_seed: The seed for the \a RandomNumberGenerator.
|
||||
* \param probability: Determins how likely a point/curve will be selected. If set to 0.0, nothing
|
||||
* will be selected, if set to 1.0 everything will be selected.
|
||||
*/
|
||||
void select_random(bke::CurvesGeometry &curves,
|
||||
const eAttrDomain selection_domain,
|
||||
uint32_t random_seed,
|
||||
float probability);
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::ed::curves
|
||||
|
|
|
@ -363,7 +363,7 @@ static int select_random_exec(bContext *C, wmOperator *op)
|
|||
|
||||
for (Curves *curves_id : unique_curves) {
|
||||
CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry);
|
||||
const bool was_anything_selected = curves::has_anything_selected(*curves_id);
|
||||
const bool was_anything_selected = curves::has_anything_selected(curves);
|
||||
|
||||
bke::SpanAttributeWriter<float> attribute = float_selection_ensure(*curves_id);
|
||||
MutableSpan<float> selection = attribute.span;
|
||||
|
@ -517,90 +517,6 @@ static void SCULPT_CURVES_OT_select_random(wmOperatorType *ot)
|
|||
"Constant per Curve",
|
||||
"The generated random number is the same for every control point of a curve");
|
||||
}
|
||||
|
||||
namespace select_end {
|
||||
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);
|
||||
const bool end_points = RNA_boolean_get(op->ptr, "end_points");
|
||||
const int amount = RNA_int_get(op->ptr, "amount");
|
||||
|
||||
for (Curves *curves_id : unique_curves) {
|
||||
CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry);
|
||||
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
|
||||
|
||||
const bool was_anything_selected = curves::has_anything_selected(*curves_id);
|
||||
curves::ensure_selection_attribute(*curves_id, CD_PROP_BOOL);
|
||||
bke::GSpanAttributeWriter selection = attributes.lookup_for_write_span(".selection");
|
||||
if (!was_anything_selected) {
|
||||
curves::fill_selection_true(selection.span);
|
||||
}
|
||||
const OffsetIndices points_by_curve = curves.points_by_curve();
|
||||
selection.span.type().to_static_type_tag<bool, float>([&](auto type_tag) {
|
||||
using T = typename decltype(type_tag)::type;
|
||||
if constexpr (std::is_void_v<T>) {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
else {
|
||||
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 IndexRange points = points_by_curve[curve_i];
|
||||
if (end_points) {
|
||||
selection_typed.slice(points.drop_back(amount)).fill(T(0));
|
||||
}
|
||||
else {
|
||||
selection_typed.slice(points.drop_front(amount)).fill(T(0));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
selection.finish();
|
||||
|
||||
/* 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;
|
||||
}
|
||||
} // namespace select_end
|
||||
|
||||
static void SCULPT_CURVES_OT_select_end(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "Select End";
|
||||
ot->idname = __func__;
|
||||
ot->description = "Select end points of curves";
|
||||
|
||||
ot->exec = select_end::select_end_exec;
|
||||
ot->poll = select_end::select_end_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
RNA_def_boolean(ot->srna,
|
||||
"end_points",
|
||||
true,
|
||||
"End Points",
|
||||
"Select points at the end of the curve as opposed to the beginning");
|
||||
RNA_def_int(
|
||||
ot->srna, "amount", 1, 0, INT32_MAX, "Amount", "Number of points to select", 0, INT32_MAX);
|
||||
}
|
||||
|
||||
namespace select_grow {
|
||||
|
||||
struct GrowOperatorDataPerCurve : NonCopyable, NonMovable {
|
||||
|
@ -1263,7 +1179,6 @@ void ED_operatortypes_sculpt_curves()
|
|||
WM_operatortype_append(SCULPT_CURVES_OT_brush_stroke);
|
||||
WM_operatortype_append(CURVES_OT_sculptmode_toggle);
|
||||
WM_operatortype_append(SCULPT_CURVES_OT_select_random);
|
||||
WM_operatortype_append(SCULPT_CURVES_OT_select_end);
|
||||
WM_operatortype_append(SCULPT_CURVES_OT_select_grow);
|
||||
WM_operatortype_append(SCULPT_CURVES_OT_min_distance_edit);
|
||||
}
|
||||
|
|
|
@ -186,6 +186,7 @@ void ED_spacetypes_keymap(wmKeyConfig *keyconf)
|
|||
ED_keymap_mesh(keyconf);
|
||||
ED_keymap_uvedit(keyconf);
|
||||
ED_keymap_curve(keyconf);
|
||||
ED_keymap_curves(keyconf);
|
||||
ED_keymap_armature(keyconf);
|
||||
ED_keymap_physics(keyconf);
|
||||
ED_keymap_metaball(keyconf);
|
||||
|
|
|
@ -407,6 +407,9 @@ static void view3d_main_region_init(wmWindowManager *wm, ARegion *region)
|
|||
keymap = WM_keymap_ensure(wm->defaultconf, "Curve", 0, 0);
|
||||
WM_event_add_keymap_handler(®ion->handlers, keymap);
|
||||
|
||||
keymap = WM_keymap_ensure(wm->defaultconf, "Curves", 0, 0);
|
||||
WM_event_add_keymap_handler(®ion->handlers, keymap);
|
||||
|
||||
keymap = WM_keymap_ensure(wm->defaultconf, "Image Paint", 0, 0);
|
||||
WM_event_add_keymap_handler(®ion->handlers, keymap);
|
||||
|
||||
|
|
Loading…
Reference in New Issue