diff --git a/scripts/startup/bl_ui/properties_data_grease_pencil.py b/scripts/startup/bl_ui/properties_data_grease_pencil.py index bafabca9ea4..115636e845b 100644 --- a/scripts/startup/bl_ui/properties_data_grease_pencil.py +++ b/scripts/startup/bl_ui/properties_data_grease_pencil.py @@ -231,6 +231,72 @@ class DATA_PT_grease_pencil_layer_relations(LayerDataButtonsPanel, Panel): col.prop_search(layer, "viewlayer_render", context.scene, "view_layers", text="View Layer") +class DATA_PT_grease_pencil_onion_skinning(DataButtonsPanel, Panel): + bl_label = "Onion Skinning" + + def draw(self, context): + grease_pencil = context.grease_pencil + + layout = self.layout + layout.use_property_split = True + + col = layout.column() + col.prop(grease_pencil, "onion_mode") + col.prop(grease_pencil, "onion_factor", text="Opacity", slider=True) + col.prop(grease_pencil, "onion_keyframe_type") + + if grease_pencil.onion_mode == 'ABSOLUTE': + col = layout.column(align=True) + col.prop(grease_pencil, "ghost_before_range", text="Frames Before") + col.prop(grease_pencil, "ghost_after_range", text="Frames After") + elif grease_pencil.onion_mode == 'RELATIVE': + col = layout.column(align=True) + col.prop(grease_pencil, "ghost_before_range", text="Keyframes Before") + col.prop(grease_pencil, "ghost_after_range", text="Keyframes After") + + +class DATA_PT_grease_pencil_onion_skinning_custom_colors(DataButtonsPanel, Panel): + bl_parent_id = "DATA_PT_grease_pencil_onion_skinning" + bl_label = "Custom Colors" + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, context): + grease_pencil = context.grease_pencil + self.layout.prop(grease_pencil, "use_ghost_custom_colors", text="") + + def draw(self, context): + grease_pencil = context.grease_pencil + + layout = self.layout + layout.use_property_split = True + layout.enabled = grease_pencil.users <= 1 and grease_pencil.use_ghost_custom_colors + + layout.prop(grease_pencil, "before_color", text="Before") + layout.prop(grease_pencil, "after_color", text="After") + + +class DATA_PT_grease_pencil_onion_skinning_display(DataButtonsPanel, Panel): + bl_parent_id = "DATA_PT_grease_pencil_onion_skinning" + bl_label = "Display" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + grease_pencil = context.grease_pencil + + layout = self.layout + layout.use_property_split = True + # This was done in GPv2 but it's not entirely clear why. Presumably it was + # to indicate that the settings will affect the onion skinning of the + # other users. + layout.enabled = grease_pencil.users <= 1 + + col = layout.column(align=True) + col.prop(grease_pencil, "use_onion_fade", text="Fade") + sub = layout.column() + sub.active = grease_pencil.onion_mode in {'RELATIVE', 'SELECTED'} + sub.prop(grease_pencil, "use_onion_loop", text="Show Start Frame") + + class DATA_PT_grease_pencil_settings(DataButtonsPanel, Panel): bl_label = "Settings" @@ -257,6 +323,9 @@ classes = ( DATA_PT_grease_pencil_layer_masks, DATA_PT_grease_pencil_layer_transform, DATA_PT_grease_pencil_layer_relations, + DATA_PT_grease_pencil_onion_skinning, + DATA_PT_grease_pencil_onion_skinning_custom_colors, + DATA_PT_grease_pencil_onion_skinning_display, DATA_PT_grease_pencil_settings, DATA_PT_grease_pencil_custom_props, GREASE_PENCIL_MT_grease_pencil_add_layer_extra, diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index 70b8263e366..8d7b26748dd 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -7861,6 +7861,8 @@ class VIEW3D_PT_overlay_grease_pencil_options(Panel): 'OBJECT': iface_("Grease Pencil"), }[context.mode], translate=False) + layout.prop(overlay, "use_gpencil_onion_skin", text="Onion Skin") + if ob.mode in {'EDIT'}: split = layout.split() col = split.column() diff --git a/source/blender/CMakeLists.txt b/source/blender/CMakeLists.txt index 593897d4a43..25854e5ac79 100644 --- a/source/blender/CMakeLists.txt +++ b/source/blender/CMakeLists.txt @@ -105,6 +105,7 @@ set(SRC_DNA_DEFAULTS_INC ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_defaults.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_fluid_defaults.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_gpencil_modifier_defaults.h + ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_grease_pencil_defaults.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_image_defaults.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_lattice_defaults.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_light_defaults.h diff --git a/source/blender/blenkernel/BKE_grease_pencil.hh b/source/blender/blenkernel/BKE_grease_pencil.hh index b60c75b1e38..52e028293cd 100644 --- a/source/blender/blenkernel/BKE_grease_pencil.hh +++ b/source/blender/blenkernel/BKE_grease_pencil.hh @@ -703,7 +703,8 @@ inline void TreeNode::set_selected(const bool selected) } inline bool TreeNode::use_onion_skinning() const { - return ((this->flag & GP_LAYER_TREE_NODE_USE_ONION_SKINNING) != 0); + return ((this->flag & GP_LAYER_TREE_NODE_HIDE_ONION_SKINNING) == 0) && + (!this->parent_group() || this->parent_group()->as_node().use_onion_skinning()); } inline bool TreeNode::use_masks() const { diff --git a/source/blender/blenkernel/intern/grease_pencil.cc b/source/blender/blenkernel/intern/grease_pencil.cc index 0dd128f0e3e..f5600ab1810 100644 --- a/source/blender/blenkernel/intern/grease_pencil.cc +++ b/source/blender/blenkernel/intern/grease_pencil.cc @@ -42,6 +42,7 @@ #include "BLI_string.h" #include "BLI_string_ref.hh" #include "BLI_string_utils.hh" +#include "BLI_utildefines.h" #include "BLI_vector_set.hh" #include "BLI_virtual_array.hh" @@ -52,6 +53,7 @@ #include "DNA_ID.h" #include "DNA_ID_enums.h" #include "DNA_brush_types.h" +#include "DNA_defaults.h" #include "DNA_gpencil_modifier_types.h" #include "DNA_grease_pencil_types.h" #include "DNA_material_types.h" @@ -80,10 +82,12 @@ static void grease_pencil_init_data(ID *id) using namespace blender::bke; GreasePencil *grease_pencil = reinterpret_cast(id); + BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(grease_pencil, id)); + + MEMCPY_STRUCT_AFTER(grease_pencil, DNA_struct_default_get(GreasePencil), id); grease_pencil->root_group_ptr = MEM_new(__func__); grease_pencil->active_layer = nullptr; - grease_pencil->flag |= GREASE_PENCIL_ANIM_CHANNEL_EXPANDED; CustomData_reset(&grease_pencil->layers_data); diff --git a/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc b/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc index 70401260aba..2a06aaf3b7b 100644 --- a/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc +++ b/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc @@ -581,8 +581,8 @@ void legacy_gpencil_to_grease_pencil(Main &bmain, GreasePencil &grease_pencil, b SET_FLAG_FROM_TEST( new_layer.base.flag, (gpl->flag & GP_LAYER_USE_LIGHTS), GP_LAYER_TREE_NODE_USE_LIGHTS); SET_FLAG_FROM_TEST(new_layer.base.flag, - (gpl->onion_flag & GP_LAYER_ONIONSKIN), - GP_LAYER_TREE_NODE_USE_ONION_SKINNING); + (gpl->onion_flag & GP_LAYER_ONIONSKIN) == 0, + GP_LAYER_TREE_NODE_HIDE_ONION_SKINNING); SET_FLAG_FROM_TEST( new_layer.base.flag, (gpl->flag & GP_LAYER_USE_MASK) == 0, GP_LAYER_TREE_NODE_HIDE_MASKS); @@ -646,18 +646,28 @@ void legacy_gpencil_to_grease_pencil(Main &bmain, GreasePencil &grease_pencil, b grease_pencil.vertex_group_active_index = gpd.vertex_group_active_index; /* Convert the onion skinning settings. */ - grease_pencil.onion_skinning_settings.opacity = gpd.onion_factor; - grease_pencil.onion_skinning_settings.mode = gpd.onion_mode; + GreasePencilOnionSkinningSettings &settings = grease_pencil.onion_skinning_settings; + settings.opacity = gpd.onion_factor; + settings.mode = gpd.onion_mode; + SET_FLAG_FROM_TEST(settings.flag, + ((gpd.onion_flag & GP_ONION_GHOST_PREVCOL) != 0 && + (gpd.onion_flag & GP_ONION_GHOST_NEXTCOL) != 0), + GP_ONION_SKINNING_USE_CUSTOM_COLORS); + SET_FLAG_FROM_TEST( + settings.flag, (gpd.onion_flag & GP_ONION_FADE) != 0, GP_ONION_SKINNING_USE_FADE); + SET_FLAG_FROM_TEST( + settings.flag, (gpd.onion_flag & GP_ONION_LOOP) != 0, GP_ONION_SKINNING_SHOW_LOOP); + /* Convert keytype filter to a bit flag. */ if (gpd.onion_keytype == -1) { - grease_pencil.onion_skinning_settings.filter = GREASE_PENCIL_ONION_SKINNING_FILTER_ALL; + settings.filter = GREASE_PENCIL_ONION_SKINNING_FILTER_ALL; } else { - grease_pencil.onion_skinning_settings.filter = (1 << gpd.onion_keytype); + settings.filter = (1 << gpd.onion_keytype); } - grease_pencil.onion_skinning_settings.num_frames_before = gpd.gstep; - grease_pencil.onion_skinning_settings.num_frames_after = gpd.gstep_next; - copy_v3_v3(grease_pencil.onion_skinning_settings.color_before, gpd.gcolor_prev); - copy_v3_v3(grease_pencil.onion_skinning_settings.color_after, gpd.gcolor_next); + settings.num_frames_before = gpd.gstep; + settings.num_frames_after = gpd.gstep_next; + copy_v3_v3(settings.color_before, gpd.gcolor_prev); + copy_v3_v3(settings.color_after, gpd.gcolor_next); BKE_id_materials_copy(&bmain, &gpd.id, &grease_pencil.id); diff --git a/source/blender/draw/engines/gpencil/gpencil_cache_utils.cc b/source/blender/draw/engines/gpencil/gpencil_cache_utils.cc index fa18c5e2365..d30687f565f 100644 --- a/source/blender/draw/engines/gpencil/gpencil_cache_utils.cc +++ b/source/blender/draw/engines/gpencil/gpencil_cache_utils.cc @@ -261,6 +261,49 @@ static void gpencil_layer_final_tint_and_alpha_get(const GPENCIL_PrivateData *pd *r_alpha *= pd->xray_alpha; } +static float4 grease_pencil_layer_final_tint_and_alpha_get(const GPENCIL_PrivateData *pd, + const GreasePencil &grease_pencil, + const int onion_id, + float *r_alpha) +{ + const bool use_onion = (onion_id != 0); + if (use_onion) { + const bool use_onion_custom_col = (grease_pencil.onion_skinning_settings.flag & + GP_ONION_SKINNING_USE_CUSTOM_COLORS) != 0; + const bool use_onion_fade = (grease_pencil.onion_skinning_settings.flag & + GP_ONION_SKINNING_USE_FADE) != 0; + const bool use_next_col = onion_id > 0; + + const float onion_factor = grease_pencil.onion_skinning_settings.opacity; + const float3 color_next(grease_pencil.onion_skinning_settings.color_after); + const float3 color_prev(grease_pencil.onion_skinning_settings.color_before); + + const float4 onion_col_custom = (use_onion_custom_col) ? + (use_next_col ? float4(color_next, 1.0f) : + float4(color_prev, 1.0f)) : + float4(U.gpencil_new_layer_col); + + *r_alpha = use_onion_fade ? (1.0f / abs(onion_id)) : 0.5f; + *r_alpha *= onion_factor; + *r_alpha = (onion_factor > 0.0f) ? clamp_f(*r_alpha, 0.1f, 1.0f) : + clamp_f(*r_alpha, 0.01f, 1.0f); + *r_alpha *= pd->xray_alpha; + + return onion_col_custom; + } + + /* Layer tint is not a property in GPv3 anymore. It's only used for onion skinning. The previous + * property is replaced by a tint modifier during conversion. */ + float4 layer_tint(0.0f); + if (GPENCIL_SIMPLIFY_TINT(pd->scene)) { + layer_tint[3] = 0.0f; + } + *r_alpha = 1.0f; + *r_alpha *= pd->xray_alpha; + + return layer_tint; +} + /* Random color by layer. */ static void gpencil_layer_random_color_get(const Object *ob, const bGPDlayer *gpl, @@ -473,7 +516,7 @@ GPENCIL_tLayer *gpencil_layer_cache_get(GPENCIL_tObject *tgp_ob, int number) GPENCIL_tLayer *grease_pencil_layer_cache_add(GPENCIL_PrivateData *pd, const Object *ob, const blender::bke::greasepencil::Layer &layer, - std::optional /*onion_id*/, + const int onion_id, GPENCIL_tObject *tgp_ob) { @@ -501,10 +544,9 @@ GPENCIL_tLayer *grease_pencil_layer_cache_add(GPENCIL_PrivateData *pd, float thickness_scale = (is_screenspace) ? -1.0f : 1.0f / 1000.0f; float layer_opacity = grease_pencil_layer_final_opacity_get(pd, ob, grease_pencil, layer); - float4 layer_tint(0.0f); float layer_alpha = pd->xray_alpha; - /* TODO: Onion skinning! */ - // gpencil_layer_final_tint_and_alpha_get(pd, gpd, gpl, gpf, layer_tint, &layer_alpha); + const float4 layer_tint = grease_pencil_layer_final_tint_and_alpha_get( + pd, grease_pencil, onion_id, &layer_alpha); /* Create the new layer descriptor. */ GPENCIL_tLayer *tgp_layer = static_cast(BLI_memblock_alloc(pd->gp_layer_pool)); diff --git a/source/blender/draw/engines/gpencil/gpencil_engine.h b/source/blender/draw/engines/gpencil/gpencil_engine.h index 4092165883a..7fdab88c6a0 100644 --- a/source/blender/draw/engines/gpencil/gpencil_engine.h +++ b/source/blender/draw/engines/gpencil/gpencil_engine.h @@ -338,7 +338,7 @@ GPENCIL_tLayer *gpencil_layer_cache_get(GPENCIL_tObject *tgp_ob, int number); GPENCIL_tLayer *grease_pencil_layer_cache_add(GPENCIL_PrivateData *pd, const Object *ob, const blender::bke::greasepencil::Layer &layer, - std::optional onion_id, + int onion_id, GPENCIL_tObject *tgp_ob); /** * Creates a linked list of material pool containing all materials assigned for a given object. diff --git a/source/blender/draw/engines/gpencil/gpencil_engine_c.cc b/source/blender/draw/engines/gpencil/gpencil_engine_c.cc index 2127f9f4788..06308970ae2 100644 --- a/source/blender/draw/engines/gpencil/gpencil_engine_c.cc +++ b/source/blender/draw/engines/gpencil/gpencil_engine_c.cc @@ -614,6 +614,9 @@ static GPENCIL_tObject *grease_pencil_object_cache_populate(GPENCIL_PrivateData const blender::Bounds bounds = grease_pencil.bounds_min_max_eval().value_or( blender::Bounds(float3(0))); + const bool do_onion = !pd->is_render && pd->do_onion; + const bool do_multi_frame = (pd->scene->toolsettings->gpencil_flags & + GP_USE_MULTI_FRAME_EDITING) != 0; const bool use_stroke_order_3d = (grease_pencil.flag & GREASE_PENCIL_STROKE_ORDER_3D) != 0; GPENCIL_tObject *tgp_ob = gpencil_object_cache_add(pd, ob, use_stroke_order_3d, bounds); @@ -658,14 +661,17 @@ static GPENCIL_tObject *grease_pencil_object_cache_populate(GPENCIL_PrivateData }; int t_offset = 0; - const Vector drawings = retrieve_visible_drawings(*pd->scene, grease_pencil); + /* Note that we loop over all the drawings (including the onion skinned ones) to make sure we + * match the offsets of the batch cache. */ + const Vector drawings = retrieve_visible_drawings(*pd->scene, grease_pencil, true); const Span layers = grease_pencil.layers(); for (const DrawingInfo info : drawings) { const Layer &layer = *layers[info.layer_index]; drawcall_flush(); - GPENCIL_tLayer *tgp_layer = grease_pencil_layer_cache_add(pd, ob, layer, {}, tgp_ob); + GPENCIL_tLayer *tgp_layer = grease_pencil_layer_cache_add( + pd, ob, layer, info.onion_id, tgp_ob); const bool use_lights = pd->use_lighting && ((layer.base.flag & GP_LAYER_TREE_NODE_USE_LIGHTS) != 0) && @@ -714,9 +720,9 @@ static GPENCIL_tObject *grease_pencil_object_cache_populate(GPENCIL_PrivateData OB_MODE_WEIGHT_PAINT, OB_MODE_VERTEX_PAINT) && info.frame_number != pd->cfra && pd->use_multiedit_lines_only; - /* bool is_onion = gpl && gpf && gpf->runtime.onion_id != 0; */ - const bool is_onion = false; - const bool hide_onion = is_onion && ((gp_style->flag & GP_MATERIAL_HIDE_ONIONSKIN) != 0); + const bool is_onion = info.onion_id != 0; + const bool hide_onion = is_onion && ((gp_style->flag & GP_MATERIAL_HIDE_ONIONSKIN) != 0 || + (!do_onion && !do_multi_frame)); const int num_stroke_triangles = (points.size() >= 3) ? (points.size() - 2) : 0; const int num_stroke_vertices = (points.size() + diff --git a/source/blender/draw/engines/overlay/overlay_outline.cc b/source/blender/draw/engines/overlay/overlay_outline.cc index 9d0f2808b26..b9b9b7d2810 100644 --- a/source/blender/draw/engines/overlay/overlay_outline.cc +++ b/source/blender/draw/engines/overlay/overlay_outline.cc @@ -293,7 +293,7 @@ static void OVERLAY_outline_grease_pencil(OVERLAY_PrivateData *pd, Scene *scene, } int t_offset = 0; - const Vector drawings = retrieve_visible_drawings(*scene, grease_pencil); + const Vector drawings = retrieve_visible_drawings(*scene, grease_pencil, true); for (const DrawingInfo info : drawings) { const bool is_screenspace = false; const bool is_stroke_order_3d = (grease_pencil.flag & GREASE_PENCIL_STROKE_ORDER_3D) != 0; @@ -332,8 +332,16 @@ static void OVERLAY_outline_grease_pencil(OVERLAY_PrivateData *pd, Scene *scene, const int material_index = stroke_materials[stroke_i]; MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, material_index + 1); + const bool hide_onion = info.onion_id != 0; const bool hide_material = (gp_style->flag & GP_MATERIAL_HIDE) != 0; - if (hide_material) { + + const int num_stroke_triangles = (points.size() >= 3) ? (points.size() - 2) : 0; + const int num_stroke_vertices = (points.size() + + int(cyclic[stroke_i] && (points.size() >= 3))); + + if (hide_material || hide_onion) { + t_offset += num_stroke_triangles; + t_offset += num_stroke_vertices * 2; return; } @@ -341,22 +349,19 @@ static void OVERLAY_outline_grease_pencil(OVERLAY_PrivateData *pd, Scene *scene, const bool show_stroke = (gp_style->flag & GP_MATERIAL_STROKE_SHOW) != 0; const bool show_fill = (points.size() >= 3) && (gp_style->flag & GP_MATERIAL_FILL_SHOW) != 0; - const bool is_cyclic = cyclic[stroke_i] && (points.size() > 2); - const int num_stroke_triangles = points.size() - 2; - const int num_stroke_vertices = (points.size() + int(is_cyclic)); if (show_fill) { - int vfirst = t_offset * 3; - int vcount = num_stroke_triangles * 3; - DRW_shgroup_call_range(grp, ob, geom, vfirst, vcount); + int v_first = t_offset * 3; + int v_count = num_stroke_triangles * 3; + DRW_shgroup_call_range(grp, ob, geom, v_first, v_count); } t_offset += num_stroke_triangles; if (show_stroke) { - int vfirst = t_offset * 3; - int vcount = num_stroke_vertices * 2 * 3; - DRW_shgroup_call_range(grp, ob, geom, vfirst, vcount); + int v_first = t_offset * 3; + int v_count = num_stroke_vertices * 2 * 3; + DRW_shgroup_call_range(grp, ob, geom, v_first, v_count); } t_offset += num_stroke_vertices * 2; }); 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 20273aeb01e..b30c5ac1158 100644 --- a/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc +++ b/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc @@ -229,7 +229,7 @@ static void grease_pencil_edit_batch_ensure(Object &object, /* Get the visible drawings. */ const Vector drawings = - ed::greasepencil::retrieve_visible_drawings(scene, grease_pencil); + ed::greasepencil::retrieve_visible_drawings(scene, grease_pencil, false); const Span layers = grease_pencil.layers(); @@ -423,7 +423,7 @@ static void grease_pencil_geom_batch_ensure(Object &object, /* Get the visible drawings. */ const Vector drawings = - ed::greasepencil::retrieve_visible_drawings(scene, grease_pencil); + ed::greasepencil::retrieve_visible_drawings(scene, grease_pencil, true); /* 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. */ 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 310ffa5afa9..95ca37542b4 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc @@ -230,9 +230,132 @@ static std::pair get_minmax_selected_frame_numbers(const GreasePencil return std::pair(frame_min, frame_max); } -static Array get_frame_numbers_for_layer(const bke::greasepencil::Layer &layer, - const int current_frame, - const bool use_multi_frame_editing) +static std::optional get_frame_id(const bke::greasepencil::Layer &layer, + const GreasePencilFrame &frame, + const int frame_number, + const int frame_index, + const int current_frame, + const int current_frame_index, + const int last_frame, + const int last_frame_index, + const bool use_multi_frame_editing, + const bool do_onion_skinning, + const bool is_before_first, + const GreasePencilOnionSkinningSettings onion_settings) +{ + if (use_multi_frame_editing) { + if (frame.is_selected()) { + if (do_onion_skinning) { + return (frame_number < current_frame) ? -1 : 1; + } + return 0; + } + return {}; + } + if (do_onion_skinning && layer.use_onion_skinning()) { + /* Keyframe type filter. */ + if (onion_settings.filter != 0 && (onion_settings.filter & (1 << frame.type)) == 0) { + return {}; + } + /* Selected mode filter. */ + if (onion_settings.mode == GP_ONION_SKINNING_MODE_SELECTED && !frame.is_selected()) { + return {}; + } + + int delta = 0; + if (onion_settings.mode == GP_ONION_SKINNING_MODE_ABSOLUTE) { + delta = frame_number - current_frame; + } + else { + delta = frame_index - current_frame_index; + } + + if (is_before_first) { + delta++; + } + if ((onion_settings.flag & GP_ONION_SKINNING_SHOW_LOOP) != 0 && + (-delta > onion_settings.num_frames_before || delta > onion_settings.num_frames_after)) + { + /* We wrap the value using the last frame and 0 as reference. */ + /* FIXME: This might not be good for animations not starting at 0. */ + int shift = 0; + if (onion_settings.mode == GP_ONION_SKINNING_MODE_ABSOLUTE) { + shift = last_frame; + } + else { + shift = last_frame_index; + } + delta += (delta < 0) ? (shift + 1) : -(shift + 1); + } + /* Frame range filter. */ + if (ELEM(onion_settings.mode, + GP_ONION_SKINNING_MODE_ABSOLUTE, + GP_ONION_SKINNING_MODE_RELATIVE) && + (-delta > onion_settings.num_frames_before || delta > onion_settings.num_frames_after)) + { + return {}; + } + + return delta; + } + return {}; +} + +static Array> get_visible_frames_for_layer( + const GreasePencil &grease_pencil, + const bke::greasepencil::Layer &layer, + const int current_frame, + const bool use_multi_frame_editing, + const bool do_onion_skinning) +{ + GreasePencilOnionSkinningSettings onion_settings = grease_pencil.onion_skinning_settings; + Vector> frame_numbers; + const Span sorted_keys = layer.sorted_keys(); + if (sorted_keys.is_empty()) { + return {}; + } + const std::optional current_frame_key = layer.frame_key_at( + current_frame); + const int current_frame_index = current_frame_key.has_value() ? + sorted_keys.first_index(*current_frame_key) : + 0; + const int last_frame = sorted_keys.last(); + const int last_frame_index = sorted_keys.index_range().last(); + const bool is_before_first = (current_frame < sorted_keys.first()); + for (const int frame_i : sorted_keys.index_range()) { + const int frame_number = sorted_keys[frame_i]; + if (frame_number == current_frame) { + continue; + } + const GreasePencilFrame &frame = layer.frames().lookup(frame_number); + const std::optional frame_id = get_frame_id(layer, + frame, + frame_number, + frame_i, + current_frame, + current_frame_index, + last_frame, + last_frame_index, + use_multi_frame_editing, + do_onion_skinning, + is_before_first, + onion_settings); + if (!frame_id.has_value()) { + /* Drawing on this frame is not visible. */ + continue; + } + + frame_numbers.append({frame_number, *frame_id}); + } + + frame_numbers.append({current_frame, 0}); + + return frame_numbers.as_span(); +} + +static Array get_editable_frames_for_layer(const bke::greasepencil::Layer &layer, + const int current_frame, + const bool use_multi_frame_editing) { Vector frame_numbers; if (use_multi_frame_editing) { @@ -271,7 +394,7 @@ Vector retrieve_editable_drawings(const Scene &scene, if (!layer.is_editable()) { continue; } - const Array frame_numbers = get_frame_numbers_for_layer( + const Array frame_numbers = get_editable_frames_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)) { @@ -309,7 +432,7 @@ Vector retrieve_editable_drawings_with_falloff(const Scene & if (!layer.is_editable()) { continue; } - const Array frame_numbers = get_frame_numbers_for_layer( + const Array frame_numbers = get_editable_frames_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)) { @@ -340,7 +463,7 @@ Vector retrieve_editable_drawings_from_layer( GP_USE_MULTI_FRAME_EDITING) != 0; Vector editable_drawings; - const Array frame_numbers = get_frame_numbers_for_layer( + const Array frame_numbers = get_editable_frames_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)) { @@ -353,7 +476,8 @@ Vector retrieve_editable_drawings_from_layer( } Vector retrieve_visible_drawings(const Scene &scene, - const GreasePencil &grease_pencil) + const GreasePencil &grease_pencil, + const bool do_onion_skinning) { using namespace blender::bke::greasepencil; const int current_frame = scene.r.cfra; @@ -368,11 +492,11 @@ Vector retrieve_visible_drawings(const Scene &scene, 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) { + const Array> frames = get_visible_frames_for_layer( + grease_pencil, layer, current_frame, use_multi_frame_editing, do_onion_skinning); + for (const auto &[frame_number, onion_id] : frames) { if (const Drawing *drawing = grease_pencil.get_drawing_at(layer, frame_number)) { - visible_drawings.append({*drawing, layer_i, frame_number}); + visible_drawings.append({*drawing, layer_i, frame_number, onion_id}); } } } diff --git a/source/blender/editors/include/ED_grease_pencil.hh b/source/blender/editors/include/ED_grease_pencil.hh index 680dd4affee..bbd30cd356a 100644 --- a/source/blender/editors/include/ED_grease_pencil.hh +++ b/source/blender/editors/include/ED_grease_pencil.hh @@ -180,6 +180,11 @@ struct DrawingInfo { const bke::greasepencil::Drawing &drawing; const int layer_index; const int frame_number; + /* This is used by the onion skinning system. A value of 0 means the drawing is on the current + * frame. Negative values are before the current frame, positive values are drawings after the + * current frame. The magnitude of the value indicates how far the drawing is from the current + * frame (either in absolute frames, or in number of keyframes). */ + const int onion_id; }; struct MutableDrawingInfo { bke::greasepencil::Drawing &drawing; @@ -194,7 +199,8 @@ Vector retrieve_editable_drawings_with_falloff(const Scene & Vector retrieve_editable_drawings_from_layer( const Scene &scene, GreasePencil &grease_pencil, const bke::greasepencil::Layer &layer); Vector retrieve_visible_drawings(const Scene &scene, - const GreasePencil &grease_pencil); + const GreasePencil &grease_pencil, + bool do_onion_skinning); IndexMask retrieve_editable_strokes(Object &grease_pencil_object, const bke::greasepencil::Drawing &drawing, diff --git a/source/blender/editors/interface/templates/interface_template_grease_pencil_layer_tree.cc b/source/blender/editors/interface/templates/interface_template_grease_pencil_layer_tree.cc index ada62f38c7f..316e237dea2 100644 --- a/source/blender/editors/interface/templates/interface_template_grease_pencil_layer_tree.cc +++ b/source/blender/editors/interface/templates/interface_template_grease_pencil_layer_tree.cc @@ -263,6 +263,10 @@ class LayerViewItem : public AbstractTreeViewItem { ICON_CLIPUV_HLT; uiItemR(sub, &layer_ptr, "use_masks", UI_ITEM_R_ICON_ONLY, nullptr, icon_mask); + sub = uiLayoutRow(&row, true); + uiLayoutSetActive(sub, layer_.parent_group().use_onion_skinning()); + uiItemR(sub, &layer_ptr, "use_onion_skinning", UI_ITEM_R_ICON_ONLY, nullptr, ICON_NONE); + sub = uiLayoutRow(&row, true); uiLayoutSetActive(sub, layer_.parent_group().is_visible()); uiItemR(sub, &layer_ptr, "hide", UI_ITEM_R_ICON_ONLY, nullptr, ICON_NONE); @@ -349,6 +353,12 @@ class LayerGroupViewItem : public AbstractTreeViewItem { ICON_CLIPUV_HLT; uiItemR(sub, &group_ptr, "use_masks", UI_ITEM_R_ICON_ONLY, nullptr, icon_mask); + sub = uiLayoutRow(&row, true); + if (group_.as_node().parent_group()) { + uiLayoutSetActive(sub, group_.as_node().parent_group()->use_onion_skinning()); + } + uiItemR(sub, &group_ptr, "use_onion_skinning", UI_ITEM_R_ICON_ONLY, nullptr, ICON_NONE); + sub = uiLayoutRow(&row, true); if (group_.as_node().parent_group()) { uiLayoutSetActive(sub, group_.as_node().parent_group()->is_visible()); diff --git a/source/blender/makesdna/DNA_grease_pencil_defaults.h b/source/blender/makesdna/DNA_grease_pencil_defaults.h new file mode 100644 index 00000000000..b94f0626a00 --- /dev/null +++ b/source/blender/makesdna/DNA_grease_pencil_defaults.h @@ -0,0 +1,38 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup DNA + */ + +#pragma once + +/* clang-format off */ + +/* -------------------------------------------------------------------- */ +/** \name Grease Pencil Struct + * \{ */ + +#define _DNA_DEFAULT_GreasePencilOnionSkinningSettings \ + { \ + .opacity = 0.5f, \ + .mode = GP_ONION_SKINNING_MODE_RELATIVE, \ + .flag = (GP_ONION_SKINNING_USE_FADE | GP_ONION_SKINNING_USE_CUSTOM_COLORS), \ + .filter = GREASE_PENCIL_ONION_SKINNING_FILTER_ALL, \ + .num_frames_before = 1, \ + .num_frames_after = 1, \ + .color_before = {0.145098f, 0.419608f, 0.137255f}, \ + .color_after = {0.125490f, 0.082353f, 0.529412f},\ + } + + +#define _DNA_DEFAULT_GreasePencil \ + { \ + .flag = GREASE_PENCIL_ANIM_CHANNEL_EXPANDED, \ + .onion_skinning_settings = _DNA_DEFAULT_GreasePencilOnionSkinningSettings, \ + } + +/** \} */ + +/* clang-format on */ diff --git a/source/blender/makesdna/DNA_grease_pencil_types.h b/source/blender/makesdna/DNA_grease_pencil_types.h index 646ca37c3b2..862b222d0dc 100644 --- a/source/blender/makesdna/DNA_grease_pencil_types.h +++ b/source/blender/makesdna/DNA_grease_pencil_types.h @@ -238,7 +238,7 @@ typedef enum GreasePencilLayerTreeNodeFlag { GP_LAYER_TREE_NODE_SELECT = (1 << 2), GP_LAYER_TREE_NODE_MUTE = (1 << 3), GP_LAYER_TREE_NODE_USE_LIGHTS = (1 << 4), - GP_LAYER_TREE_NODE_USE_ONION_SKINNING = (1 << 5), + GP_LAYER_TREE_NODE_HIDE_ONION_SKINNING = (1 << 5), GP_LAYER_TREE_NODE_EXPANDED = (1 << 6), GP_LAYER_TREE_NODE_HIDE_MASKS = (1 << 7), } GreasePencilLayerTreeNodeFlag; @@ -352,6 +352,15 @@ typedef enum GreasePencilOnionSkinningMode { GP_ONION_SKINNING_MODE_SELECTED = 2, } GreasePencilOnionSkinningMode; +typedef enum GreasePencilOnionSkinningFlag { + /* Use custom colors (per object-data) for onion skinning. */ + GP_ONION_SKINNING_USE_CUSTOM_COLORS = (1 << 0), + /* Fade the opacity of ghost frames further away from the current frame. */ + GP_ONION_SKINNING_USE_FADE = (1 << 1), + /* Show looping frames in onion skinning. */ + GP_ONION_SKINNING_SHOW_LOOP = (1 << 2), +} GreasePencilOnionSkinningFlag; + /** * Flag for filtering the onion skinning per keyframe type. * #GreasePencilOnionSkinningSettings.filter @@ -378,15 +387,13 @@ typedef struct GreasePencilOnionSkinningSettings { * Opacity for the ghost frames. */ float opacity; - /** - * Onion skinning mode. See `GreasePencilOnionSkinningMode`. - */ + /* #GreasePencilOnionSkinningMode. */ int8_t mode; - /** - * Onion skinning filtering flag. See `GreasePencilOnionSkinningFilter`. - */ + /* #GreasePencilOnionSkinningFlag. */ + uint8_t flag; + /* #GreasePencilOnionSkinningFilter. */ uint8_t filter; - char _pad[2]; + char _pad[1]; /** * Number of ghost frames shown before. */ diff --git a/source/blender/makesdna/intern/dna_defaults.c b/source/blender/makesdna/intern/dna_defaults.c index fa37868e589..1f59711a1ae 100644 --- a/source/blender/makesdna/intern/dna_defaults.c +++ b/source/blender/makesdna/intern/dna_defaults.c @@ -87,6 +87,7 @@ #include "DNA_curves_types.h" #include "DNA_fluid_types.h" #include "DNA_gpencil_modifier_types.h" +#include "DNA_grease_pencil_types.h" #include "DNA_image_types.h" #include "DNA_key_types.h" #include "DNA_lattice_types.h" @@ -120,6 +121,7 @@ #include "DNA_curves_defaults.h" #include "DNA_fluid_defaults.h" #include "DNA_gpencil_modifier_defaults.h" +#include "DNA_grease_pencil_defaults.h" #include "DNA_image_defaults.h" #include "DNA_lattice_defaults.h" #include "DNA_light_defaults.h" @@ -181,6 +183,9 @@ SDNA_DEFAULT_DECL_STRUCT(Image); /* DNA_curves_defaults.h */ SDNA_DEFAULT_DECL_STRUCT(Curves); +/* DNA_grease_pencil_defaults.h */ +SDNA_DEFAULT_DECL_STRUCT(GreasePencil); + /* DNA_lattice_defaults.h */ SDNA_DEFAULT_DECL_STRUCT(Lattice); @@ -429,6 +434,9 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = { /* DNA_curves_defaults.h */ SDNA_DEFAULT_DECL(Curves), + /* DNA_grease_pencil_defaults.h */ + SDNA_DEFAULT_DECL(GreasePencil), + /* DNA_lattice_defaults.h */ SDNA_DEFAULT_DECL(Lattice), diff --git a/source/blender/makesrna/intern/rna_grease_pencil.cc b/source/blender/makesrna/intern/rna_grease_pencil.cc index bf0d580c7fb..bca6d68a063 100644 --- a/source/blender/makesrna/intern/rna_grease_pencil.cc +++ b/source/blender/makesrna/intern/rna_grease_pencil.cc @@ -400,8 +400,9 @@ static void rna_def_grease_pencil_layer(BlenderRNA *brna) /* Onion Skinning. */ prop = RNA_def_property(srna, "use_onion_skinning", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna( - prop, "GreasePencilLayerTreeNode", "flag", GP_LAYER_TREE_NODE_USE_ONION_SKINNING); + RNA_def_property_ui_icon(prop, ICON_ONIONSKIN_OFF, 1); + RNA_def_property_boolean_negative_sdna( + prop, "GreasePencilLayerTreeNode", "flag", GP_LAYER_TREE_NODE_HIDE_ONION_SKINNING); RNA_def_property_ui_text( prop, "Onion Skinning", "Display onion skins before and after the current frame"); RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update"); @@ -542,6 +543,148 @@ static void rna_def_grease_pencil_layer_group(BlenderRNA *brna) "The visibility of drawings in the layers in this group is affected by " "the layers in the masks lists"); RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update"); + + prop = RNA_def_property(srna, "use_onion_skinning", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_ui_icon(prop, ICON_ONIONSKIN_OFF, 1); + RNA_def_property_boolean_negative_sdna( + prop, "GreasePencilLayerTreeNode", "flag", GP_LAYER_TREE_NODE_HIDE_ONION_SKINNING); + RNA_def_property_ui_text( + prop, "Onion Skinning", "Display onion skins before and after the current frame"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update"); +} + +static void rna_def_grease_pencil_onion_skinning(StructRNA *srna) +{ + PropertyRNA *prop; + + static EnumPropertyItem prop_enum_onion_modes_items[] = { + {GP_ONION_SKINNING_MODE_ABSOLUTE, + "ABSOLUTE", + 0, + "Frames", + "Frames in absolute range of the scene frame"}, + {GP_ONION_SKINNING_MODE_RELATIVE, + "RELATIVE", + 0, + "Keyframes", + "Frames in relative range of the Grease Pencil keyframes"}, + {GP_ONION_SKINNING_MODE_SELECTED, "SELECTED", 0, "Selected", "Only selected keyframes"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + static EnumPropertyItem prop_enum_onion_keyframe_type_items[] = { + {GREASE_PENCIL_ONION_SKINNING_FILTER_ALL, "ALL", 0, "All", "Include all Keyframe types"}, + {GP_ONION_SKINNING_FILTER_KEYTYPE_KEYFRAME, + "KEYFRAME", + ICON_KEYTYPE_KEYFRAME_VEC, + "Keyframe", + "Normal keyframe, e.g. for key poses"}, + {GP_ONION_SKINNING_FILTER_KEYTYPE_BREAKDOWN, + "BREAKDOWN", + ICON_KEYTYPE_BREAKDOWN_VEC, + "Breakdown", + "A breakdown pose, e.g. for transitions between key poses"}, + {GP_ONION_SKINNING_FILTER_KEYTYPE_MOVEHOLD, + "MOVING_HOLD", + ICON_KEYTYPE_MOVING_HOLD_VEC, + "Moving Hold", + "A keyframe that is part of a moving hold"}, + {GP_ONION_SKINNING_FILTER_KEYTYPE_EXTREME, + "EXTREME", + ICON_KEYTYPE_EXTREME_VEC, + "Extreme", + "An 'extreme' pose, or some other purpose as needed"}, + {GP_ONION_SKINNING_FILTER_KEYTYPE_JITTER, + "JITTER", + ICON_KEYTYPE_JITTER_VEC, + "Jitter", + "A filler or baked keyframe for keying on ones, or some other purpose as needed"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + prop = RNA_def_property(srna, "ghost_before_range", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, nullptr, "onion_skinning_settings.num_frames_before"); + RNA_def_property_range(prop, 0, 120); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, ParameterFlag(0)); + RNA_def_property_ui_text(prop, + "Frames Before", + "Maximum number of frames to show before current frame " + "(0 = don't show any frames before current)"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update"); + + prop = RNA_def_property(srna, "ghost_after_range", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, nullptr, "onion_skinning_settings.num_frames_after"); + RNA_def_property_range(prop, 0, 120); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, ParameterFlag(0)); + RNA_def_property_ui_text(prop, + "Frames After", + "Maximum number of frames to show after current frame " + "(0 = don't show any frames after current)"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update"); + + prop = RNA_def_property(srna, "use_ghost_custom_colors", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "onion_skinning_settings.flag", GP_ONION_SKINNING_USE_CUSTOM_COLORS); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, ParameterFlag(0)); + RNA_def_property_ui_text(prop, "Use Custom Ghost Colors", "Use custom colors for ghost frames"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update"); + + prop = RNA_def_property(srna, "before_color", PROP_FLOAT, PROP_COLOR); + RNA_def_property_float_sdna(prop, nullptr, "onion_skinning_settings.color_before"); + RNA_def_property_array(prop, 3); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, ParameterFlag(0)); + RNA_def_property_ui_text(prop, "Before Color", "Base color for ghosts before the active frame"); + RNA_def_property_update(prop, + NC_SCREEN | NC_SCENE | ND_TOOLSETTINGS | ND_DATA | NC_GPENCIL, + "rna_grease_pencil_update"); + + prop = RNA_def_property(srna, "after_color", PROP_FLOAT, PROP_COLOR); + RNA_def_property_float_sdna(prop, nullptr, "onion_skinning_settings.color_after"); + RNA_def_property_array(prop, 3); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, ParameterFlag(0)); + RNA_def_property_ui_text(prop, "After Color", "Base color for ghosts after the active frame"); + RNA_def_property_update(prop, + NC_SCREEN | NC_SCENE | ND_TOOLSETTINGS | ND_DATA | NC_GPENCIL, + "rna_grease_pencil_update"); + + prop = RNA_def_property(srna, "onion_mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, nullptr, "onion_skinning_settings.mode"); + RNA_def_property_enum_items(prop, prop_enum_onion_modes_items); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, ParameterFlag(0)); + RNA_def_property_ui_text(prop, "Mode", "Mode to display frames"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update"); + + prop = RNA_def_property(srna, "onion_keyframe_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, nullptr, "onion_skinning_settings.filter"); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, ParameterFlag(0)); + RNA_def_property_enum_items(prop, prop_enum_onion_keyframe_type_items); + RNA_def_property_ui_text(prop, "Filter by Type", "Type of keyframe (for filtering)"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update"); + + prop = RNA_def_property(srna, "use_onion_fade", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "onion_skinning_settings.flag", GP_ONION_SKINNING_USE_FADE); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, ParameterFlag(0)); + RNA_def_property_ui_text( + prop, "Fade", "Display onion keyframes with a fade in color transparency"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update"); + + prop = RNA_def_property(srna, "use_onion_loop", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "onion_skinning_settings.flag", GP_ONION_SKINNING_SHOW_LOOP); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, ParameterFlag(0)); + RNA_def_property_ui_text( + prop, "Show Start Frame", "Display onion keyframes for looping animations"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update"); + + prop = RNA_def_property(srna, "onion_factor", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, nullptr, "onion_skinning_settings.opacity"); + RNA_def_property_range(prop, 0.0, 1.0f); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, ParameterFlag(0)); + RNA_def_property_ui_text(prop, "Onion Opacity", "Change fade opacity of displayed onion frames"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update"); } static void rna_def_grease_pencil_data(BlenderRNA *brna) @@ -632,6 +775,9 @@ static void rna_def_grease_pencil_data(BlenderRNA *brna) "Stroke Depth Order", "Defines how the strokes are ordered in 3D space (for objects not displayed 'In Front')"); RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update"); + + /* Onion skinning. */ + rna_def_grease_pencil_onion_skinning(srna); } void RNA_def_grease_pencil(BlenderRNA *brna)