GPv3: Separate operator #116715
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#116715
Loading…
Reference in New Issue
No description provided.
Delete Branch "mendio/blender:GPv3_OP_Separate"
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?
This PR adds the Separate operator from GPv2.
Changes:
In GPv2 there were three modes: Selected Point, Selected Strokes and Active Layer. For better consistency with meshes Separate Operator, there are now three modes:
Also Multiframe selection is now supported.
@ -28,8 +29,11 @@
#include "DEG_depsgraph.hh"
#include "DNA_material_types.h"
This include is already here, just a few lines above
@ -37,6 +41,7 @@
#include "UI_resources.hh"
namespace blender::ed::greasepencil {
static void remove_unused_materials(Object *object_src, Main *bmain);
Doesn't look like this prototype is necessary
@ -1643,0 +1653,4 @@
/* Selected Points/Strokes */
SELECTED = 0,
/* By Material */
MATERIAL,
Add
= 1
,= 2
. I've seen people do cleanups to give these explicit values before, since they can be stored in blend files.@ -1643,0 +1699,4 @@
}
static bke::greasepencil::Layer &get_layer_dst(const int layer_index,
GreasePencil &grease_pencil_src,
const GreasePencil &grease_pencil_src
@ -1643,0 +1719,4 @@
const bke::AttributeAccessor src_attributes = grease_pencil_src.attributes();
bke::MutableAttributeAccessor dst_attributes = grease_pencil_dst.attributes_for_write();
bke::gather_attributes(src_attributes,
Most of the variables above can be passed directly to
gather_attributes
like this:I passing most of the variables but had to keep
src_to_dst_layer
vector.Using
{layer_index}
gives an error: C2668: 'blender::bke::gather_attributes': ambiguous call to overloaded function@ -1643,0 +1737,4 @@
Object *object_src)
{
using namespace bke::greasepencil;
bool result = false;
Maybe there's a clearer name than "result". Maybe "changed"?
@ -1643,0 +1752,4 @@
bke::CurvesGeometry &curves_src = info.drawing.strokes_for_write();
IndexMaskMemory memory;
IndexMask selected_points = ed::curves::retrieve_selected_points(curves_src, memory);
This newline isn't really helpful IMO, better to visually connect it to the
retrieve_selected_points
. Also, declare theIndexMask
const@ -1643,0 +1759,4 @@
/* Insert Keyframe at current frame/layer */
Layer &layer_dst = get_layer_dst(info.layer_index, grease_pencil_src, grease_pencil_dst);
grease_pencil_dst.insert_blank_frame(layer_dst, info.frame_number, 0, BEZT_KEYTYPE_KEYFRAME);
I don't think frames can be inserted into a grease pencil data-block in parallel. So this can't be inside of a
parallel_for_each
loop really. Either the frames could be added first, or this could just be a regular for loop@ -1643,0 +1767,4 @@
/* Copy selected points/strokes to new CurvesGeometry. */
const bke::AnonymousAttributePropagationInfo propagation_info{};
drawing_dst->strokes_for_write() = bke::curves_copy_point_selection(
curves_src, selected_points, propagation_info);
Pass
{}
intocurves_copy_point_selection
for the propagation info rather than declaring a local variable@ -1643,0 +1822,4 @@
Object *object_dst = duplicate_grease_pencil_object(
bmain, scene, view_layer, base_prev, grease_pencil_src);
GreasePencil &grease_pencil_dst = *static_cast<GreasePencil *>(object_dst->data);
/* Create Layer. */
Try to avoid comments describing the direct behavior of the code like "create layer" or "select all strokes" or "insert keyframe". These things should be obvious from the code itself. Comments can be a waste of space or a crutch in cases like that-- they're best reserved for describing why decisions were made or non-obvious aspects.
@ -1643,0 +1834,4 @@
/* Select all strokes */
IndexMaskMemory memory;
IndexMask strokes = retrieve_editable_strokes(
*static_cast<Object *>(object_src), info.drawing, memory);
I don't think this
static_cast
is necessary@ -1643,0 +1836,4 @@
IndexMask strokes = retrieve_editable_strokes(
*static_cast<Object *>(object_src), info.drawing, memory);
if (!strokes.is_empty()) {
Reverse this if statement and use
continue
inside it to un-indent the rest of the codeIt was my first intention, but
continue
doesn't seem to work: "a continue statement may only be used within a loop"Ah, right, since this is in a lambda,
return
, will do the trick@ -1643,0 +1877,4 @@
return result;
}
static bool grease_pencil_separate_material(bContext *C,
Use references instead of pointers for values that are expected to be non-null
@ -1643,0 +1884,4 @@
Base *base_prev,
Object *object_src)
{
Unnecessary newline
@ -1643,0 +1893,4 @@
/* Create a new object for each material. */
for (const int mat_i : IndexRange(object_src->totcol)) {
Unnecessary newline
@ -1643,0 +1912,4 @@
const Array<MutableDrawingInfo> drawings_src = retrieve_editable_drawings(*scene,
grease_pencil_src);
threading::parallel_for_each(drawings_src, [&](const MutableDrawingInfo &info) {
/* Get geometry of current drawing. */
Another example of comments that aren't worth it
@ -1643,0 +1918,4 @@
IndexMask strokes = retrieve_editable_strokes_by_material(
*static_cast<Object *>(object_src), info.drawing, memory, mat_i);
if (!strokes.is_empty()) {
Flip condition and continue to un-indent code
same problem as in #116715 (comment)
@ -1643,0 +1924,4 @@
/* Insert Keyframe at current frame/layer */
Layer &layer_dst = get_layer_dst(info.layer_index, grease_pencil_src, grease_pencil_dst);
grease_pencil_dst.insert_blank_frame(
This doesn't look threadsafe, make the loop non-parallel or do this step separately first
@ -1643,0 +1961,4 @@
static int grease_pencil_separate_exec(bContext *C, wmOperator *op)
{
using namespace blender;
This code is already inside the
blender
namespace@ -1643,0 +1975,4 @@
bool has_selection = false;
/* Cancel if nothing selected. */
if ((mode == SeparateMode::SELECTED)) {
Unnecessary parentheses here. Also, would be clearer to move these checks inside of the relevant switch cases
@ -1643,0 +1978,4 @@
if ((mode == SeparateMode::SELECTED)) {
const Array<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene,
grease_pencil_src);
threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
has_anything_selected
is already selected anywaybool has_selection = false;
in this case, there's room for improvement to make the code more readable)std::all_of(drawings.begin(), drawings.end(), [&](const MutableDrawingInfo &drawing) { return !ed::curves::has_anything_selected(drawing.strokes()); });
I had to use std::any_of in this case
@ -1643,0 +2026,4 @@
WM_cursor_wait(false);
if (changed) {
Unnecessary empty line
@ -373,0 +425,4 @@
if (material_index == mat_i) {
return editable_material_indices.contains(material_index);
}
else {
else after return is unnecessary
Thanks for looking into this. I don't have a strong opinion about the changed functionality. It seems reasonable. Let me know if you have questions about any of my inline comments.
Thanks for the review @HooglyBoogly, I hope I managed to resolve all the comments.
@ -1643,0 +1712,4 @@
src_to_dst_layer.append(layer_index);
bke::gather_attributes(grease_pencil_src.attributes(),
bke::AttrDomain::Layer,
{},
It should be possible to use
Span({layer_index}),
here, directly at the call site@ -1643,0 +1757,4 @@
drawing_dst->strokes_for_write() = bke::curves_copy_point_selection(
curves_src, selected_points, {});
/* Delete selected points from current drawing. */
This is another case where the code says everything the comment does (just as clearly IMO), making the comment redundant
@ -1643,0 +1782,4 @@
DEG_id_tag_update(&grease_pencil_dst.id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(&C, NC_OBJECT | ND_DRAW, &grease_pencil_dst);
/* Remove unused material slots from source object. */
And here too-- the comment says "remove unused materials" and the code says the same thing
@ -1643,0 +1802,4 @@
/* Create a new object for each layer. */
for (const Layer *layer_src : grease_pencil_src.layers()) {
Unnecessary newline here
@ -1643,0 +1819,4 @@
for (const MutableDrawingInfo &info : drawings_src) {
bke::CurvesGeometry &curves_src = info.drawing.strokes_for_write();
IndexMaskMemory memory;
const IndexMask strokes = retrieve_editable_strokes(object_src, info.drawing, memory);
And here
@ -1643,0 +1831,4 @@
/* Copy strokes to new CurvesGeometry. */
Drawing *drawing_dst = grease_pencil_dst.get_editable_drawing_at(&layer_dst,
info.frame_number);
const bke::AnonymousAttributePropagationInfo propagation_info{};
Just pass
{}
to both functions individually, that seems better than having the meaningless local variable@ -1643,0 +1876,4 @@
/* Create a new object for each material. */
for (const int mat_i : IndexRange(object_src.totcol)) {
if (mat_i == 0) {
Rather than checking the material index every loop, it's simpler to use
IndexRange(object_src.totcol).drop_front(1)
@ -1643,0 +1898,4 @@
IndexMaskMemory memory;
const IndexMask strokes = retrieve_editable_strokes_by_material(
*static_cast<Object *>(&object_src), info.drawing, memory, mat_i);
Static cast is unnecessary, same with newline after this
@ -1643,0 +1932,4 @@
}
if (changed) {
/* Remove unused material slots from source object. */
Redundant comment
@ -1777,0 +1895,4 @@
};
if (changed) {
Unnecessary whitespace
@ -1777,0 +1996,4 @@
/* Create a new object for each material. */
for (const int mat_i : IndexRange(object_src.totcol).drop_front(1)) {
Unnecessary whitespace
@ -373,0 +396,4 @@
IndexMask retrieve_editable_strokes_by_material(Object &object,
const bke::greasepencil::Drawing &drawing,
IndexMaskMemory &memory,
const int &mat_i)
Pass int by value, that's typical for small structs or values
Did a pass over this and it generally looks good to me. I just have two remarks
@ -1777,0 +1826,4 @@
return object_dst;
}
static bke::greasepencil::Layer &get_layer_dst(const int layer_index,
The name is a bit generic for what the function does. Maybe
find_or_create_layer_by_name
is a bit more specific.@ -1777,0 +1844,4 @@
bke::gather_attributes(grease_pencil_src.attributes(),
bke::AttrDomain::Layer,
{},
{".layers"},
I'm not sure what the
".layers"
attribute is. I think this can just be{}
.@ -1777,0 +1927,4 @@
/* Create a new object for each layer. */
for (const Layer *layer_src : grease_pencil_src.layers()) {
if (layer_src->is_selected() || layer_src->is_locked()) {
Why are selected layers skipped here?
We need to keep one of the layers on the source object, these lines prevent the creation of a new object for the selected layer.
In by_material I use the same concept but ommiting the first material because an IndexRange is used there:
for (const int mat_i : IndexRange(object_src.totcol).drop_front(1))
@ -1801,0 +2094,4 @@
grease_pencil_src);
has_selection = std::any_of(
drawings.begin(), drawings.end(), [&](const MutableDrawingInfo &info) {
return ed::curves::has_anything_selected(info.drawing.strokes());
Guess this can be simplified. Get
selection_attribute_span
, then just checkselection.contains()
IMO this is fine-- this function can deal with various attribute-stored-as-single-value cases a bit better.
Hi, found few changes
@ -4419,3 +4419,3 @@
op_menu("GPENCIL_MT_layer_active", {"type": 'Y', "value": 'PRESS'}),
# Merge Layer
("gpencil.layer_merge", {"type": 'M', "value": 'PRESS', "shift": True, "ctrl": True}, None),
("gpencil.layer_merge", {"type": 'M', "value": 'PRESS', "shift": True, "ctrl": True}, None),
trailing whitespace
@ -5800,2 +5800,4 @@
layout.menu("VIEW3D_MT_mirror")
layout.separator()
trailing whitespace
@ -5802,0 +5802,4 @@
layout.separator()
layout.operator_menu_enum("grease_pencil.separate", "mode", text="Separate")
trailing whitespace
@ -1801,0 +1863,4 @@
}
/* Transfer Layer attributes. */
bke::gather_attributes(grease_pencil_src.attributes(),
getting assert inside this function when source object has only one layer.
The assert is in line 945 in
attributte_access.cc
when it try to use the functioncopy_attributes
.Really don't know if there is a problem with my code or inside that function
@ -1801,0 +1909,4 @@
info.frame_number);
drawing_dst.strokes_for_write() = bke::curves_copy_point_selection(
curves_src, selected_points, {});
curves_src.remove_points(selected_points, {});
This will only dissolve points and curve won't split after that.
Instead use:
curves_src = remove_points_and_split(curves_src, selected_points);
@ -1801,0 +2083,4 @@
const SeparateMode mode = SeparateMode(RNA_enum_get(op->ptr, "mode"));
bool changed = false;
bool has_selection = false;
I think this can be moved inside
case SeparateMode::SELECTED
const bool has_selection = std::any_of()
@ -1801,0 +1915,4 @@
drawing_dst.tag_topology_changed();
changed = true;
};
extra semicolon
@ -1801,0 +1992,4 @@
drawing_dst.tag_topology_changed();
changed = true;
};
extra semicolon
@ -1801,0 +2059,4 @@
WM_event_add_notifier(&C, NC_OBJECT | ND_DRAW, &grease_pencil_dst);
changed = true;
};
extra semicolon
@ -1801,0 +1931,4 @@
DEG_id_tag_update(&grease_pencil_dst.id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(&C, NC_OBJECT | ND_DRAW, &grease_pencil_dst);
remove_unused_materials(&bmain, &object_src);
I don't think we should clear unused materials of source object (neither legacy operator does)
@ -1801,0 +1948,4 @@
GreasePencil &grease_pencil_src = *static_cast<GreasePencil *>(object_src.data);
/* Create a new object for each layer. */
I'm not sure if this is the plan. @filedescriptor ?
If plan is to separate "selected" layers then I would expect to move them to single object instead of creating new object for each layer
I am not sure. It seems a bit strange indeed to seperate into individual objects. I would expect it to seperate the selection (or just the active) into one new object.
As I mentioned in the PR description in GPv2 there were three modes for the Separate operator: Selected Point, Selected Strokes and Active Layer. For better consistency with meshes the Separate Operator in this PR has now three modes:
Selected:
Point or strokes is determined now by the mode selection, there is no need to have Separate Point or Separate Stroke
By Materials:
a new mode that separate each material into separate objects, following the mesh Separate operator behaviour
By Layers:
a new mode that separate each layer into separate objects (similar to Separate by materials)
The Separate Active layer in GPv2 (deprecated in this PR) can be easily done by simply selecting all and using Separate Selected. I don't think it's worth having both (Separate Active Layer and Separate by Layers)
@ -1801,0 +1950,4 @@
/* Create a new object for each layer. */
for (const Layer *layer_src : grease_pencil_src.layers()) {
if (layer_src->is_selected() || layer_src->is_locked()) {
!layer_src->is_selected()
This line actually prevents creating a new object if the layer is selected. In Separate by Layers we need to keep one layer (selected) on the source object and only create a new object for the rest of the layers.
See my response above to @filedescriptor comment.
Only have a few cleanup comments. I'm fine with the changes if this is how it's supposed to work.
@ -1696,0 +1745,4 @@
return object_dst;
}
static bke::greasepencil::Layer &find_or_create_layer_by_name(
Maybe
find_or_create_layer_in_dst_by_name
so it's a bit more clear.@ -1696,0 +1752,4 @@
const Layer *layer_src = grease_pencil_src.layers().get(layer_index, layer_src);
for (Layer *layer : grease_pencil_dst.layers_for_write()) {
This could be
@ -224,6 +224,29 @@ Array<MutableDrawingInfo> retrieve_editable_drawings(const Scene &scene,
return editable_drawings.as_span();
}
Array<MutableDrawingInfo> retrieve_editable_drawings_by_layer(
Maybe
retrieve_editable_drawings_from_layer
makes it more clear@ -318,0 +341,4 @@
IndexMask retrieve_editable_strokes_by_material(Object &object,
const bke::greasepencil::Drawing &drawing,
IndexMaskMemory &memory,
const int mat_i)
This should be before the
memory
.