GPv3: Split Stroke #115842

Open
Ayanfe Ibitoye wants to merge 22 commits from arryemoji/blender:gpv3-stroke-split into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
First-time contributor

Ports the split stroke operator to GPv3

Code mostly adapted from Delete and Duplicate operators

Resolves #113643

Ports the split stroke operator to GPv3 Code mostly adapted from Delete and Duplicate operators Resolves #113643
Ayanfe Ibitoye force-pushed gpv3-stroke-split from 263b35dbfb to ae3246c69b 2023-12-06 13:47:17 +01:00 Compare
Ayanfe Ibitoye added 1 commit 2023-12-06 13:50:03 +01:00
Ayanfe Ibitoye added 1 commit 2023-12-06 13:54:02 +01:00
Ayanfe Ibitoye added 1 commit 2023-12-06 14:05:56 +01:00
Ayanfe Ibitoye added 1 commit 2023-12-06 15:52:19 +01:00
Iliya Katushenock added this to the Grease Pencil project 2023-12-06 15:53:01 +01:00
Falk David requested review from Falk David 2023-12-07 10:50:15 +01:00
Falk David refused to review 2023-12-07 10:52:30 +01:00
Falk David requested review from Falk David 2023-12-07 10:53:04 +01:00
Falk David requested changes 2023-12-07 11:39:23 +01:00
Falk David left a comment
Member

I have some higher level thoughts about this.

In summary, the Split Strokes operator will make sure all the point selection ranges within a stroke become their own stroke. This means that
a) The number of points stays exactly the same and all the point attributes stay the same.
b) The number of curves/strokes changes so we need to change the offsets and propagate the curve attributes.

So we don't need to recreate a new CurvesGeometry. We can resize the existing CurvesGeometry and only change the number of strokes.

The duplicate_points function in grease_pencil_edit.cc is doing something similar. Note that for the duplicate operation we do need to add more points, which in the split case, we don't.

I have some higher level thoughts about this. In summary, the `Split Strokes` operator will make sure all the point selection ranges within a stroke become their own stroke. This means that a) The number of points stays exactly the same and all the point attributes stay the same. b) The number of curves/strokes changes so we need to change the `offsets` and propagate the curve attributes. So we don't need to recreate a new `CurvesGeometry`. We can `resize` the existing `CurvesGeometry` and only change the number of strokes. The `duplicate_points` function in `grease_pencil_edit.cc` is doing something similar. Note that for the duplicate operation we do need to add more points, which in the split case, we don't.
Ayanfe Ibitoye added 1 commit 2023-12-07 16:01:46 +01:00
Ayanfe Ibitoye requested review from Falk David 2023-12-07 16:03:57 +01:00
Ayanfe Ibitoye added 1 commit 2023-12-10 15:24:14 +01:00
Author
First-time contributor

I made changes here in case you hadn't seen @filedescriptor

I made changes here in case you hadn't seen @filedescriptor
Falk David requested review from Hans Goudey 2023-12-15 13:40:11 +01:00
Ayanfe Ibitoye added 1 commit 2024-01-02 00:55:51 +01:00
Ayanfe Ibitoye added 1 commit 2024-01-02 01:23:47 +01:00
Member

Hi, I'm not sure why selected and unselected cases are handled separately. Would it not work if we just change the offset to separate strokes based on selection_range. I mean for each range_i , do curve_counts.append(range.first()) and curve_counts.append(range.last()); so we can have 3 strokes (old_curve_count = 1) in results when selection_range is 1
I think I'll implement this roughly and share it here later

Hi, I'm not sure why selected and unselected cases are handled separately. Would it not work if we just change the offset to separate strokes based on selection_range. I mean for each range_i , do `curve_counts.append(range.first())` and `curve_counts.append(range.last());` so we can have 3 strokes (old_curve_count = 1) in results when selection_range is 1 I think I'll implement this roughly and share it here later
Author
First-time contributor

@PratikPB2123 I did want to combine the loops but wasn't sure how to do that. Your help is appreciated.

@PratikPB2123 I did want to combine the loops but wasn't sure how to do that. Your help is appreciated.
Member

Did some changes in existing "duplicate operator" code and got this working.
Points we're going to split the curve at, are end points of each "selection range":

+      dst_curve_counts.append(range.first());
+      dst_curve_counts.append(range.last() + 1);

Following code adds the new offset values to the end of new_curve_offsets then reorders this span/array in ascending order.
Feel free to try this.

I'm not sure whether this approach is correct. (@filedescriptor @HooglyBoogly knows better)

Something like this should work

diff --git a/source/blender/editors/curves/intern/curves_edit.cc b/source/blender/editors/curves/intern/curves_edit.cc
index 337d6f99bde..0f12ccb93a0 100644
--- a/source/blender/editors/curves/intern/curves_edit.cc
+++ b/source/blender/editors/curves/intern/curves_edit.cc
@@ -82,9 +82,13 @@ void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask)
           range.start() + points.first());
       curr_dst_point_start += range.size();

-      dst_curve_counts.append(range.size());
+      dst_curve_counts.append(range.first());
+      dst_curve_counts.append(range.last() + 1);
+
+      dst_to_src_curve.append(curve_i);
       dst_to_src_curve.append(curve_i);
       dst_cyclic.append(is_cyclic);
+      dst_cyclic.append(is_cyclic);
     }

     /* Join the first range to the end of the last range. */
@@ -105,15 +109,13 @@ void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask)
   bke::MutableAttributeAccessor attributes = curves.attributes_for_write();

   /* Delete selection attribute so that it will not have to be resized. */
-  attributes.remove(".selection");

-  curves.resize(old_points_num + num_points_to_add, old_curves_num + num_curves_to_add);
+  curves.resize(old_points_num, old_curves_num + num_curves_to_add);

   MutableSpan<int> new_curve_offsets = curves.offsets_for_write();
   array_utils::copy(dst_curve_counts.as_span(),
                     new_curve_offsets.drop_front(old_curves_num).drop_back(1));
-  offset_indices::accumulate_counts_to_offsets(new_curve_offsets.drop_front(old_curves_num),
-                                               old_points_num);
+  std::sort(new_curve_offsets.begin(), new_curve_offsets.end());

   /* Transfer curve and point attributes. */
   attributes.for_all([&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) {
@@ -134,10 +136,10 @@ void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask)
         break;
       }
       case bke::AttrDomain::Point: {
-        bke::attribute_math::gather(
+        /*bke::attribute_math::gather(
             attribute.span,
             dst_to_src_point,
-            attribute.span.slice(IndexRange(old_points_num, num_points_to_add)));
+            attribute.span.slice(IndexRange(old_points_num, num_points_to_add)));*/
         break;
       }
       default: {
@@ -158,11 +160,6 @@ void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask)

   curves.update_curve_types();
   curves.tag_topology_changed();
-
-  bke::SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
-      ".selection", bke::AttrDomain::Point);
-  selection.span.take_back(num_points_to_add).fill(true);
-  selection.finish();
 }

 void duplicate_curves(bke::CurvesGeometry &curves, const IndexMask &mask)
diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_ops.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_ops.cc
index 4b006748513..b77f66ff2a2 100644
--- a/source/blender/editors/grease_pencil/intern/grease_pencil_ops.cc
+++ b/source/blender/editors/grease_pencil/intern/grease_pencil_ops.cc
@@ -26,7 +26,6 @@ void ED_operatortypes_grease_pencil()
 void ED_operatormacros_grease_pencil()
 {
   wmOperatorType *ot;
-  wmOperatorTypeMacro *otmacro;

   /* Duplicate + Move = Interactively place newly duplicated strokes */
   ot = WM_operatortype_append_macro(
@@ -35,7 +34,4 @@ void ED_operatormacros_grease_pencil()
       "Make copies of the selected Grease Pencil strokes and move them",
       OPTYPE_UNDO | OPTYPE_REGISTER);
   WM_operatortype_macro_define(ot, "GREASE_PENCIL_OT_duplicate");
-  otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
-  RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
-  RNA_boolean_set(otmacro->ptr, "mirror", false);
 }
Did some changes in existing "duplicate operator" code and got this working. Points we're going to split the curve at, are end points of each "selection range": ``` + dst_curve_counts.append(range.first()); + dst_curve_counts.append(range.last() + 1); ``` Following code adds the new offset values to the end of `new_curve_offsets` then reorders this span/array in ascending order. Feel free to try this. I'm not sure whether this approach is correct. (@filedescriptor @HooglyBoogly knows better) Something like this should work ```Diff diff --git a/source/blender/editors/curves/intern/curves_edit.cc b/source/blender/editors/curves/intern/curves_edit.cc index 337d6f99bde..0f12ccb93a0 100644 --- a/source/blender/editors/curves/intern/curves_edit.cc +++ b/source/blender/editors/curves/intern/curves_edit.cc @@ -82,9 +82,13 @@ void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask) range.start() + points.first()); curr_dst_point_start += range.size(); - dst_curve_counts.append(range.size()); + dst_curve_counts.append(range.first()); + dst_curve_counts.append(range.last() + 1); + + dst_to_src_curve.append(curve_i); dst_to_src_curve.append(curve_i); dst_cyclic.append(is_cyclic); + dst_cyclic.append(is_cyclic); } /* Join the first range to the end of the last range. */ @@ -105,15 +109,13 @@ void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask) bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); /* Delete selection attribute so that it will not have to be resized. */ - attributes.remove(".selection"); - curves.resize(old_points_num + num_points_to_add, old_curves_num + num_curves_to_add); + curves.resize(old_points_num, old_curves_num + num_curves_to_add); MutableSpan<int> new_curve_offsets = curves.offsets_for_write(); array_utils::copy(dst_curve_counts.as_span(), new_curve_offsets.drop_front(old_curves_num).drop_back(1)); - offset_indices::accumulate_counts_to_offsets(new_curve_offsets.drop_front(old_curves_num), - old_points_num); + std::sort(new_curve_offsets.begin(), new_curve_offsets.end()); /* Transfer curve and point attributes. */ attributes.for_all([&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) { @@ -134,10 +136,10 @@ void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask) break; } case bke::AttrDomain::Point: { - bke::attribute_math::gather( + /*bke::attribute_math::gather( attribute.span, dst_to_src_point, - attribute.span.slice(IndexRange(old_points_num, num_points_to_add))); + attribute.span.slice(IndexRange(old_points_num, num_points_to_add)));*/ break; } default: { @@ -158,11 +160,6 @@ void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask) curves.update_curve_types(); curves.tag_topology_changed(); - - bke::SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>( - ".selection", bke::AttrDomain::Point); - selection.span.take_back(num_points_to_add).fill(true); - selection.finish(); } void duplicate_curves(bke::CurvesGeometry &curves, const IndexMask &mask) diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_ops.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_ops.cc index 4b006748513..b77f66ff2a2 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_ops.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_ops.cc @@ -26,7 +26,6 @@ void ED_operatortypes_grease_pencil() void ED_operatormacros_grease_pencil() { wmOperatorType *ot; - wmOperatorTypeMacro *otmacro; /* Duplicate + Move = Interactively place newly duplicated strokes */ ot = WM_operatortype_append_macro( @@ -35,7 +34,4 @@ void ED_operatormacros_grease_pencil() "Make copies of the selected Grease Pencil strokes and move them", OPTYPE_UNDO | OPTYPE_REGISTER); WM_operatortype_macro_define(ot, "GREASE_PENCIL_OT_duplicate"); - otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate"); - RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false); - RNA_boolean_set(otmacro->ptr, "mirror", false); } ```
Ayanfe Ibitoye added 2 commits 2024-01-04 19:27:44 +01:00
Author
First-time contributor

I managed to get it down to one loop. I made a to_ranges function so I could have the one range and iterate only over that.

I managed to get it down to one loop. I made a to_ranges function so I could have the one range and iterate only over that.
Falk David reviewed 2024-01-08 12:24:20 +01:00
@ -587,0 +591,4 @@
/**
* Finds all the index ranges for consecutive values in \a span.
*/
template<typename T> inline Vector<IndexRange> to_ranges(const Span<T> span)
Member

No need to reimplement this. Use array_utils::find_all_ranges (from here).

No need to reimplement this. Use `array_utils::find_all_ranges` ([from here](https://projects.blender.org/blender/blender/src/commit/4d56b696ece2e2376658afd1773c7de020ae2b79/source/blender/blenlib/BLI_array_utils.hh#L242)).
Author
First-time contributor

I was using array_utils::find_all_ranges before, but it takes a value and only returns the ranges for indices equal to that value, so I had to have two ranges for selected and unselected points. I added this so I could get all the ranges at once and only need one loop.

I was using `array_utils::find_all_ranges` before, but it takes a value and only returns the ranges for indices equal to that value, so I had to have two ranges for selected and unselected points. I added this so I could get all the ranges at once and only need one loop.
Ayanfe Ibitoye added 1 commit 2024-01-15 18:05:50 +01:00
Ayanfe Ibitoye added 1 commit 2024-01-15 21:56:13 +01:00
Falk David requested changes 2024-01-23 16:13:46 +01:00
Falk David left a comment
Member

The structure looks good to me, I left some cleanup comments.
Would like @HooglyBoogly to take a look too.

The structure looks good to me, I left some cleanup comments. Would like @HooglyBoogly to take a look too.
@ -592,0 +705,4 @@
});
array_utils::copy(cyclic.as_span(), curves.cyclic_for_write());
curves.remove_attributes_based_on_types();
Member

The types of the curves did not change, so this line can be removed.

The types of the curves did not change, so this line can be removed.
arryemoji marked this conversation as resolved
@ -592,0 +707,4 @@
curves.remove_attributes_based_on_types();
return;
Member

Remove return, not needed

Remove `return`, not needed
arryemoji marked this conversation as resolved
@ -592,0 +717,4 @@
Object *object = CTX_data_active_object(C);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
const blender::bke::AttrDomain selection_domain = ED_grease_pencil_selection_domain_get(scene->toolsettings);
Member

Looks like this is unused, can be removed.

Looks like this is unused, can be removed.
arryemoji marked this conversation as resolved
@ -592,0 +723,4 @@
const Array<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
IndexMaskMemory memory;
const IndexMask mask = ed::greasepencil::retrieve_editable_and_selected_elements(
Member

I believe this operator should only work in point selection mode? I that case this should just use retrieve_editable_and_selected_points

I believe this operator should only work in point selection mode? I that case this should just use `retrieve_editable_and_selected_points`
arryemoji marked this conversation as resolved
@ -592,0 +751,4 @@
/* callbacks */
ot->exec = grease_pencil_stroke_split_exec;
ot->poll = editable_grease_pencil_poll;
Member

-> editable_grease_pencil_point_selection_poll

-> `editable_grease_pencil_point_selection_poll`
arryemoji marked this conversation as resolved
Ayanfe Ibitoye added 2 commits 2024-01-23 16:42:25 +01:00
Member

While testing this PR locally, I noticed that when selecting the first point, then using the operator, it does not create a new stroke for that point.

While testing this PR locally, I noticed that when selecting the first point, then using the operator, it does not create a new stroke for that point.
Ayanfe Ibitoye added 1 commit 2024-01-23 18:01:59 +01:00
Author
First-time contributor

While testing this PR locally, I noticed that when selecting the first point, then using the operator, it does not create a new stroke for that point.

Figured out what I did wrong. Fixed now.

> While testing this PR locally, I noticed that when selecting the first point, then using the operator, it does not create a new stroke for that point. Figured out what I did wrong. Fixed now.
Falk David requested changes 2024-02-06 12:19:14 +01:00
Falk David left a comment
Member

The code for this looks generally fine now, one comment

The code for this looks generally fine now, one comment
@ -252,0 +252,4 @@
/**
* Finds all the index ranges for consecutive values in \a span.
*/
template<typename T> inline Vector<IndexRange> to_ranges(const Span<T> span)
Member

I'd expect this to return OffsetIndices<int>, which would make this a bit better. Indexing into OffsetIndices with operator[] still gives you an IndexRange so it shouldn't change the rest of your code :)

I'd expect this to return `OffsetIndices<int>`, which would make this a bit better. Indexing into `OffsetIndices` with `operator[]` still gives you an `IndexRange` so it shouldn't change the rest of your code :)
Author
First-time contributor

I've done this. If there's a better way to handle the backing vector, please let me know. I couldn't find any other examples so I went this route. Thanks!

I've done this. If there's a better way to handle the backing vector, please let me know. I couldn't find any other examples so I went this route. Thanks!
Iliya Katushenock reviewed 2024-02-06 12:23:15 +01:00
@ -408,0 +409,4 @@
/** \name Split Stroke Operator
* \{ */
static void split_points(bke::CurvesGeometry &curves, const IndexMask &mask)

It is this is not the same thing as remove_points_and_split?

It is this is not the same thing as `remove_points_and_split`?
Member

No, remove_points_and_split will remove points, this doesn't remove them. It creates new curves.
But I agree, there are some overlapping things. Could be refactored at some point.

No, `remove_points_and_split` will remove points, this doesn't remove them. It creates new curves. But I agree, there are some overlapping things. Could be refactored at some point.

remove_points_and_split(mask) + remove_points_and_split(!mask) != split_points(mask) ?

`remove_points_and_split(mask)` + `remove_points_and_split(!mask)` != `split_points(mask)` ?
Member

I would argue remove_points_and_split = split_points + curves_copy_point_selection ;)

I would argue `remove_points_and_split` = `split_points` + `curves_copy_point_selection` ;)
Ayanfe Ibitoye added 1 commit 2024-02-06 16:58:52 +01:00
Ayanfe Ibitoye added 3 commits 2024-03-01 11:07:56 +01:00
Ayanfe Ibitoye added 2 commits 2024-04-05 21:18:01 +02:00
Ayanfe Ibitoye requested review from Falk David 2024-04-05 21:20:05 +02:00
Merge conflict checking is in progress. Try again in few moments.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u gpv3-stroke-split:arryemoji-gpv3-stroke-split
git checkout arryemoji-gpv3-stroke-split
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
4 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#115842
No description provided.