Curves: Bezier handle selection support #119712
No reviewers
Labels
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
EEVEE & Viewport
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
EEVEE & Viewport
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No project
No Assignees
4 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: blender/blender#119712
Loading…
Reference in New Issue
No description provided.
Delete Branch "laurynas/blender:select-bezier-handles"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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.WIP: Curves: Bezier handle selection supportto Curves: Bezier handle selection supportThanks for working on this!
SelectionAttributeList
introduces unnecessary complexity. Couldn't there just be a function likeget_curves_selection_attribute_names(const CurvesGeometry &curves) -> Span<std::string>
?Span<float3> positions_[3]
. Often one can just usestd::array
instead.SelectableRangeList
to be a class instead of just having itsforeach
methods as standalone functions.Evaluated data shouldn't be changed by drawing code, but it looks like this is currently done innoticed that this is not actually drawing code but an operatorcurves_draw.cc
because it seems to remove the selection attributes.Regarding
SelectionAttributeList
absolutely agree. I came with aSelectionAttributeWriterList
, then separatedSelectionAttributeList
from it and missed the fact it could be justSpan
.Still I'd rather go with
get_curves_selection_attribute_ids(const CurvesGeometry &curves) -> Span<bke::AttributeIDRef>
.All selection functions take
AttributeIDRef
as an argument and it feels like it is more effective to have it ready.In functions like
select_box
SelectableRangeList
is used in a loop. Having class here helps to calculatebezier_curves_
only once. Another option is to pass them as a parameter, but this would separate logic that always go together.Maybe I hurried to answer regarding
SelectableRangeList
.@ -922,3 +924,1 @@
".selection", bke::AttrDomain::Curve);
selection.varray.set(curve_index, true);
selection.finish();
/* If Bezier curve is being added, loop through all three ids. */
bezier
should be lower case here, same below.three
shouldn't be mentioned here because it's a detail from somewhere else.Bezier is a proper noun, in wikipedia curves are referenced as "Bézier curves". Am I missing something?
"three" is decided exactly here. If we create bezier_as_nurbs, Bezier handles will be needed only if Bezier curves were already there. So
selection_attribute_names
is used as to reflects initial state.If the Bezier curve has just been created, it is known for sure that attributes for Bezier handles are needed.
It must be confusing because those selection attributes we deleted on line 777. I'll explain in more detail in reply to another message.
Ok, either way is fine with me. Looks like we are inconsistent with that in Blender currently.
@ -986,12 +1002,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
Aren't those created further up already?
No.
I think it is answered in the reply below.
@ -988,1 +1004,4 @@
/* Creates ".selection_handle_left" and ".selection_handle_right" attributes, otherwise all
* existing Bezier handles would be treated as selected. */
for (const int i : selection_attribute_ids.index_range().drop_front(1)) {
Weird knowledge of implementation details by calling
drop_front(1)
.Better just use the attribute names directly here and only use
selection_attribute_ids
when you actually want to refer to all selection attributes. Then those also don't have to mentioned in the comment.for
loop's body here will be executed either twice or will be skipped. I prefer herefor
overif
with the body repeated twice, to avoid later mistakes on changes. Furthermore withif
same comment would be needed.Intention from UI side is that user after drawing gets his new curve with all control points selected and control points of all other deselected. So to append new curve
curves.resize()
is called and my guess is that to avoid reallocating memory for.selection
attribute and then cleaning values out, inf5adfa6
the attribute was removedattributes.remove(".selection");
and latter added:As I added two more attributes did the same the same with them. Maybe just didn't express right in the comment.
Also as writing of this I understood there is a small flaw where I deal with NURBS and Bezier cases. If NURBS curve is added and curves already had Bezier then code will mark left and right handles for the NURBS curve as selected. User will not see it, but logically it is a mistake.
With
get_curves_bezier_selection_attribute_names
this one should look better, but the reasoning for wholefor
loop still stays deeper.@ -50,6 +50,58 @@ float (*ED_curves_point_normals_array_create(const Curves *curves_id))[3];
namespace blender::ed::curves {
Span<bke::AttributeIDRef> get_curves_selection_attribute_ids(const bke::CurvesGeometry &curves);
I kind of think that this should be a
Span<std::string>
, just because it can be. It avoids that the reader has to consider what else it might be. I'd assume that this doesn't make using these functions any harder, but that can be tried.I did it, but still doubt about it. For me it's easier to follow things when return type and parameter type matches without even implicit conversions.
Again just think about how much power will go to waste on conversion to
AttributeIDRef
every time artists selects curve control points all over the world. Think about pollution, about climate change. Think about whales!It is not too late to revert it.
@ -51,2 +51,4 @@
namespace blender::ed::curves {
Span<bke::AttributeIDRef> get_curves_selection_attribute_ids(const bke::CurvesGeometry &curves);
Span<bke::AttributeIDRef> get_curves_selection_attribute_ids(const int size);
This seems like a weird public function. It's not obvious what it does. Maybe it should be removed or at least be removed from the header?
@ -52,1 +52,4 @@
Span<bke::AttributeIDRef> get_curves_selection_attribute_ids(const bke::CurvesGeometry &curves);
Span<bke::AttributeIDRef> get_curves_selection_attribute_ids(const int size);
Span<bke::AttributeIDRef> get_curves_selection_attribute_ids();
This functions should have comments describing what they return.
@ -53,0 +54,4 @@
Span<bke::AttributeIDRef> get_curves_selection_attribute_ids(const int size);
Span<bke::AttributeIDRef> get_curves_selection_attribute_ids();
class SelectionAttributeWriterList {
I'm not sure this class is really needed either. Seems like just using
Vector<bke::GSpanAttributeWriter>
would work too. Maybe this can also usebke::SpanAttributeWriter<bool>
instead, not sure if float selection may be allowed here.Use case. Create empty hair, go to sculpt mode, add some curves and Paint select something. Go to edit mode and box select. In
select_box()>>fill_selection_false()
conditionselection.type().is<float>()
istrue
.In defense of
SelectionAttributeWriterList
I find destructor callingselection.finish()
automatically most important.It allows compact notations concentrated on the problem without an extra noise. Such as:
In methods like
select_box
,select_circle
the class spares extrafor
loop (or util function) withfinish()
. Frees from necessity to remember it, also decide where to put it.Off topic. I noticed that after drawing Bezier curve in "Edit mode" in "Empty Hair" selection mode inChanges didn't help. Also started to doubt if I got design intentions right.float
s is lost.Most likely because of deletion and recreation of selection attributes. So it also has to be rewritten with
GSpanAttributeWriter
.Note that the call to
.finish()
is intentionally not part of the destructor. Calling it may invalidate some caches etc. in some cases which should happen at an explicit point in time.@ -53,0 +63,4 @@
const bke::AttrDomain selection_domain);
~SelectionAttributeWriterList();
inline const Span<bke::AttributeIDRef> &attribute_ids() const
No need for
inline
here. Methods defined inside in class scope areinline
implicitly.@ -53,0 +67,4 @@
{
return attribute_ids_;
}
inline const int size() const
The
const
on values that are returned by value is meaningless.@ -53,0 +90,4 @@
}
};
typedef std::function<void(
FunctionRef
here.using
instead oftypedef
.@ -53,0 +91,4 @@
};
typedef std::function<void(
const IndexRange range, const Span<float3> positions, const int selection_attribute_index)>
selection_attribute_index
is a good abstraction level here. It's certainly missing a comment, but may be worth trying to find alternatives that don't need this index.const
in this declaration is meaningless.@ -53,0 +94,4 @@
const IndexRange range, const Span<float3> positions, const int selection_attribute_index)>
SelectableRangeConsumer;
void foreach_selectable_point_range(const bke::CurvesGeometry &curves,
It's not really clear to me what a
selected point range
is based on this header. Some comment is necessary.@ -184,0 +240,4 @@
const eCustomDataType create_type,
const bke::AttributeIDRef &attribute_id = ".selection");
template<typename Fn>
Does not seem likely that using a template parameter here instead of a
FunctionRef
has any benefits in practice.@ -776,2 +776,2 @@
attributes.remove(".selection");
Span<std::string> selection_attribute_names = get_curves_selection_attribute_names(curves);
for (const bke::AttributeIDRef selection_attribute_id : selection_attribute_names) {
I think these loops should work like this:
const StringRef selection_name : selection_attribute_names
.The conversion to
AttributeIDRef
is done implicitly in the call toattributes.remove
.The name
selection_name
should probably be used in other places too instead ofselection_attribute_id
.@ -74,30 +76,140 @@ IndexMask retrieve_selected_points(const Curves &curves_id, IndexMaskMemory &mem
return retrieve_selected_points(curves, memory);
}
static const std::array<std::string, 3> selection_attribute_names_{
Move this into
get_curves_all_selection_attribute_names
to avoid the static initialization order fiasco.@ -77,0 +89,4 @@
Span<std::string> get_curves_all_selection_attribute_names()
{
return Span<std::string>(selection_attribute_names_.data(), selection_attribute_names_.size());
std::array
can be cast toSpan
implicitly.@ -77,0 +153,4 @@
range_consumer(curves.points_range(), positions[0], selection_attribute_names[0]);
if (selection_attribute_names.size() > 1) {
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
for (const int attribute_i : IndexRange(1, 2)) {
It's less bad now than before because the order of selection attribute names is documented in the header, but this still feels very brittle. It's totally not obvious here what the 1 and 2 mean. May also be fine though, I don't have a better idea to structure this right now.
Also note that it may be better to use named constructors for creating such index masks nowadays:
IndexRange::from_*
.Added function
get_curves_bezier_selection_attribute_names
. I think it is good now.@ -53,0 +55,4 @@
* [".selection", ".selection_handle_left", ".selection_handle_right"] otherwise. */
Span<std::string> get_curves_selection_attribute_names(const bke::CurvesGeometry &curves);
/* Get all possible curve selection attribute names. */
Comment style. Use
/**
when documenting symbols.@ -53,0 +84,4 @@
return selections_[index];
}
bke::GSpanAttributeWriter &operator[](const std::string &attribute_name);
operator[]
generally shouldn't be used for such non-trivial operations. Better use a method with a name.Also note that this should use
StringRef
instead ofconst std::string &
.@ -53,0 +100,4 @@
using SelectableRangeConsumer =
blender::FunctionRef<void(const IndexRange range,
const Span<float3> positions,
const std::string &selection_attribute_name)>;
Instead of
const std::string &
we generally either useStringRefNull
orStringRef
.@ -53,0 +102,4 @@
const Span<float3> positions,
const std::string &selection_attribute_name)>;
/**
* Traverses all ranges of control points posible select. Callback function is provided with a
Typo (
posible
), same somewhere else.Sorry for the late reply, was a bit busy this week. Got a few more comments, but overall looks good to me now.
@ -826,3 +828,2 @@
}
BKE_id_attributes_active_set(&curves_id->id, active_attribute.c_str());
if (layer) {
The pointer to the
CustomDataLayer
is unfortunately not stable when attributes are added/removed. Solayer
may be dangling here, so it's probably better not to use it but to check foractive_attribute
instead.@ -77,0 +110,4 @@
}
}
inline MutableSpan<bke::GSpanAttributeWriter> init_selection_writers(
inline
shouldn't be used here.Also, could this maybe just return the
Vector
instead of returning a span into the passed in vector?@ -78,2 +224,3 @@
const bke::AttrDomain selection_domain,
const eCustomDataType create_type)
const eCustomDataType create_type,
const bke::AttributeIDRef &attribute_id)
rename
attribute_id
toselection_name
or so in a few places, because that conveys more semantic information.@ -53,0 +88,4 @@
SelectableRangeConsumer range_consumer);
/**
* Same logic as in foreach_selectable_point_range, just ranges reference curves insted of
typo (
instead
)@ -184,0 +244,4 @@
bke::GSpanAttributeWriter &selection_attribute_writer_by_name(
MutableSpan<bke::GSpanAttributeWriter> selections, StringRef attribute_name);
inline void foreach_selection_attribute_writer(
The function implementation can be moved to a
.cc
file.@ -64,1 +64,3 @@
IndexMask retrieve_selected_points(const bke::CurvesGeometry &curves, IndexMaskMemory &memory)
IndexMask retrieve_selected_points(const bke::CurvesGeometry &curves,
IndexMaskMemory &memory,
const std::string &attribute_name)
Generally use
StringRef
orStringRefNull
instead ofattribute_name
.Sorry, I was in a hurry. You told me this already.
Thanks for working on this. Big picture it looks reasonable. Just left some smaller cleanup comments.
@ -164,3 +163,1 @@
".selection", bke::AttrDomain::Point);
selection.span.take_back(num_points_to_add).fill(true);
selection.finish();
for (const StringRef selection_name : ed::curves::get_curves_selection_attribute_names(curves)) {
We're already in the
ed::curves
namespace here, no need to write that again@ -63,2 +63,3 @@
IndexMask retrieve_selected_points(const bke::CurvesGeometry &curves, IndexMaskMemory &memory)
IndexMask retrieve_selected_points(const bke::CurvesGeometry &curves,
IndexMaskMemory &memory,
Also the non-const
memory
argument can be lastIt has default value for the
attribute_name
in a header. Should I add third version ofretrieve_selected_points
?Oh, my bad. I don't have a strong opinion, but a third overload does sound a bit nicer.
@ -77,0 +85,4 @@
selection_attribute_names;
}
Span<std::string> get_curves_all_selection_attribute_names()
How about
Span<StringRef>
, since these strings have static ownership?I'm doing it, but just for the future: what is the benefit?
Thanks for asking! Constructing
StringRef
from a static string is "free", whereas constructing astd::string
will have to allocate heap memory if the inline buffer (usually 11-14 bytes) isn't large enough. Also, just in general, using reference types likeSpan
is preferable to owning types likeArray
) since they give more flexibility about data ownership, etc.@ -77,0 +110,4 @@
}
}
Vector<bke::GSpanAttributeWriter> &init_selection_writers(
It seems unnecessary to return a reference to the vector here, and maybe a bit more confusing than necessary?
Or the argument could be removed and it could just be a return value?
Removed argument, but now it is tempting to use a one-liner:
Returning by value looks dangerous to me.
I think that's just something people have to know about spans and vectors. There are so many places you could make the same mistake, and using return values is very nice from a code readability perspective.
@ -77,0 +124,4 @@
return writers;
}
inline void finish_attribute_writers(MutableSpan<bke::GSpanAttributeWriter> attribute_writers)
Not sure these need to be inline, that could change to
static
@ -77,0 +157,4 @@
MutableSpan<bke::GSpanAttributeWriter> selection_writers = init_selection_writers(
writers_buffer, curves, selection_domain);
/* TODO: maybe add threading */
I'd remove this todo comment. This would only ever be able to use 3 threads, and there are potential pitfalls that probably wouldn't be worth it
@ -53,0 +73,4 @@
const Span<std::string> selection_attribute_names =
get_curves_all_selection_attribute_names());
using SelectableRangeConsumer = blender::FunctionRef<void(
This name feels a bit off to me. It seems to imply that it's some object oriented thing when it's really just a callback function. How about
SelectionRangeFn
or something?Also,
const
can be removed in this declaration, andblender::
can be removed too, we're already in that namespace@ -184,0 +231,4 @@
bke::GSpanAttributeWriter ensure_selection_attribute(
bke::CurvesGeometry &curves,
const bke::AttrDomain selection_domain,
const eCustomDataType create_type,
const
is meaningless for types passed by value in declarations. Same in a few other functions in this header now.I like to put
const
everywhere I can as a quick hint for eyes, that variable is not intended to change. Maybe it could save from faulty using it as reference.But don't have deep attachment to this. Fixed.
@ -184,0 +232,4 @@
bke::CurvesGeometry &curves,
const bke::AttrDomain selection_domain,
const eCustomDataType create_type,
const std::string &attribute_name = ".selection");
const std::string &
->StringRef
@ -77,0 +156,4 @@
bke::AttrDomain selection_domain,
blender::FunctionRef<void(bke::GSpanAttributeWriter &selection)> fn)
{
Vector<bke::GSpanAttributeWriter> writers_buffer = init_selection_writers(curves,
You can rely on the implicit conversion from Vector to MutableSpan when calling functions. That should make having the separate Vector and MutableSpan variables unnecessary everywhere (so here,
writers_buffer
is renamedselection_writers
and the originalselection_writers
is removed).That is what I was talking about yesterday :)
In that case
Vector
would be temporary (or r-value) and it's destructor will get called just before leaving expression. TheSpan
will be left initialized with invalid pointer.I hold myself yesterday from noting that though it's an easy thing to remember, but API should pass a much as possible things to compiler to worry about. There is enough things to keep in a head.
OK. Unless you suggest removing
Span
variable completely.In that case I don't like the fact that in functions like
select_box
conversion toSpan
will happen several times.I prefer getting
Span
once and then dealing with it as pleased.Converting to Span is free, no need to consider that from a performance perspective.
Duplicating all these variables just seems messy and gets in the way TBH
Don't merge it yet. It crashes on Release build.
Curves: Bezier handle selection supportto WIP: Curves: Bezier handle selection supportCrash in release, but not in debug? Are you on MSVC?
MacOS, VSCode.
Don't know if it is same thing, but I was cheking #117709 on XCode.
Maybe this PR has nothing to with the crash. I usually don't check Relase build.
@ -3129,1 +3112,3 @@
new_closest.curves_id = &curves_id;
const IndexMask elements(curves.attributes().domain_size(selection_domain));
const ed::curves::SelectionRangeFn range_consumer =
[&](IndexRange range, Span<float3> positions, StringRef selection_attribute_name) {
Problem is with this variable, captures by reference go crazy in
Release
build.Tried to play by passing one by one, only captures by value work well.
If to define type
SelectionRangeFn
as:instead of using
FunctionRef
everything works fine.Ah right, you can't assign a lambda to a
FunctionRef
directly (mentioned in the description ofFunctionRef
I think).Just use
auto range_consumer = [&]...;
WIP: Curves: Bezier handle selection supportto Curves: Bezier handle selection support@blender-bot build
Can you add some more information to the patch description?
Just checked, indeed little provided :)