GPv3: Select Alternate #109240

Merged
Falk David merged 6 commits from Lorenzo-Carpaneto/blender:gpv3-select-alternate into main 2023-06-23 11:34:06 +02:00
Contributor

Adds a "Select Alternate" operator for the new Grease Pencil data type.
This replaces the previous gpencil.select_alternate.

Resolves #109204.

Adds a "Select Alternate" operator for the new Grease Pencil data type. This replaces the previous `gpencil.select_alternate`. Resolves #109204.
Iliya Katushenock added this to the Grease Pencil project 2023-06-22 14:12:55 +02:00
Falk David requested review from Falk David 2023-06-22 14:15:33 +02:00
Falk David requested changes 2023-06-22 14:27:27 +02:00
Falk David left a comment
Member

A few initial comments.

A few initial comments.
@ -275,0 +278,4 @@
curves, ATTR_DOMAIN_POINT, CD_PROP_BOOL);
const VArray<bool> cyclic = curves.cyclic();
BLI_assert_msg(selection.span.type().is<bool>(), "Error: Cannot alternate select on selection type different than bool.");
Member

The ensure_selection_attribute call already makes sure we have a bool attribute. No need for the assert here.

The `ensure_selection_attribute` call already makes sure we have a `bool` attribute. No need for the assert here.
Member

It doesn't actually. The type argument is only used when the selection attribute doesn't exist yet. So if the selection already has a float type, this operator will have to support that or change the attribute's type to boolean.

It doesn't actually. The type argument is only used when the selection attribute doesn't exist yet. So if the selection already has a float type, this operator will have to support that or change the attribute's type to boolean.
Author
Contributor

what do we want to do? We could template the function and support both selection; we could change the selection type, we could do nothing and prevent the user to use this if the type of selection isn't bool...

what do we want to do? We could template the function and support both selection; we could change the selection type, we could do nothing and prevent the user to use this if the type of selection isn't bool...
Member

Since there is no exposed way of creating a float selection at the moment, I would just remove the assert and leave it as is.

Since there is no exposed way of creating a `float` selection at the moment, I would just remove the assert and leave it as is.
Author
Contributor

Then we are ready to merge this pr I think :)

Then we are ready to merge this pr I think :)
Lorenzo-Carpaneto marked this conversation as resolved
@ -275,0 +285,4 @@
for (const int curve_i : range) {
const IndexRange points = points_by_curve[curve_i];
if (has_anything_selected(selection.span.slice(points))) {
Member

I think it make sense to avoid the indentation and continue early.

if (!has_anything_selected(selection.span.slice(points))) {
   continue;
I think it make sense to avoid the indentation and continue early. ``` if (!has_anything_selected(selection.span.slice(points))) { continue; ```
Lorenzo-Carpaneto marked this conversation as resolved
@ -275,0 +311,4 @@
}
}
else {
if (!select_ends)
Member

Missing brackets.
For formatting issues like these, please make sure to use clang-format. See https://wiki.blender.org/wiki/Tools/ClangFormat

Missing brackets. For formatting issues like these, please make sure to use `clang-format`. See https://wiki.blender.org/wiki/Tools/ClangFormat
Lorenzo-Carpaneto marked this conversation as resolved
@ -198,0 +233,4 @@
"select_ends",
true,
"Select Ends",
"Select the first and enable the selection of the last point of each stroke");
Member

"(De)select the first and last point of each stroke"

"(De)select the first and last point of each stroke"
Author
Contributor

This description is not precise though, because we select the first point but the last point is selected only if we get there alternated (e.g. if we have two points we select only the first one....).
Do I change the description anyway?

This description is not precise though, because we select the first point but the last point is selected only if we get there alternated (e.g. if we have two points we select only the first one....). Do I change the description anyway?
Member

Hm looking at the old operator, I think it makes sense to keep the same behavior. What we should do is call this option deselect_ends (everywhere it's used).
If deselect_ends is true, then we just deselect the fist and last point. Otherwise we leave the selection as is.
Then the description would be "Deselect the first and last point of each stroke".

Hm looking at the old operator, I think it makes sense to keep the same behavior. What we should do is call this option `deselect_ends` (everywhere it's used). If `deselect_ends` is true, then we just deselect the fist and last point. Otherwise we leave the selection as is. Then the description would be "Deselect the first and last point of each stroke".
Author
Contributor

So right now it is coded as the old behavior, we just need to decide on description and if we want to use select_ends or deselect_ends :)
I will make it as similar as the old version as possible, thus using deselect_ends

So right now it is coded as the old behavior, we just need to decide on description and if we want to use select_ends or deselect_ends :) I will make it as similar as the old version as possible, thus using deselect_ends
Lorenzo-Carpaneto marked this conversation as resolved
Falk David reviewed 2023-06-22 14:47:19 +02:00
@ -275,0 +288,4 @@
if (has_anything_selected(selection.span.slice(points))) {
bool prev_selected = !select_ends;
for (int point_i = points.first(); point_i <= points.last(); point_i++) {
Member

I think this loop can now be simplified:

for (const int point_i : points.index_range()) {
   selection[point_i] = (point_i % 2 == 0);
}
I think this loop can now be simplified: ``` for (const int point_i : points.index_range()) { selection[point_i] = (point_i % 2 == 0); } ```
Author
Contributor

mmm, this is not entirely right I think: the select ends param makes us start or from first or from second point... So the selection is kind of "inverted" based on this params (I am basing this fact on the older version of gp, that works like this)

mmm, this is not entirely right I think: the select ends param makes us start or from first or from second point... So the selection is kind of "inverted" based on this params (I am basing this fact on the older version of gp, that works like this)
Member

Ah then you use (point_i % 2 == unselect_ends) instead.

Ah then you use `(point_i % 2 == unselect_ends)` instead.
Member

It's weird, but I noticed we can't use points.index_range(), because it always starts at 0. While points.first() gives the correct offset.
So keeping
for (int point_i = points.first(); point_i <= points.last(); point_i++) {
is the right thing to do.

It's weird, but I noticed we can't use `points.index_range()`, because it always starts at `0`. While `points.first()` gives the correct offset. So keeping ` for (int point_i = points.first(); point_i <= points.last(); point_i++) {` is the right thing to do.
const int half_of_size = points.size() / 2;
const IndexRange selected = points.shift(select_ends ? 0 : 1);
const IndexRange deselected = points.shift(select_ends ? 1 : 0);
for (const int index : IndexRange(half_of_size)) {
  const int selected_index = selected[index * 2];
  const int deselected_index = deselected[index * 2];
  selection_typed[selected_index] = true;
  selection_typed[deselected_index] = false;
}
selection_typed.last() = select_ends;

Maybe something like this?

```Cpp const int half_of_size = points.size() / 2; const IndexRange selected = points.shift(select_ends ? 0 : 1); const IndexRange deselected = points.shift(select_ends ? 1 : 0); for (const int index : IndexRange(half_of_size)) { const int selected_index = selected[index * 2]; const int deselected_index = deselected[index * 2]; selection_typed[selected_index] = true; selection_typed[deselected_index] = false; } selection_typed.last() = select_ends; ``` Maybe something like this?
Author
Contributor

@mod_moder to me this seems more difficult to read than what I had originally written though...

@mod_moder to me this seems more difficult to read than what I had originally written though...
Author
Contributor

Ah then you use (point_i % 2 == unselect_ends) instead.

Are we entirely sure that the first point is always 0? Because I see that index_range has a start_ init param

> Ah then you use `(point_i % 2 == unselect_ends)` instead. Are we entirely sure that the first point is always 0? Because I see that index_range has a start_ init param
Member

@SietseB is right, the for loop can stay as is.

@SietseB is right, the for loop can stay as is.

@Lorenzo-Carpaneto The main reason for my approach is that it is:

  • It doesn't require you to calculate each step of the loop in your head that you know the result.
  • It can be multi-threaded. Remember, these arrays can be millions of points long.
@Lorenzo-Carpaneto The main reason for my approach is that it is: - It doesn't require you to calculate each step of the loop in your head that you know the result. - It can be multi-threaded. Remember, these arrays can be millions of points long.
Author
Contributor

@mod_moder These are good motivations, but I think there might be problems with your solution.
@SietseB and @filedescriptor said that points.index_range() is not working (and that function returns: IndexRange(size_);. So I fear that your approach suffers from the same bug. So for now maybe better keep the loop as is, and as soon as we fix the problem with iteration on indexRange, we change the code

@mod_moder These are good motivations, but I think there might be problems with your solution. @SietseB and @filedescriptor said that `points.index_range()` is not working (and that function returns: `IndexRange(size_);`. So I fear that your approach suffers from the same bug. So for now maybe better keep the loop as is, and as soon as we fix the problem with iteration on indexRange, we change the code

IndexRange is iterator factory itself. If you use index_range() method, you will create new IndexRange for iterating on something. In my case, points sampled directly, look at const IndexRange selected = points.shift(select_ends ? 0 : 1);... lines.

IndexRange is iterator factory itself. If you use `index_range()` method, you will create new IndexRange for iterating on something. In my case, `points` sampled directly, look at `const IndexRange selected = points.shift(select_ends ? 0 : 1);...` lines.
Author
Contributor

I was referring to for (const int index : IndexRange(half_of_size))

I was referring to `for (const int index : IndexRange(half_of_size))`

const int selected_index = selected[index * 2];
You can sampling IndexRage to maping indices.

`const int selected_index = selected[index * 2];` You can sampling IndexRage to maping indices.
Author
Contributor

@mod_moder @HooglyBoogly I looked better at the problem that @SietseB was saying about IndexRange and I am now convinced that the solution proposed should work :) I have made the change (a bit different from suggestion but basically the same) check it out :)

@mod_moder @HooglyBoogly I looked better at the problem that @SietseB was saying about IndexRange and I am now convinced that the solution proposed should work :) I have made the change (a bit different from suggestion but basically the same) check it out :)
Lorenzo-Carpaneto marked this conversation as resolved
Lorenzo-Carpaneto force-pushed gpv3-select-alternate from 38ba283662 to c66efbd41b 2023-06-22 15:27:36 +02:00 Compare
Lorenzo-Carpaneto requested review from Falk David 2023-06-22 15:30:15 +02:00
Hans Goudey reviewed 2023-06-22 18:26:43 +02:00
@ -275,0 +289,4 @@
}
bool prev_selected = deselect_ends;
for (int point_i = points.first(); point_i <= points.last(); point_i++) {
Member

for (const int point : points) {

`for (const int point : points) {`
Author
Contributor

We are just discussing about this in the other thread, please join in the dicussion :D

We are just discussing about this in the other thread, please join in the dicussion :D
Member

Ah sorry, haha
Think I agree with Iliya in principle (whenever possible, better to write loops in a parallel way, so they have no dependence on the previous iteration)

Ah sorry, haha Think I agree with Iliya in principle (whenever possible, better to write loops in a parallel way, so they have no dependence on the previous iteration)
Lorenzo-Carpaneto marked this conversation as resolved
Lorenzo-Carpaneto added 1 commit 2023-06-22 23:52:04 +02:00
Iliya Katushenock reviewed 2023-06-22 23:57:07 +02:00
@ -275,0 +289,4 @@
}
for (const int offset : IndexRange(points.size())) {
selection_typed[points.first() + offset] = deselect_ends ? offset % 2 : !(offset % 2);
for (const int index : points.index_range()) {
  selection_typed[points[index]] = deselect_ends ? index % 2 : !(index % 2);

This one is exactly the same

```Cpp for (const int index : points.index_range()) { selection_typed[points[index]] = deselect_ends ? index % 2 : !(index % 2); ``` This one is exactly the same
Author
Contributor

According to @SietseB (previous comment in the other thread)

It's weird, but I noticed we can't use points.index_range(), because it always starts at 0. While points.first() gives the correct offset.

This way should avoid this problem (cause we need to start from 0)

According to @SietseB (previous comment in the other thread) ``` It's weird, but I noticed we can't use points.index_range(), because it always starts at 0. While points.first() gives the correct offset. ``` This way should avoid this problem (cause we need to start from 0)

IndexRange(points.size()) == points.index_range()
points.first() + 0..1..2... == points[0...1...2...]

`IndexRange(points.size())` == `points.index_range()` `points.first() + 0..1..2...` == `points[0...1...2...]`
Author
Contributor

Ah, you are totally right, pushing the proposed version, it's a bit more readable and efficient :)

Ah, you are totally right, pushing the proposed version, it's a bit more readable and efficient :)
Lorenzo-Carpaneto marked this conversation as resolved
Lorenzo-Carpaneto added 1 commit 2023-06-23 00:45:39 +02:00
Falk David requested changes 2023-06-23 10:49:50 +02:00
Falk David left a comment
Member

Looks very close now! Just one comment on the todo.

Looks very close now! Just one comment on the `todo`.
@ -198,0 +205,4 @@
grease_pencil.foreach_editable_drawing(
scene->r.cfra, [&](int /*drawing_index*/, GreasePencilDrawing &drawing) {
// todo: check that some points of stroke are selected
Member

We can do this with ed::curves::has_anything_selected. This check should probably be done inside of select_alternate though and return early if nothing in the entire CurvesGeometry is selected.

We can do this with `ed::curves::has_anything_selected`. This check should probably be done inside of `select_alternate` though and return early if nothing in the entire `CurvesGeometry` is selected.
Author
Contributor

The check is already there, just forgot the comment :(

The check is already there, just forgot the comment :(
Member

The check inside the loop is fine, what I mean is that there should be a check at the top of the function like this:

if (!has_anything_selected(curves)) {
   return;
}
The check inside the loop is fine, what I mean is that there should be a check at the top of the function like this: ``` if (!has_anything_selected(curves)) { return; } ```
Lorenzo-Carpaneto marked this conversation as resolved
Lorenzo-Carpaneto added 1 commit 2023-06-23 11:12:27 +02:00
Member

@Lorenzo-Carpaneto The check inside the loop is fine, what I meant is that there should be a check at the top of the function like this:

if (!has_anything_selected(curves)) {
   return;
}
@Lorenzo-Carpaneto The check inside the loop is fine, what I meant is that there should be a check at the top of the function like this: ``` if (!has_anything_selected(curves)) { return; } ```
Lorenzo-Carpaneto added 1 commit 2023-06-23 11:33:02 +02:00
Falk David approved these changes 2023-06-23 11:33:37 +02:00
Falk David left a comment
Member

Looking good!

Looking good!
Falk David merged commit fdc0402a50 into main 2023-06-23 11:34:06 +02:00
Falk David referenced this issue from a commit 2023-06-23 11:34:08 +02:00
Pratik Borhade reviewed 2023-06-24 11:57:21 +02:00
@ -275,0 +280,4 @@
const OffsetIndices points_by_curve = curves.points_by_curve();
bke::GSpanAttributeWriter selection = ensure_selection_attribute(
curves, ATTR_DOMAIN_POINT, CD_PROP_BOOL);
Member

Hi, looks like this operator is only supported in point selection mode.
In that case, we could change the poll function to editable_grease_pencil_point_selection_poll

Hi, looks like this operator is only supported in point selection mode. In that case, we could change the poll function to `editable_grease_pencil_point_selection_poll`
Member

That is correct, yes. Should be changed.

That is correct, yes. Should be changed.
Member

I'll commit then directly? (or want a PR again?)

Edit: pushed to main: 51a5be8913

I'll commit then directly? (or want a PR again?) Edit: pushed to main: 51a5be891308e174e2bd8bc862bdde41545b08d1
Member

Thank you @PratikPB2123

Thank you @PratikPB2123
Lorenzo-Carpaneto marked this conversation as resolved
Sign in to join this conversation.
No reviewers
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
6 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: blender/blender#109240
No description provided.