From 91db8fc5a04fcea58fdc9e8dfc0188ada1239c05 Mon Sep 17 00:00:00 2001 From: Falk David Date: Thu, 2 Nov 2023 17:10:59 +0100 Subject: [PATCH] GPv3: Multi-frame editing Adds a new scene tool setting `use_grease_pencil_multi_frame_editing`. The `foreach_*_drawing` functions are moved to the `ed::greasepencil` namespace in the editor, since they are now context sensitive and depend on the toolsetting. They are now named `retrieve_editable_drawings` and `retrieve_visible_drawings` and return an array of drawings instead of calling a callback function. Pull Request: https://projects.blender.org/blender/blender/pulls/114283 --- scripts/startup/bl_ui/space_view3d.py | 3 + source/blender/blenkernel/BKE_crazyspace.hh | 6 +- .../blender/blenkernel/intern/crazyspace.cc | 14 +- .../blenkernel/intern/grease_pencil.cc | 104 +---- .../draw/engines/gpencil/gpencil_object.hh | 14 +- .../engines/overlay/overlay_grease_pencil.cc | 5 +- source/blender/draw/intern/draw_cache.h | 15 +- .../intern/draw_cache_impl_grease_pencil.cc | 56 +-- .../intern/grease_pencil_edit.cc | 381 +++++++++--------- .../intern/grease_pencil_select.cc | 118 +++--- .../intern/grease_pencil_utils.cc | 71 ++++ .../editors/include/ED_grease_pencil.hh | 15 + .../sculpt_paint/grease_pencil_erase.cc | 90 +++-- .../editors/space_view3d/view3d_select.cc | 98 ++--- .../transform_convert_grease_pencil.cc | 111 ++--- .../editors/transform/transform_gizmo_3d.cc | 10 +- .../makesdna/DNA_grease_pencil_types.h | 14 - source/blender/makesdna/DNA_scene_types.h | 2 + source/blender/makesrna/intern/rna_scene.cc | 24 ++ 19 files changed, 599 insertions(+), 552 deletions(-) diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index 7c1dc07aabf..ae51f66f3e6 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -826,6 +826,9 @@ class VIEW3D_HT_header(Header): depress=(tool_settings.gpencil_selectmode_edit == 'STROKE'), ).mode = 'STROKE' + row = layout.row(align=True) + row.prop(tool_settings, "use_grease_pencil_multi_frame_editing", text="") + if object_mode == 'PAINT_GREASE_PENCIL': row = layout.row() sub = row.row(align=True) diff --git a/source/blender/blenkernel/BKE_crazyspace.hh b/source/blender/blenkernel/BKE_crazyspace.hh index f158af024eb..4e967b619bb 100644 --- a/source/blender/blenkernel/BKE_crazyspace.hh +++ b/source/blender/blenkernel/BKE_crazyspace.hh @@ -53,9 +53,11 @@ GeometryDeformation get_evaluated_curves_deformation(const Depsgraph &depsgraph, const Object &ob_orig); GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Object *ob_eval, const Object &ob_orig, - int layer_index); + int layer_index, + int frame); GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Depsgraph &depsgraph, const Object &ob_orig, - int layer_index); + int layer_index, + int frame); } // namespace blender::bke::crazyspace diff --git a/source/blender/blenkernel/intern/crazyspace.cc b/source/blender/blenkernel/intern/crazyspace.cc index d7c062e6136..24ea23dcfc6 100644 --- a/source/blender/blenkernel/intern/crazyspace.cc +++ b/source/blender/blenkernel/intern/crazyspace.cc @@ -670,16 +670,15 @@ GeometryDeformation get_evaluated_curves_deformation(const Depsgraph &depsgraph, GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Object *ob_eval, const Object &ob_orig, - const int layer_index) + const int layer_index, + const int frame) { BLI_assert(ob_orig.type == OB_GREASE_PENCIL); - const GreasePencil &grease_pencil = *static_cast(ob_eval->data); const GreasePencil &grease_pencil_orig = *static_cast(ob_orig.data); - const int eval_frame = grease_pencil.runtime->eval_frame; const Span layers_orig = grease_pencil_orig.layers(); BLI_assert(layer_index >= 0 && layer_index < layers_orig.size()); - const int drawing_index = layers_orig[layer_index]->drawing_index_at(eval_frame); + const int drawing_index = layers_orig[layer_index]->drawing_index_at(frame); if (drawing_index == -1) { return {}; } @@ -726,7 +725,7 @@ GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Object Span layers_eval = grease_pencil_eval->layers(); if (layers_eval.size() != layers_orig.size()) { const bke::greasepencil::Layer *layer_eval = layers_eval[layer_index]; - const int drawing_index_eval = layer_eval->drawing_index_at(eval_frame); + const int drawing_index_eval = layer_eval->drawing_index_at(frame); if (drawing_index_eval != -1) { const GreasePencilDrawingBase *drawing_base_eval = grease_pencil_eval->drawing( drawing_index_eval); @@ -748,10 +747,11 @@ GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Object GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Depsgraph &depsgraph, const Object &ob_orig, - const int layer_index) + const int layer_index, + const int frame) { const Object *ob_eval = DEG_get_evaluated_object(&depsgraph, const_cast(&ob_orig)); - return get_evaluated_grease_pencil_drawing_deformation(ob_eval, ob_orig, layer_index); + return get_evaluated_grease_pencil_drawing_deformation(ob_eval, ob_orig, layer_index, frame); } } // namespace blender::bke::crazyspace diff --git a/source/blender/blenkernel/intern/grease_pencil.cc b/source/blender/blenkernel/intern/grease_pencil.cc index 4f4d05151c4..4757f136717 100644 --- a/source/blender/blenkernel/intern/grease_pencil.cc +++ b/source/blender/blenkernel/intern/grease_pencil.cc @@ -1823,101 +1823,23 @@ blender::bke::greasepencil::Drawing *GreasePencil::get_editable_drawing_at( return &drawing->wrap(); } -enum ForeachDrawingMode { - VISIBLE, - EDITABLE, -}; - -static void foreach_drawing_ex( - const GreasePencil &grease_pencil, - const int frame, - const ForeachDrawingMode mode, - blender::FunctionRef function) -{ - using namespace blender::bke::greasepencil; - - blender::Span drawings = grease_pencil.drawings(); - blender::Span layers = grease_pencil.layers(); - for (const int layer_i : layers.index_range()) { - const Layer *layer = layers[layer_i]; - switch (mode) { - case VISIBLE: { - if (!layer->is_visible()) { - continue; - } - break; - } - case EDITABLE: { - if (!layer->is_editable()) { - continue; - } - break; - } - } - - int index = layer->drawing_index_at(frame); - if (index == -1) { - continue; - } - const GreasePencilDrawingBase *drawing_base = drawings[index]; - if (drawing_base->type == GP_DRAWING) { - const GreasePencilDrawing *drawing = reinterpret_cast( - drawing_base); - function(layer_i, drawing->wrap()); - } - else if (drawing_base->type == GP_DRAWING_REFERENCE) { - /* TODO: Drawing references are not implemented yet. */ - BLI_assert_unreachable(); - } - } -} - -void GreasePencil::foreach_visible_drawing( - const int frame, - blender::FunctionRef function) -{ - foreach_drawing_ex( - *this, - frame, - VISIBLE, - [&](const int layer_index, const blender::bke::greasepencil::Drawing &drawing) { - /* We const_cast here to be able to implement `foreach_drawing_ex` only once. */ - function(layer_index, const_cast(drawing)); - }); -} - -void GreasePencil::foreach_visible_drawing( - const int frame, - blender::FunctionRef function) - const -{ - foreach_drawing_ex(*this, frame, VISIBLE, function); -} - -void GreasePencil::foreach_editable_drawing( - const int frame, - blender::FunctionRef function) -{ - foreach_drawing_ex( - *this, - frame, - EDITABLE, - [&](const int layer_index, const blender::bke::greasepencil::Drawing &drawing) { - /* We const_cast here to be able to implement `foreach_drawing_ex` only once. */ - function(layer_index, const_cast(drawing)); - }); -} - std::optional> GreasePencil::bounds_min_max() const { using namespace blender; std::optional> bounds; - this->foreach_visible_drawing( - this->runtime->eval_frame, - [&](const int /*layer_index*/, const bke::greasepencil::Drawing &drawing) { - const bke::CurvesGeometry &curves = drawing.strokes(); - bounds = bounds::merge(bounds, curves.bounds_min_max()); - }); + const Span layers = this->layers(); + for (const int layer_i : layers.index_range()) { + const bke::greasepencil::Layer *layer = layers[layer_i]; + if (!layer->is_visible()) { + continue; + } + if (const bke::greasepencil::Drawing *drawing = this->get_drawing_at( + layer, this->runtime->eval_frame)) + { + const bke::CurvesGeometry &curves = drawing->strokes(); + bounds = bounds::merge(bounds, curves.bounds_min_max()); + } + } return bounds; } diff --git a/source/blender/draw/engines/gpencil/gpencil_object.hh b/source/blender/draw/engines/gpencil/gpencil_object.hh index 328d77df395..87db63a22fc 100644 --- a/source/blender/draw/engines/gpencil/gpencil_object.hh +++ b/source/blender/draw/engines/gpencil/gpencil_object.hh @@ -59,8 +59,8 @@ class ObjectModule { /** Forward vector used to sort gpencil objects. */ float3 camera_forward_; float3 camera_pos_; - /** Scene current frame. */ - float current_frame_ = 0; + + const Scene *scene_ = nullptr; /** \note Needs not to be temporary variable since it is dereferenced later. */ std::array clear_colors_ = {float4(0.0f, 0.0f, 0.0f, 0.0f), @@ -73,6 +73,7 @@ class ObjectModule { void init(const View3D *v3d, const Scene *scene) { const bool is_viewport = (v3d != nullptr); + scene_ = scene; if (is_viewport) { /* TODO(fclem): Avoid access to global DRW. */ @@ -93,11 +94,10 @@ class ObjectModule { } } - void begin_sync(Depsgraph *depsgraph, const View &main_view) + void begin_sync(Depsgraph * /*depsgraph*/, const View &main_view) { camera_forward_ = main_view.forward(); camera_pos_ = main_view.location(); - current_frame_ = DEG_get_ctime(depsgraph); is_object_fb_needed_ = false; is_layer_fb_needed_ = false; @@ -157,9 +157,9 @@ class ObjectModule { object_subpass.state_set(state); object_subpass.shader_set(shaders_.static_shader_get(GREASE_PENCIL)); - GPUVertBuf *position_tx = DRW_cache_grease_pencil_position_buffer_get(object, current_frame_); - GPUVertBuf *color_tx = DRW_cache_grease_pencil_color_buffer_get(object, current_frame_); - GPUBatch *geom = DRW_cache_grease_pencil_get(object, current_frame_); + GPUVertBuf *position_tx = DRW_cache_grease_pencil_position_buffer_get(scene_, object); + GPUVertBuf *color_tx = DRW_cache_grease_pencil_color_buffer_get(scene_, object); + GPUBatch *geom = DRW_cache_grease_pencil_get(scene_, object); /* TODO(fclem): Pass per frame object matrix here. */ ResourceHandle handle = manager.resource_handle(object_ref); diff --git a/source/blender/draw/engines/overlay/overlay_grease_pencil.cc b/source/blender/draw/engines/overlay/overlay_grease_pencil.cc index 958104f814f..7334aa5a2ce 100644 --- a/source/blender/draw/engines/overlay/overlay_grease_pencil.cc +++ b/source/blender/draw/engines/overlay/overlay_grease_pencil.cc @@ -43,17 +43,18 @@ void OVERLAY_edit_grease_pencil_cache_init(OVERLAY_Data *vedata) void OVERLAY_edit_grease_pencil_cache_populate(OVERLAY_Data *vedata, Object *ob) { OVERLAY_PrivateData *pd = vedata->stl->pd; + const DRWContextState *draw_ctx = DRW_context_state_get(); DRWShadingGroup *lines_grp = pd->edit_grease_pencil_wires_grp; if (lines_grp) { - GPUBatch *geom_lines = DRW_cache_grease_pencil_edit_lines_get(ob, pd->cfra); + GPUBatch *geom_lines = DRW_cache_grease_pencil_edit_lines_get(draw_ctx->scene, ob); DRW_shgroup_call_no_cull(lines_grp, geom_lines, ob); } DRWShadingGroup *points_grp = pd->edit_grease_pencil_points_grp; if (points_grp) { - GPUBatch *geom_points = DRW_cache_grease_pencil_edit_points_get(ob, pd->cfra); + GPUBatch *geom_points = DRW_cache_grease_pencil_edit_points_get(draw_ctx->scene, ob); DRW_shgroup_call_no_cull(points_grp, geom_points, ob); } } diff --git a/source/blender/draw/intern/draw_cache.h b/source/blender/draw/intern/draw_cache.h index d6df94513d6..3b85890d2b7 100644 --- a/source/blender/draw/intern/draw_cache.h +++ b/source/blender/draw/intern/draw_cache.h @@ -23,6 +23,7 @@ struct Volume; struct VolumeGrid; struct bGPDstroke; struct bGPdata; +struct Scene; /** * Shape resolution level of detail. @@ -279,11 +280,15 @@ void DRW_cache_gpencil_sbuffer_clear(struct Object *ob); /* Grease Pencil */ -struct GPUBatch *DRW_cache_grease_pencil_get(struct Object *ob, int cfra); -struct GPUBatch *DRW_cache_grease_pencil_edit_points_get(struct Object *ob, int cfra); -struct GPUBatch *DRW_cache_grease_pencil_edit_lines_get(struct Object *ob, int cfra); -struct GPUVertBuf *DRW_cache_grease_pencil_position_buffer_get(struct Object *ob, int cfra); -struct GPUVertBuf *DRW_cache_grease_pencil_color_buffer_get(struct Object *ob, int cfra); +struct GPUBatch *DRW_cache_grease_pencil_get(const struct Scene *scene, struct Object *ob); +struct GPUBatch *DRW_cache_grease_pencil_edit_points_get(const struct Scene *scene, + struct Object *ob); +struct GPUBatch *DRW_cache_grease_pencil_edit_lines_get(const struct Scene *scene, + struct Object *ob); +struct GPUVertBuf *DRW_cache_grease_pencil_position_buffer_get(const struct Scene *scene, + struct Object *ob); +struct GPUVertBuf *DRW_cache_grease_pencil_color_buffer_get(const struct Scene *scene, + struct Object *ob); #ifdef __cplusplus } diff --git a/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc b/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc index 05a99f6f3f0..f79d700f37c 100644 --- a/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc +++ b/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc @@ -19,6 +19,8 @@ #include "DRW_engine.h" #include "DRW_render.h" +#include "ED_grease_pencil.hh" + #include "GPU_batch.h" #include "draw_cache_impl.hh" @@ -193,14 +195,13 @@ BLI_INLINE int32_t pack_rotation_aspect_hardness(float rot, float asp, float har } static void grease_pencil_edit_lines_batch_ensure( - const Span drawings, - GreasePencilBatchCache *cache) + const Span drawings, GreasePencilBatchCache *cache) { using namespace blender::bke::greasepencil; int total_line_ids_num = 0; - for (const Drawing *drawing : drawings) { - const bke::CurvesGeometry &curves = drawing->strokes(); + for (const ed::greasepencil::DrawingInfo &info : drawings) { + const bke::CurvesGeometry &curves = info.drawing.strokes(); /* Add one id for the restart after every curve. */ total_line_ids_num += curves.curves_num(); /* Add one id for every non-cyclic segment. */ @@ -218,8 +219,8 @@ static void grease_pencil_edit_lines_batch_ensure( /* Fill buffers with data. */ int drawing_start_offset = 0; for (const int drawing_i : drawings.index_range()) { - const Drawing &drawing = *drawings[drawing_i]; - const bke::CurvesGeometry &curves = drawing.strokes(); + const ed::greasepencil::DrawingInfo &info = drawings[drawing_i]; + const bke::CurvesGeometry &curves = info.drawing.strokes(); const OffsetIndices points_by_curve = curves.points_by_curve(); const VArray cyclic = curves.cyclic(); @@ -250,7 +251,7 @@ static void grease_pencil_edit_lines_batch_ensure( } static void grease_pencil_geom_batch_ensure(const GreasePencil &grease_pencil, - int cfra, + const Scene &scene, const bool lines) { using namespace blender::bke::greasepencil; @@ -267,9 +268,8 @@ static void grease_pencil_geom_batch_ensure(const GreasePencil &grease_pencil, BLI_assert(cache->geom_batch == nullptr); /* Get the visible drawings. */ - Vector drawings; - grease_pencil.foreach_visible_drawing( - cfra, [&](const int /*layer_index*/, const Drawing &drawing) { drawings.append(&drawing); }); + const Array drawings = + ed::greasepencil::retrieve_visible_drawings(scene, grease_pencil); /* First, count how many vertices and triangles are needed for the whole object. Also record the * offsets into the curves for the vertices and triangles. */ @@ -279,8 +279,8 @@ static void grease_pencil_geom_batch_ensure(const GreasePencil &grease_pencil, int v_offset = 0; Vector> verts_start_offsets_per_visible_drawing; Vector> tris_start_offsets_per_visible_drawing; - for (const Drawing *drawing : drawings) { - const bke::CurvesGeometry &curves = drawing->strokes(); + for (const ed::greasepencil::DrawingInfo &info : drawings) { + const bke::CurvesGeometry &curves = info.drawing.strokes(); const OffsetIndices points_by_curve = curves.points_by_curve(); const VArray cyclic = curves.cyclic(); @@ -314,7 +314,7 @@ static void grease_pencil_geom_batch_ensure(const GreasePencil &grease_pencil, /* One vertex is stored before and after as padding. Cyclic strokes have one extra vertex. */ total_verts_num += curves.points_num() + num_cyclic + curves.curves_num() * 2; total_triangles_num += (curves.points_num() + num_cyclic) * 2; - total_triangles_num += drawing->triangles().size(); + total_triangles_num += info.drawing.triangles().size(); verts_start_offsets_per_visible_drawing.append(std::move(verts_start_offsets)); tris_start_offsets_per_visible_drawing.append(std::move(tris_start_offsets)); @@ -365,14 +365,14 @@ static void grease_pencil_geom_batch_ensure(const GreasePencil &grease_pencil, /* Fill buffers with data. */ int drawing_start_offset = 0; for (const int drawing_i : drawings.index_range()) { - const Drawing &drawing = *drawings[drawing_i]; - const bke::CurvesGeometry &curves = drawing.strokes(); + const ed::greasepencil::DrawingInfo &info = drawings[drawing_i]; + const bke::CurvesGeometry &curves = info.drawing.strokes(); const bke::AttributeAccessor attributes = curves.attributes(); const OffsetIndices points_by_curve = curves.points_by_curve(); const Span positions = curves.positions(); const VArray cyclic = curves.cyclic(); - const VArray radii = drawing.radii(); - const VArray opacities = drawing.opacities(); + const VArray radii = info.drawing.radii(); + const VArray opacities = info.drawing.opacities(); const VArray vertex_colors = *attributes.lookup_or_default( "vertex_color", ATTR_DOMAIN_POINT, ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f)); /* Assumes that if the ".selection" attribute does not exist, all points are selected. */ @@ -384,7 +384,7 @@ static void grease_pencil_geom_batch_ensure(const GreasePencil &grease_pencil, "end_cap", ATTR_DOMAIN_CURVE, 0); const VArray materials = *attributes.lookup_or_default( "material_index", ATTR_DOMAIN_CURVE, -1); - const Span triangles = drawing.triangles(); + const Span triangles = info.drawing.triangles(); const Span verts_start_offsets = verts_start_offsets_per_visible_drawing[drawing_i]; const Span tris_start_offsets = tris_start_offsets_per_visible_drawing[drawing_i]; @@ -547,52 +547,52 @@ void DRW_grease_pencil_batch_cache_free(GreasePencil *grease_pencil) grease_pencil->runtime->batch_cache = nullptr; } -GPUBatch *DRW_cache_grease_pencil_get(Object *ob, int cfra) +GPUBatch *DRW_cache_grease_pencil_get(const Scene *scene, Object *ob) { using namespace blender::draw; GreasePencil &grease_pencil = *static_cast(ob->data); GreasePencilBatchCache *cache = grease_pencil_batch_cache_get(grease_pencil); - grease_pencil_geom_batch_ensure(grease_pencil, cfra, false); + grease_pencil_geom_batch_ensure(grease_pencil, *scene, false); return cache->geom_batch; } -GPUBatch *DRW_cache_grease_pencil_edit_points_get(Object *ob, int cfra) +GPUBatch *DRW_cache_grease_pencil_edit_points_get(const Scene *scene, Object *ob) { using namespace blender::draw; GreasePencil &grease_pencil = *static_cast(ob->data); GreasePencilBatchCache *cache = grease_pencil_batch_cache_get(grease_pencil); - grease_pencil_geom_batch_ensure(grease_pencil, cfra, false); + grease_pencil_geom_batch_ensure(grease_pencil, *scene, false); return cache->edit_points; } -GPUBatch *DRW_cache_grease_pencil_edit_lines_get(Object *ob, int cfra) +GPUBatch *DRW_cache_grease_pencil_edit_lines_get(const Scene *scene, Object *ob) { using namespace blender::draw; GreasePencil &grease_pencil = *static_cast(ob->data); GreasePencilBatchCache *cache = grease_pencil_batch_cache_get(grease_pencil); - grease_pencil_geom_batch_ensure(grease_pencil, cfra, true); + grease_pencil_geom_batch_ensure(grease_pencil, *scene, true); return cache->edit_lines; } -GPUVertBuf *DRW_cache_grease_pencil_position_buffer_get(Object *ob, int cfra) +GPUVertBuf *DRW_cache_grease_pencil_position_buffer_get(const Scene *scene, Object *ob) { using namespace blender::draw; GreasePencil &grease_pencil = *static_cast(ob->data); GreasePencilBatchCache *cache = grease_pencil_batch_cache_get(grease_pencil); - grease_pencil_geom_batch_ensure(grease_pencil, cfra, true); + grease_pencil_geom_batch_ensure(grease_pencil, *scene, true); return cache->vbo; } -GPUVertBuf *DRW_cache_grease_pencil_color_buffer_get(Object *ob, int cfra) +GPUVertBuf *DRW_cache_grease_pencil_color_buffer_get(const Scene *scene, Object *ob) { using namespace blender::draw; GreasePencil &grease_pencil = *static_cast(ob->data); GreasePencilBatchCache *cache = grease_pencil_batch_cache_get(grease_pencil); - grease_pencil_geom_batch_ensure(grease_pencil, cfra, false); + grease_pencil_geom_batch_ensure(grease_pencil, *scene, false); return cache->vbo_col; } diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc index 76086c43d5b..d681e89406e 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc @@ -305,59 +305,65 @@ static int grease_pencil_stroke_smooth_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](const int /*layer_index*/, bke::greasepencil::Drawing &drawing) { - bke::CurvesGeometry &curves = drawing.strokes_for_write(); - if (curves.points_num() == 0) { - return; - } + bool changed = false; + const Array drawings = retrieve_editable_drawings(*scene, grease_pencil); + threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { + bke::CurvesGeometry &curves = info.drawing.strokes_for_write(); + if (curves.points_num() == 0) { + return; + } - bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); - const OffsetIndices points_by_curve = curves.points_by_curve(); - const VArray cyclic = curves.cyclic(); - const VArray selection = *curves.attributes().lookup_or_default( - ".selection", ATTR_DOMAIN_POINT, true); + bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); + const OffsetIndices points_by_curve = curves.points_by_curve(); + const VArray cyclic = curves.cyclic(); + const VArray selection = *curves.attributes().lookup_or_default( + ".selection", ATTR_DOMAIN_POINT, true); - if (smooth_position) { - bke::GSpanAttributeWriter positions = attributes.lookup_for_write_span("position"); - smooth_curve_attribute(points_by_curve, - selection, - cyclic, - iterations, - influence, - smooth_ends, - keep_shape, - positions.span); - positions.finish(); - } - if (smooth_opacity && drawing.opacities().is_span()) { - bke::GSpanAttributeWriter opacities = attributes.lookup_for_write_span("opacity"); - smooth_curve_attribute(points_by_curve, - selection, - cyclic, - iterations, - influence, - smooth_ends, - false, - opacities.span); - opacities.finish(); - } - if (smooth_radius && drawing.radii().is_span()) { - bke::GSpanAttributeWriter radii = attributes.lookup_for_write_span("radius"); - smooth_curve_attribute(points_by_curve, - selection, - cyclic, - iterations, - influence, - smooth_ends, - false, - radii.span); - radii.finish(); - } - }); + if (smooth_position) { + bke::GSpanAttributeWriter positions = attributes.lookup_for_write_span("position"); + smooth_curve_attribute(points_by_curve, + selection, + cyclic, + iterations, + influence, + smooth_ends, + keep_shape, + positions.span); + positions.finish(); + changed = true; + } + if (smooth_opacity && info.drawing.opacities().is_span()) { + bke::GSpanAttributeWriter opacities = attributes.lookup_for_write_span("opacity"); + smooth_curve_attribute(points_by_curve, + selection, + cyclic, + iterations, + influence, + smooth_ends, + false, + opacities.span); + opacities.finish(); + changed = true; + } + if (smooth_radius && info.drawing.radii().is_span()) { + bke::GSpanAttributeWriter radii = attributes.lookup_for_write_span("radius"); + smooth_curve_attribute(points_by_curve, + selection, + cyclic, + iterations, + influence, + smooth_ends, + false, + radii.span); + radii.finish(); + changed = true; + } + }); - DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil); + if (changed) { + DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil); + } return OPERATOR_FINISHED; } @@ -453,84 +459,84 @@ static int grease_pencil_stroke_simplify_exec(bContext *C, wmOperator *op) const float epsilon = RNA_float_get(op->ptr, "factor"); bool changed = false; - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](const int /*layer_index*/, bke::greasepencil::Drawing &drawing) { - bke::CurvesGeometry &curves = drawing.strokes_for_write(); - if (curves.points_num() == 0) { - return; - } - if (!ed::curves::has_anything_selected(curves)) { - return; - } + const Array drawings = retrieve_editable_drawings(*scene, grease_pencil); + threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { + bke::CurvesGeometry &curves = info.drawing.strokes_for_write(); + if (curves.points_num() == 0) { + return; + } + if (!ed::curves::has_anything_selected(curves)) { + return; + } - const Span positions = curves.positions(); - const VArray radii = drawing.radii(); + const Span positions = curves.positions(); + const VArray radii = info.drawing.radii(); - /* Distance functions for `ramer_douglas_peucker_simplify`. */ - const auto dist_function_positions = - [positions](int64_t first_index, int64_t last_index, int64_t index) { - const float dist_position = dist_to_line_v3( - positions[index], positions[first_index], positions[last_index]); - return dist_position; - }; - const auto dist_function_positions_and_radii = - [positions, radii](int64_t first_index, int64_t last_index, int64_t index) { - const float dist_position = dist_to_line_v3( - positions[index], positions[first_index], positions[last_index]); - /* We divide the distance by 2000.0f to convert from "pixels" to an actual distance. - * For some reason, grease pencil strokes the thickness of strokes in pixels rather - * than object space distance. */ - const float dist_radii = dist_to_interpolated(positions[index], - positions[first_index], - positions[last_index], - radii[index], - radii[first_index], - radii[last_index]) / - 2000.0f; - return math::max(dist_position, dist_radii); - }; + /* Distance functions for `ramer_douglas_peucker_simplify`. */ + const auto dist_function_positions = + [positions](int64_t first_index, int64_t last_index, int64_t index) { + const float dist_position = dist_to_line_v3( + positions[index], positions[first_index], positions[last_index]); + return dist_position; + }; + const auto dist_function_positions_and_radii = + [positions, radii](int64_t first_index, int64_t last_index, int64_t index) { + const float dist_position = dist_to_line_v3( + positions[index], positions[first_index], positions[last_index]); + /* We divide the distance by 2000.0f to convert from "pixels" to an actual + * distance. For some reason, grease pencil strokes the thickness of strokes in + * pixels rather than object space distance. */ + const float dist_radii = dist_to_interpolated(positions[index], + positions[first_index], + positions[last_index], + radii[index], + radii[first_index], + radii[last_index]) / + 2000.0f; + return math::max(dist_position, dist_radii); + }; - const VArray cyclic = curves.cyclic(); - const OffsetIndices points_by_curve = curves.points_by_curve(); - const VArray selection = *curves.attributes().lookup_or_default( - ".selection", ATTR_DOMAIN_POINT, true); + const VArray cyclic = curves.cyclic(); + const OffsetIndices points_by_curve = curves.points_by_curve(); + const VArray selection = *curves.attributes().lookup_or_default( + ".selection", ATTR_DOMAIN_POINT, true); - Array points_to_delete(curves.points_num()); - selection.materialize(points_to_delete); + Array points_to_delete(curves.points_num()); + selection.materialize(points_to_delete); - std::atomic total_points_to_delete = 0; - if (radii.is_single()) { - threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) { - for (const int curve_i : range) { - const IndexRange points = points_by_curve[curve_i]; - total_points_to_delete += stroke_simplify(points, - cyclic[curve_i], - epsilon, - dist_function_positions, - points_to_delete.as_mutable_span()); - } - }); - } - else if (radii.is_span()) { - threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) { - for (const int curve_i : range) { - const IndexRange points = points_by_curve[curve_i]; - total_points_to_delete += stroke_simplify(points, - cyclic[curve_i], - epsilon, - dist_function_positions_and_radii, - points_to_delete.as_mutable_span()); - } - }); - } - - if (total_points_to_delete > 0) { - IndexMaskMemory memory; - curves.remove_points(IndexMask::from_bools(points_to_delete, memory)); - drawing.tag_topology_changed(); - changed = true; + std::atomic total_points_to_delete = 0; + if (radii.is_single()) { + threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) { + for (const int curve_i : range) { + const IndexRange points = points_by_curve[curve_i]; + total_points_to_delete += stroke_simplify(points, + cyclic[curve_i], + epsilon, + dist_function_positions, + points_to_delete.as_mutable_span()); } }); + } + else if (radii.is_span()) { + threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) { + for (const int curve_i : range) { + const IndexRange points = points_by_curve[curve_i]; + total_points_to_delete += stroke_simplify(points, + cyclic[curve_i], + epsilon, + dist_function_positions_and_radii, + points_to_delete.as_mutable_span()); + } + }); + } + + if (total_points_to_delete > 0) { + IndexMaskMemory memory; + curves.remove_points(IndexMask::from_bools(points_to_delete, memory)); + info.drawing.tag_topology_changed(); + changed = true; + } + }); if (changed) { DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); @@ -601,8 +607,8 @@ static Array get_points_to_dissolve(bke::CurvesGeometry &curves, const Dis return points_to_dissolve; } - /* Both `between` and `unselect` have the unselected point being the ones dissolved so we need to - * invert. */ + /* Both `between` and `unselect` have the unselected point being the ones dissolved so we need + * to invert. */ BLI_assert(ELEM(mode, DissolveMode::BETWEEN, DissolveMode::UNSELECT)); const OffsetIndices points_by_curve = curves.points_by_curve(); @@ -659,25 +665,25 @@ static int grease_pencil_dissolve_exec(bContext *C, wmOperator *op) const DissolveMode mode = DissolveMode(RNA_enum_get(op->ptr, "type")); bool changed = false; - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](const int /*layer_index*/, bke::greasepencil::Drawing &drawing) { - bke::CurvesGeometry &curves = drawing.strokes_for_write(); - if (curves.points_num() == 0) { - return; - } - if (!ed::curves::has_anything_selected(curves)) { - return; - } + const Array drawings = retrieve_editable_drawings(*scene, grease_pencil); + threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { + bke::CurvesGeometry &curves = info.drawing.strokes_for_write(); + if (curves.points_num() == 0) { + return; + } + if (!ed::curves::has_anything_selected(curves)) { + return; + } - const Array points_to_dissolve = get_points_to_dissolve(curves, mode); + const Array points_to_dissolve = get_points_to_dissolve(curves, mode); - if (points_to_dissolve.as_span().contains(true)) { - IndexMaskMemory memory; - curves.remove_points(IndexMask::from_bools(points_to_dissolve, memory)); - drawing.tag_topology_changed(); - changed = true; - } - }); + if (points_to_dissolve.as_span().contains(true)) { + IndexMaskMemory memory; + curves.remove_points(IndexMask::from_bools(points_to_dissolve, memory)); + info.drawing.tag_topology_changed(); + changed = true; + } + }); if (changed) { DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); @@ -815,25 +821,25 @@ static int grease_pencil_stroke_material_set_exec(bContext *C, wmOperator * /*op return OPERATOR_CANCELLED; } - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](int /*layer_index*/, bke::greasepencil::Drawing &drawing) { - bke::CurvesGeometry &curves = drawing.strokes_for_write(); + const Array drawings = retrieve_editable_drawings(*scene, grease_pencil); + threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { + bke::CurvesGeometry &curves = info.drawing.strokes_for_write(); - IndexMaskMemory memory; - IndexMask selected_curves = ed::curves::retrieve_selected_curves(curves, memory); + IndexMaskMemory memory; + IndexMask selected_curves = ed::curves::retrieve_selected_curves(curves, memory); - if (selected_curves.is_empty()) { - return; - } + if (selected_curves.is_empty()) { + return; + } - bke::SpanAttributeWriter materials = - curves.attributes_for_write().lookup_or_add_for_write_span("material_index", - ATTR_DOMAIN_CURVE); - selected_curves.foreach_index( - [&](const int curve_index) { materials.span[curve_index] = material_index; }); + bke::SpanAttributeWriter materials = + curves.attributes_for_write().lookup_or_add_for_write_span("material_index", + ATTR_DOMAIN_CURVE); + selected_curves.foreach_index( + [&](const int curve_index) { materials.span[curve_index] = material_index; }); - materials.finish(); - }); + materials.finish(); + }); DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_EDITED, &grease_pencil); @@ -883,45 +889,44 @@ static int grease_pencil_cyclical_set_exec(bContext *C, wmOperator *op) const CyclicalMode mode = CyclicalMode(RNA_enum_get(op->ptr, "type")); bool changed = false; - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](int /*layer_index*/, bke::greasepencil::Drawing &drawing) { - bke::CurvesGeometry &curves = drawing.strokes_for_write(); + const Array drawings = retrieve_editable_drawings(*scene, grease_pencil); + threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { + bke::CurvesGeometry &curves = info.drawing.strokes_for_write(); - if (mode == CyclicalMode::OPEN && !curves.attributes().contains("cyclic")) { - /* Avoid creating unneeded attribute. */ - return; - } + if (mode == CyclicalMode::OPEN && !curves.attributes().contains("cyclic")) { + /* Avoid creating unneeded attribute. */ + return; + } - IndexMaskMemory memory; - const IndexMask curve_selection = ed::curves::retrieve_selected_curves(curves, memory); - if (curve_selection.is_empty()) { - return; - } + IndexMaskMemory memory; + const IndexMask curve_selection = ed::curves::retrieve_selected_curves(curves, memory); + if (curve_selection.is_empty()) { + return; + } - MutableSpan cyclic = curves.cyclic_for_write(); + MutableSpan cyclic = curves.cyclic_for_write(); - switch (mode) { - case CyclicalMode::CLOSE: - index_mask::masked_fill(cyclic, true, curve_selection); - break; - case CyclicalMode::OPEN: - index_mask::masked_fill(cyclic, false, curve_selection); - break; - case CyclicalMode::TOGGLE: - array_utils::invert_booleans(cyclic, curve_selection); - break; - } + switch (mode) { + case CyclicalMode::CLOSE: + index_mask::masked_fill(cyclic, true, curve_selection); + break; + case CyclicalMode::OPEN: + index_mask::masked_fill(cyclic, false, curve_selection); + break; + case CyclicalMode::TOGGLE: + array_utils::invert_booleans(cyclic, curve_selection); + break; + } - /* Remove the attribute if it is empty. */ - if (mode != CyclicalMode::CLOSE) { - if (array_utils::booleans_mix_calc(curves.cyclic()) == array_utils::BooleanMix::AllFalse) - { - curves.attributes_for_write().remove("cyclic"); - } - } + /* Remove the attribute if it is empty. */ + if (mode != CyclicalMode::CLOSE) { + if (array_utils::booleans_mix_calc(curves.cyclic()) == array_utils::BooleanMix::AllFalse) { + curves.attributes_for_write().remove("cyclic"); + } + } - changed = true; - }); + changed = true; + }); if (changed) { DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_select.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_select.cc index 54fd2325778..07c77aea427 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_select.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_select.cc @@ -36,10 +36,10 @@ static int select_all_exec(bContext *C, wmOperator *op) GreasePencil &grease_pencil = *static_cast(object->data); eAttrDomain selection_domain = ED_grease_pencil_selection_domain_get(scene->toolsettings); - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](const int /*layer_index*/, blender::bke::greasepencil::Drawing &drawing) { - blender::ed::curves::select_all(drawing.strokes_for_write(), selection_domain, action); - }); + const Array drawings = retrieve_editable_drawings(*scene, grease_pencil); + threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { + blender::ed::curves::select_all(info.drawing.strokes_for_write(), selection_domain, action); + }); /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic * attribute for now. */ @@ -69,10 +69,10 @@ static int select_more_exec(bContext *C, wmOperator * /*op*/) Object *object = CTX_data_active_object(C); GreasePencil &grease_pencil = *static_cast(object->data); - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [](const int /*layer_index*/, blender::bke::greasepencil::Drawing &drawing) { - blender::ed::curves::select_adjacent(drawing.strokes_for_write(), false); - }); + const Array drawings = retrieve_editable_drawings(*scene, grease_pencil); + threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { + blender::ed::curves::select_adjacent(info.drawing.strokes_for_write(), false); + }); /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic * attribute for now. */ @@ -100,10 +100,10 @@ static int select_less_exec(bContext *C, wmOperator * /*op*/) Object *object = CTX_data_active_object(C); GreasePencil &grease_pencil = *static_cast(object->data); - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [](const int /*layer_index*/, blender::bke::greasepencil::Drawing &drawing) { - blender::ed::curves::select_adjacent(drawing.strokes_for_write(), true); - }); + const Array drawings = retrieve_editable_drawings(*scene, grease_pencil); + threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { + blender::ed::curves::select_adjacent(info.drawing.strokes_for_write(), true); + }); /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic * attribute for now. */ @@ -131,10 +131,10 @@ static int select_linked_exec(bContext *C, wmOperator * /*op*/) Object *object = CTX_data_active_object(C); GreasePencil &grease_pencil = *static_cast(object->data); - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [](const int /*layer_index*/, blender::bke::greasepencil::Drawing &drawing) { - blender::ed::curves::select_linked(drawing.strokes_for_write()); - }); + const Array drawings = retrieve_editable_drawings(*scene, grease_pencil); + threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { + blender::ed::curves::select_linked(info.drawing.strokes_for_write()); + }); /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic * attribute for now. */ @@ -166,28 +166,28 @@ static int select_random_exec(bContext *C, wmOperator *op) GreasePencil &grease_pencil = *static_cast(object->data); eAttrDomain selection_domain = ED_grease_pencil_selection_domain_get(scene->toolsettings); - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](const int layer_index, bke::greasepencil::Drawing &drawing) { - bke::CurvesGeometry &curves = drawing.strokes_for_write(); + const Array drawings = retrieve_editable_drawings(*scene, grease_pencil); + threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { + bke::CurvesGeometry &curves = info.drawing.strokes_for_write(); - IndexMaskMemory memory; - const IndexMask random_elements = ed::curves::random_mask( - curves, - selection_domain, - blender::get_default_hash_2(seed, layer_index), - ratio, - memory); + IndexMaskMemory memory; + const IndexMask random_elements = ed::curves::random_mask( + curves, + selection_domain, + blender::get_default_hash_2(seed, info.layer_index), + ratio, + memory); - const bool was_anything_selected = ed::curves::has_anything_selected(curves); - bke::GSpanAttributeWriter selection = ed::curves::ensure_selection_attribute( - curves, selection_domain, CD_PROP_BOOL); - if (!was_anything_selected) { - curves::fill_selection_true(selection.span); - } + const bool was_anything_selected = ed::curves::has_anything_selected(curves); + bke::GSpanAttributeWriter selection = ed::curves::ensure_selection_attribute( + curves, selection_domain, CD_PROP_BOOL); + if (!was_anything_selected) { + curves::fill_selection_true(selection.span); + } - curves::fill_selection_false(selection.span, random_elements); - selection.finish(); - }); + curves::fill_selection_false(selection.span, random_elements); + selection.finish(); + }); /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic * attribute for now. */ @@ -218,10 +218,10 @@ static int select_alternate_exec(bContext *C, wmOperator *op) Object *object = CTX_data_active_object(C); GreasePencil &grease_pencil = *static_cast(object->data); - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](const int /*layer_index*/, blender::bke::greasepencil::Drawing &drawing) { - blender::ed::curves::select_alternate(drawing.strokes_for_write(), deselect_ends); - }); + const Array drawings = retrieve_editable_drawings(*scene, grease_pencil); + threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { + blender::ed::curves::select_alternate(info.drawing.strokes_for_write(), deselect_ends); + }); /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic * attribute for now. */ @@ -257,29 +257,29 @@ static int select_ends_exec(bContext *C, wmOperator *op) Object *object = CTX_data_active_object(C); GreasePencil &grease_pencil = *static_cast(object->data); - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](const int /*layer_index*/, blender::bke::greasepencil::Drawing &drawing) { - bke::CurvesGeometry &curves = drawing.strokes_for_write(); + const Array drawings = retrieve_editable_drawings(*scene, grease_pencil); + threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { + bke::CurvesGeometry &curves = info.drawing.strokes_for_write(); - IndexMaskMemory memory; - const IndexMask inverted_end_points_mask = ed::curves::end_points( - curves, amount_start, amount_end, true, memory); + IndexMaskMemory memory; + const IndexMask inverted_end_points_mask = ed::curves::end_points( + curves, amount_start, amount_end, true, memory); - const bool was_anything_selected = ed::curves::has_anything_selected(curves); - bke::GSpanAttributeWriter selection = ed::curves::ensure_selection_attribute( - curves, ATTR_DOMAIN_POINT, CD_PROP_BOOL); - if (!was_anything_selected) { - ed::curves::fill_selection_true(selection.span); - } + const bool was_anything_selected = ed::curves::has_anything_selected(curves); + bke::GSpanAttributeWriter selection = ed::curves::ensure_selection_attribute( + curves, ATTR_DOMAIN_POINT, CD_PROP_BOOL); + if (!was_anything_selected) { + ed::curves::fill_selection_true(selection.span); + } - if (selection.span.type().is()) { - index_mask::masked_fill(selection.span.typed(), false, inverted_end_points_mask); - } - if (selection.span.type().is()) { - index_mask::masked_fill(selection.span.typed(), 0.0f, inverted_end_points_mask); - } - selection.finish(); - }); + if (selection.span.type().is()) { + index_mask::masked_fill(selection.span.typed(), false, inverted_end_points_mask); + } + if (selection.span.type().is()) { + index_mask::masked_fill(selection.span.typed(), 0.0f, inverted_end_points_mask); + } + selection.finish(); + }); /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic * attribute for now. */ diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc index 145b9f78d37..4cc61df74a1 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc @@ -76,4 +76,75 @@ float brush_radius_world_space(bContext &C, int x, int y) return radius; } +static Array get_frame_numbers_for_layer(const bke::greasepencil::Layer &layer, + const int frame, + const bool use_multi_frame_editing) +{ + if (!use_multi_frame_editing) { + return Array({frame}); + } + Vector frame_numbers; + for (const auto [frame_number, frame] : layer.frames().items()) { + if (frame.is_selected()) { + frame_numbers.append_unchecked(frame_number); + } + } + return frame_numbers.as_span(); +} + +Array retrieve_editable_drawings(const Scene &scene, + GreasePencil &grease_pencil) +{ + using namespace blender::bke::greasepencil; + const int current_frame = scene.r.cfra; + const ToolSettings *toolsettings = scene.toolsettings; + const bool use_multi_frame_editing = (toolsettings->gpencil_flags & + GP_USE_MULTI_FRAME_EDITING) != 0; + + Vector editable_drawings; + Span layers = grease_pencil.layers_for_write(); + for (const int layer_i : layers.index_range()) { + Layer *layer = layers[layer_i]; + if (!layer->is_editable()) { + continue; + } + const Array frame_numbers = get_frame_numbers_for_layer( + *layer, current_frame, use_multi_frame_editing); + for (const int frame_number : frame_numbers) { + if (Drawing *drawing = grease_pencil.get_editable_drawing_at(layer, frame_number)) { + editable_drawings.append({*drawing, layer_i, frame_number}); + } + } + } + + return editable_drawings.as_span(); +} + +Array retrieve_visible_drawings(const Scene &scene, const GreasePencil &grease_pencil) +{ + using namespace blender::bke::greasepencil; + const int current_frame = scene.r.cfra; + const ToolSettings *toolsettings = scene.toolsettings; + const bool use_multi_frame_editing = (toolsettings->gpencil_flags & + GP_USE_MULTI_FRAME_EDITING) != 0; + + Vector visible_drawings; + Span layers = grease_pencil.layers(); + for (const int layer_i : layers.index_range()) { + const Layer *layer = layers[layer_i]; + if (!layer->is_visible()) { + continue; + } + const Array frame_numbers = get_frame_numbers_for_layer( + *layer, current_frame, use_multi_frame_editing); + for (const int frame_number : frame_numbers) { + if (const Drawing *drawing = grease_pencil.get_drawing_at(layer, frame_number)) { + visible_drawings.append({*drawing, layer_i, frame_number}); + } + } + } + + return visible_drawings.as_span(); +} + } // namespace blender::ed::greasepencil diff --git a/source/blender/editors/include/ED_grease_pencil.hh b/source/blender/editors/include/ED_grease_pencil.hh index 2771e2ed8bf..7430fc9662e 100644 --- a/source/blender/editors/include/ED_grease_pencil.hh +++ b/source/blender/editors/include/ED_grease_pencil.hh @@ -110,6 +110,21 @@ bool editable_grease_pencil_poll(bContext *C); bool editable_grease_pencil_point_selection_poll(bContext *C); bool grease_pencil_painting_poll(bContext *C); +struct DrawingInfo { + const bke::greasepencil::Drawing &drawing; + const int layer_index; + const int frame_number; +}; +struct MutableDrawingInfo { + bke::greasepencil::Drawing &drawing; + const int layer_index; + const int frame_number; +}; +Array retrieve_editable_drawings(const Scene &scene, + GreasePencil &grease_pencil); +Array retrieve_visible_drawings(const Scene &scene, + const GreasePencil &grease_pencil); + void create_blank(Main &bmain, Object &object, int frame_number); void create_stroke(Main &bmain, Object &object, float4x4 matrix, int frame_number); void create_suzanne(Main &bmain, Object &object, float4x4 matrix, const int frame_number); diff --git a/source/blender/editors/sculpt_paint/grease_pencil_erase.cc b/source/blender/editors/sculpt_paint/grease_pencil_erase.cc index 3ba46f9526a..4601c5f9785 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_erase.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_erase.cc @@ -23,6 +23,7 @@ #include "DEG_depsgraph_query.hh" #include "DNA_brush_enums.h" +#include "ED_grease_pencil.hh" #include "ED_view3d.hh" #include "WM_api.hh" @@ -753,49 +754,50 @@ struct EraseOperationExecutor { GreasePencil &grease_pencil = *static_cast(obact->data); bool changed = false; - const auto execute_eraser_on_drawing = [&](const int layer_index, Drawing &drawing) { - const bke::CurvesGeometry &src = drawing.strokes(); + const auto execute_eraser_on_drawing = + [&](const int layer_index, const int frame_number, Drawing &drawing) { + const bke::CurvesGeometry &src = drawing.strokes(); - /* Evaluated geometry. */ - bke::crazyspace::GeometryDeformation deformation = - bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation( - ob_eval, *obact, layer_index); + /* Evaluated geometry. */ + bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation( + ob_eval, *obact, layer_index, frame_number); - /* Compute screen space positions. */ - Array screen_space_positions(src.points_num()); - threading::parallel_for(src.points_range(), 4096, [&](const IndexRange src_points) { - for (const int src_point : src_points) { - ED_view3d_project_float_global( - region, - math::transform_point(transforms_.layer_space_to_world_space, - deformation.positions[src_point]), - screen_space_positions[src_point], - V3D_PROJ_TEST_NOP); - } - }); + /* Compute screen space positions. */ + Array screen_space_positions(src.points_num()); + threading::parallel_for(src.points_range(), 4096, [&](const IndexRange src_points) { + for (const int src_point : src_points) { + ED_view3d_project_float_global( + region, + math::transform_point(transforms_.layer_space_to_world_space, + deformation.positions[src_point]), + screen_space_positions[src_point], + V3D_PROJ_TEST_NOP); + } + }); - /* Erasing operator. */ - bke::CurvesGeometry dst; - bool erased = false; - switch (self.eraser_mode) { - case GP_BRUSH_ERASER_STROKE: - erased = stroke_eraser(src, screen_space_positions, dst); - break; - case GP_BRUSH_ERASER_HARD: - erased = hard_eraser(src, screen_space_positions, dst, self.keep_caps); - break; - case GP_BRUSH_ERASER_SOFT: - // To be implemented - return; - } + /* Erasing operator. */ + bke::CurvesGeometry dst; + bool erased = false; + switch (self.eraser_mode) { + case GP_BRUSH_ERASER_STROKE: + erased = stroke_eraser(src, screen_space_positions, dst); + break; + case GP_BRUSH_ERASER_HARD: + erased = hard_eraser(src, screen_space_positions, dst, self.keep_caps); + break; + case GP_BRUSH_ERASER_SOFT: + // To be implemented + return; + } - if (erased) { - /* Set the new geometry. */ - drawing.geometry.wrap() = std::move(dst); - drawing.tag_topology_changed(); - changed = true; - } - }; + if (erased) { + /* Set the new geometry. */ + drawing.geometry.wrap() = std::move(dst); + drawing.tag_topology_changed(); + changed = true; + } + }; if (self.active_layer_only) { /* Erase only on the drawing at the current frame of the active layer. */ @@ -806,11 +808,17 @@ struct EraseOperationExecutor { return; } - execute_eraser_on_drawing(active_layer->drawing_index_at(scene->r.cfra), *drawing); + execute_eraser_on_drawing( + active_layer->drawing_index_at(scene->r.cfra), scene->r.cfra, *drawing); } else { /* Erase on all editable drawings. */ - grease_pencil.foreach_editable_drawing(scene->r.cfra, execute_eraser_on_drawing); + const Array drawings = + ed::greasepencil::retrieve_editable_drawings(*scene, grease_pencil); + threading::parallel_for_each( + drawings, [&](const ed::greasepencil::MutableDrawingInfo &info) { + execute_eraser_on_drawing(info.layer_index, info.frame_number, info.drawing); + }); } if (changed) { diff --git a/source/blender/editors/space_view3d/view3d_select.cc b/source/blender/editors/space_view3d/view3d_select.cc index a9898bf0a54..5318a28d214 100644 --- a/source/blender/editors/space_view3d/view3d_select.cc +++ b/source/blender/editors/space_view3d/view3d_select.cc @@ -1192,20 +1192,21 @@ static bool do_lasso_select_grease_pencil(ViewContext *vc, vc->scene->toolsettings); bool changed = false; - grease_pencil.foreach_editable_drawing( - vc->scene->r.cfra, [&](const int layer_index, blender::bke::greasepencil::Drawing &drawing) { - bke::crazyspace::GeometryDeformation deformation = - bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation( - ob_eval, *vc->obedit, layer_index); + const Array drawings = + ed::greasepencil::retrieve_editable_drawings(*vc->scene, grease_pencil); + for (const ed::greasepencil::MutableDrawingInfo info : drawings) { + bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation( + ob_eval, *vc->obedit, info.layer_index, info.frame_number); - changed = ed::curves::select_lasso( - *vc, - drawing.strokes_for_write(), - deformation.positions, - selection_domain, - Span(reinterpret_cast(mcoords), mcoords_len), - sel_op); - }); + changed = ed::curves::select_lasso( + *vc, + info.drawing.strokes_for_write(), + deformation.positions, + selection_domain, + Span(reinterpret_cast(mcoords), mcoords_len), + sel_op); + } if (changed) { /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a @@ -3167,13 +3168,8 @@ static bool ed_grease_pencil_select_pick(bContext *C, /* Collect editable drawings. */ const Object *ob_eval = DEG_get_evaluated_object(vc.depsgraph, const_cast(vc.obedit)); GreasePencil &grease_pencil = *static_cast(vc.obedit->data); - Vector drawings; - Vector layer_indices; - grease_pencil.foreach_editable_drawing( - vc.scene->r.cfra, [&](const int layer_index, blender::bke::greasepencil::Drawing &drawing) { - drawings.append(&drawing); - layer_indices.append(layer_index); - }); + const Array drawings = + ed::greasepencil::retrieve_editable_drawings(*vc.scene, grease_pencil); /* Get selection domain from tool settings. */ const eAttrDomain selection_domain = ED_grease_pencil_selection_domain_get( @@ -3186,21 +3182,22 @@ static bool ed_grease_pencil_select_pick(bContext *C, [&](const IndexRange range, const ClosestGreasePencilDrawing &init) { ClosestGreasePencilDrawing new_closest = init; for (const int i : range) { + ed::greasepencil::MutableDrawingInfo info = drawings[i]; /* Get deformation by modifiers. */ bke::crazyspace::GeometryDeformation deformation = bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation( - ob_eval, *vc.obedit, layer_indices[i]); + ob_eval, *vc.obedit, info.layer_index, info.frame_number); std::optional new_closest_elem = ed::curves::closest_elem_find_screen_space(vc, *vc.obedit, - drawings[i]->strokes_for_write(), + info.drawing.strokes_for_write(), deformation.positions, selection_domain, mval, new_closest.elem); if (new_closest_elem) { new_closest.elem = *new_closest_elem; - new_closest.drawing = drawings[i]; + new_closest.drawing = &info.drawing; } } return new_closest; @@ -3213,7 +3210,8 @@ static bool ed_grease_pencil_select_pick(bContext *C, if (params.deselect_all || params.sel_op == SEL_OP_SET) { threading::parallel_for(drawings.index_range(), 1L, [&](const IndexRange range) { for (const int i : range) { - bke::CurvesGeometry &curves = drawings[i]->geometry.wrap(); + ed::greasepencil::MutableDrawingInfo info = drawings[i]; + bke::CurvesGeometry &curves = info.drawing.strokes_for_write(); if (!ed::curves::has_anything_selected(curves)) { continue; } @@ -4191,18 +4189,19 @@ static bool do_grease_pencil_box_select(ViewContext *vc, const rcti *rect, const const eAttrDomain selection_domain = ED_grease_pencil_selection_domain_get(scene->toolsettings); bool changed = false; - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](const int layer_index, blender::bke::greasepencil::Drawing &drawing) { - bke::crazyspace::GeometryDeformation deformation = - bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation( - ob_eval, *vc->obedit, layer_index); - changed |= ed::curves::select_box(*vc, - drawing.strokes_for_write(), - deformation.positions, - selection_domain, - *rect, - sel_op); - }); + const Array drawings = + ed::greasepencil::retrieve_editable_drawings(*scene, grease_pencil); + for (const ed::greasepencil::MutableDrawingInfo info : drawings) { + bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation( + ob_eval, *vc->obedit, info.layer_index, info.frame_number); + changed |= ed::curves::select_box(*vc, + info.drawing.strokes_for_write(), + deformation.positions, + selection_domain, + *rect, + sel_op); + } if (changed) { DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); @@ -5039,20 +5038,21 @@ static bool grease_pencil_circle_select(ViewContext *vc, vc->scene->toolsettings); bool changed = false; - grease_pencil.foreach_editable_drawing( - vc->scene->r.cfra, [&](const int layer_index, blender::bke::greasepencil::Drawing &drawing) { - bke::crazyspace::GeometryDeformation deformation = - bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation( - ob_eval, *vc->obedit, layer_index); + const Array drawings = + ed::greasepencil::retrieve_editable_drawings(*vc->scene, grease_pencil); + for (const ed::greasepencil::MutableDrawingInfo info : drawings) { + bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation( + ob_eval, *vc->obedit, info.layer_index, info.frame_number); - changed = ed::curves::select_circle(*vc, - drawing.strokes_for_write(), - deformation.positions, - selection_domain, - int2(mval), - rad, - sel_op); - }); + changed = ed::curves::select_circle(*vc, + info.drawing.strokes_for_write(), + deformation.positions, + selection_domain, + int2(mval), + rad, + sel_op); + } if (changed) { /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a diff --git a/source/blender/editors/transform/transform_convert_grease_pencil.cc b/source/blender/editors/transform/transform_convert_grease_pencil.cc index e547e5b7eff..b9ff488f0a9 100644 --- a/source/blender/editors/transform/transform_convert_grease_pencil.cc +++ b/source/blender/editors/transform/transform_convert_grease_pencil.cc @@ -29,42 +29,42 @@ static void createTransGreasePencilVerts(bContext *C, TransInfo *t) const bool use_proportional_edit = (t->flag & T_PROP_EDIT_ALL) != 0; const bool use_connected_only = (t->flag & T_PROP_CONNECTED) != 0; - int number_of_layers_total = 0; + int total_number_of_drawings = 0; + Vector> all_drawings; /* Count the number layers in all objects. */ for (const int i : trans_data_contrainers.index_range()) { TransDataContainer &tc = trans_data_contrainers[i]; GreasePencil &grease_pencil = *static_cast(tc.obedit->data); - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](int /*layer_index*/, bke::greasepencil::Drawing & /*drawing*/) { - number_of_layers_total++; - }); + const Array drawings = + ed::greasepencil::retrieve_editable_drawings(*scene, grease_pencil); + all_drawings.append(drawings); + total_number_of_drawings += drawings.size(); } int layer_offset = 0; IndexMaskMemory memory; - Array selection_per_layer_per_object(number_of_layers_total); + Array selection_per_layer_per_object(total_number_of_drawings); /* Count selected elements per layer per object and create TransData structs. */ for (const int i : trans_data_contrainers.index_range()) { TransDataContainer &tc = trans_data_contrainers[i]; GreasePencil &grease_pencil = *static_cast(tc.obedit->data); - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](int /*layer_index*/, bke::greasepencil::Drawing &drawing) { - const bke::CurvesGeometry &curves = drawing.strokes(); + const Array drawings = all_drawings[i]; + for (ed::greasepencil::MutableDrawingInfo info : drawings) { + const bke::CurvesGeometry &curves = info.drawing.strokes(); + if (use_proportional_edit) { + tc.data_len += curves.point_num; + } + else { + selection_per_layer_per_object[layer_offset] = ed::curves::retrieve_selected_points( + curves, memory); + tc.data_len += selection_per_layer_per_object[layer_offset].size(); + } - if (use_proportional_edit) { - tc.data_len += curves.point_num; - } - else { - selection_per_layer_per_object[layer_offset] = ed::curves::retrieve_selected_points( - curves, memory); - tc.data_len += selection_per_layer_per_object[layer_offset].size(); - } - - layer_offset++; - }); + layer_offset++; + } if (tc.data_len > 0) { tc.data = MEM_cnew_array(tc.data_len, __func__); @@ -88,37 +88,37 @@ static void createTransGreasePencilVerts(bContext *C, TransInfo *t) int layer_points_offset = 0; - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](int /*layer_index*/, bke::greasepencil::Drawing &drawing) { - bke::CurvesGeometry &curves = drawing.strokes_for_write(); - const IndexMask selected_indices = selection_per_layer_per_object[layer_offset]; + const Array drawings = all_drawings[i]; + for (ed::greasepencil::MutableDrawingInfo info : drawings) { + bke::CurvesGeometry &curves = info.drawing.strokes_for_write(); + const IndexMask selected_indices = selection_per_layer_per_object[layer_offset]; - std::optional> value_attribute; - if (t->mode == TFM_CURVE_SHRINKFATTEN) { - MutableSpan radii = drawing.radii_for_write(); - value_attribute = radii; - } - else if (t->mode == TFM_GPENCIL_OPACITY) { - MutableSpan opacities = drawing.opacities_for_write(); - value_attribute = opacities; - } + std::optional> value_attribute; + if (t->mode == TFM_CURVE_SHRINKFATTEN) { + MutableSpan radii = info.drawing.radii_for_write(); + value_attribute = radii; + } + else if (t->mode == TFM_GPENCIL_OPACITY) { + MutableSpan opacities = info.drawing.opacities_for_write(); + value_attribute = opacities; + } - curve_populate_trans_data_structs(tc, - curves, - value_attribute, - selected_indices, - use_proportional_edit, - use_connected_only, - layer_points_offset); + curve_populate_trans_data_structs(tc, + curves, + value_attribute, + selected_indices, + use_proportional_edit, + use_connected_only, + layer_points_offset); - if (use_proportional_edit) { - layer_points_offset += curves.points_num(); - } - else { - layer_points_offset += selected_indices.size(); - } - layer_offset++; - }); + if (use_proportional_edit) { + layer_points_offset += curves.points_num(); + } + else { + layer_points_offset += selected_indices.size(); + } + layer_offset++; + } } } @@ -131,14 +131,15 @@ static void recalcData_grease_pencil(TransInfo *t) for (const TransDataContainer &tc : trans_data_contrainers) { GreasePencil &grease_pencil = *static_cast(tc.obedit->data); - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](int /*layer_index*/, bke::greasepencil::Drawing &drawing) { - bke::CurvesGeometry &curves = drawing.strokes_for_write(); + const Array drawings = + ed::greasepencil::retrieve_editable_drawings(*scene, grease_pencil); + for (ed::greasepencil::MutableDrawingInfo info : drawings) { + bke::CurvesGeometry &curves = info.drawing.strokes_for_write(); - curves.calculate_bezier_auto_handles(); - curves.tag_positions_changed(); - drawing.tag_positions_changed(); - }); + curves.calculate_bezier_auto_handles(); + curves.tag_positions_changed(); + info.drawing.tag_positions_changed(); + } DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); } diff --git a/source/blender/editors/transform/transform_gizmo_3d.cc b/source/blender/editors/transform/transform_gizmo_3d.cc index 399c13de23e..228178af7e1 100644 --- a/source/blender/editors/transform/transform_gizmo_3d.cc +++ b/source/blender/editors/transform/transform_gizmo_3d.cc @@ -818,13 +818,15 @@ static int gizmo_3d_foreach_selected(const bContext *C, mat_local = float4x4(obedit->world_to_object) * float4x4(ob_iter->object_to_world); } - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](int layer_index, blender::bke::greasepencil::Drawing &drawing) { - const bke::CurvesGeometry &curves = drawing.strokes(); + const Array drawings = + ed::greasepencil::retrieve_editable_drawings(*scene, grease_pencil); + threading::parallel_for_each( + drawings, [&](const ed::greasepencil::MutableDrawingInfo &info) { + const bke::CurvesGeometry &curves = info.drawing.strokes(); const bke::crazyspace::GeometryDeformation deformation = bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation( - *depsgraph, *ob, layer_index); + *depsgraph, *ob, info.layer_index, info.frame_number); IndexMaskMemory memory; const IndexMask selected_points = ed::curves::retrieve_selected_points(curves, diff --git a/source/blender/makesdna/DNA_grease_pencil_types.h b/source/blender/makesdna/DNA_grease_pencil_types.h index 11b656e40e1..e2afdf0c7e4 100644 --- a/source/blender/makesdna/DNA_grease_pencil_types.h +++ b/source/blender/makesdna/DNA_grease_pencil_types.h @@ -577,20 +577,6 @@ typedef struct GreasePencil { blender::bke::greasepencil::Drawing *get_editable_drawing_at( const blender::bke::greasepencil::Layer *layer, int frame_number); - void foreach_visible_drawing( - const int frame, - blender::FunctionRef function); - void foreach_visible_drawing( - const int frame, - blender::FunctionRef function) - const; - void foreach_editable_drawing( - const int frame, - blender::FunctionRef function); - std::optional> bounds_min_max() const; blender::bke::AttributeAccessor attributes() const; diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 922220eb069..855a9b5de33 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -2694,6 +2694,8 @@ enum { /** #ToolSettings::gpencil_flags */ typedef enum eGPencil_Flags { + /** Enables multi-frame editing. */ + GP_USE_MULTI_FRAME_EDITING = (1 << 0), /** When creating new frames, the last frame gets used as the basis for the new one. */ GP_TOOL_FLAG_RETAIN_LAST = (1 << 1), /** Add the strokes below all strokes in the layer. */ diff --git a/source/blender/makesrna/intern/rna_scene.cc b/source/blender/makesrna/intern/rna_scene.cc index b647e263f07..dc25add22c3 100644 --- a/source/blender/makesrna/intern/rna_scene.cc +++ b/source/blender/makesrna/intern/rna_scene.cc @@ -11,6 +11,7 @@ #include "DNA_brush_types.h" #include "DNA_collection_types.h" #include "DNA_gpencil_legacy_types.h" +#include "DNA_grease_pencil_types.h" #include "DNA_layer_types.h" #include "DNA_linestyle_types.h" #include "DNA_modifier_types.h" @@ -878,6 +879,19 @@ static void rna_Gpencil_vertex_mask_segment_update(bContext *C, PointerRNA *ptr) ED_gpencil_tag_scene_gpencil(CTX_data_scene(C)); } +# ifdef WITH_GREASE_PENCIL_V3 +static void rna_active_grease_pencil_update(bContext *C, PointerRNA * /*ptr*/) +{ + Object *active_object = CTX_data_active_object(C); + if (!active_object || active_object->type != OB_GREASE_PENCIL) { + return; + } + GreasePencil *grease_pencil = static_cast(active_object->data); + DEG_id_tag_update(&grease_pencil->id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GPENCIL | NA_EDITED, nullptr); +} +# endif + /* Read-only Iterator of all the scene objects. */ static void rna_Scene_objects_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) @@ -3734,6 +3748,16 @@ static void rna_def_tool_settings(BlenderRNA *brna) RNA_def_property_update( prop, NC_SPACE | ND_SPACE_VIEW3D, "rna_Gpencil_vertex_mask_segment_update"); +# ifdef WITH_GREASE_PENCIL_V3 + prop = RNA_def_property(srna, "use_grease_pencil_multi_frame_editing", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "gpencil_flags", GP_USE_MULTI_FRAME_EDITING); + RNA_def_property_ui_text(prop, "Multi-frame Editing", "Enable multi-frame editing"); + RNA_def_property_ui_icon(prop, ICON_GP_MULTIFRAME_EDITING, 0); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_active_grease_pencil_update"); +# endif + /* Annotations - 2D Views Stroke Placement */ prop = RNA_def_property(srna, "annotation_stroke_placement_view2d", PROP_ENUM, PROP_NONE); RNA_def_property_enum_bitflag_sdna(prop, nullptr, "gpencil_v2d_align");