GPv3: Onion Skinning

Implements the GPv2 onion skinning functionality.

There are no functional changes exept for the `use_ghosts_always`
option, which has been removed. This was used to show onion
skinning in the final render.

Pull Request: https://projects.blender.org/blender/blender/pulls/119792
This commit is contained in:
Falk David
2024-04-03 15:34:40 +02:00
committed by Falk David
parent 1e2e20fe3c
commit 8512a608a4
18 changed files with 536 additions and 57 deletions

View File

@@ -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,

View File

@@ -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()

View File

@@ -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

View File

@@ -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
{

View File

@@ -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<GreasePencil *>(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<greasepencil::LayerGroup>(__func__);
grease_pencil->active_layer = nullptr;
grease_pencil->flag |= GREASE_PENCIL_ANIM_CHANNEL_EXPANDED;
CustomData_reset(&grease_pencil->layers_data);

View File

@@ -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);

View File

@@ -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<int> /*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<GPENCIL_tLayer *>(BLI_memblock_alloc(pd->gp_layer_pool));

View File

@@ -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<int> onion_id,
int onion_id,
GPENCIL_tObject *tgp_ob);
/**
* Creates a linked list of material pool containing all materials assigned for a given object.

View File

@@ -614,6 +614,9 @@ static GPENCIL_tObject *grease_pencil_object_cache_populate(GPENCIL_PrivateData
const blender::Bounds<float3> 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<DrawingInfo> 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<DrawingInfo> drawings = retrieve_visible_drawings(*pd->scene, grease_pencil, true);
const Span<const Layer *> 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() +

View File

@@ -293,7 +293,7 @@ static void OVERLAY_outline_grease_pencil(OVERLAY_PrivateData *pd, Scene *scene,
}
int t_offset = 0;
const Vector<DrawingInfo> drawings = retrieve_visible_drawings(*scene, grease_pencil);
const Vector<DrawingInfo> 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;
});

View File

@@ -229,7 +229,7 @@ static void grease_pencil_edit_batch_ensure(Object &object,
/* Get the visible drawings. */
const Vector<ed::greasepencil::DrawingInfo> drawings =
ed::greasepencil::retrieve_visible_drawings(scene, grease_pencil);
ed::greasepencil::retrieve_visible_drawings(scene, grease_pencil, false);
const Span<const Layer *> layers = grease_pencil.layers();
@@ -423,7 +423,7 @@ static void grease_pencil_geom_batch_ensure(Object &object,
/* Get the visible drawings. */
const Vector<ed::greasepencil::DrawingInfo> 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. */

View File

@@ -230,9 +230,132 @@ static std::pair<int, int> get_minmax_selected_frame_numbers(const GreasePencil
return std::pair<int, int>(frame_min, frame_max);
}
static Array<int> get_frame_numbers_for_layer(const bke::greasepencil::Layer &layer,
const int current_frame,
const bool use_multi_frame_editing)
static std::optional<int> 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<std::pair<int, int>> 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<std::pair<int, int>> frame_numbers;
const Span<int> sorted_keys = layer.sorted_keys();
if (sorted_keys.is_empty()) {
return {};
}
const std::optional<bke::greasepencil::FramesMapKey> 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<int> 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<int> get_editable_frames_for_layer(const bke::greasepencil::Layer &layer,
const int current_frame,
const bool use_multi_frame_editing)
{
Vector<int> frame_numbers;
if (use_multi_frame_editing) {
@@ -271,7 +394,7 @@ Vector<MutableDrawingInfo> retrieve_editable_drawings(const Scene &scene,
if (!layer.is_editable()) {
continue;
}
const Array<int> frame_numbers = get_frame_numbers_for_layer(
const Array<int> 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<MutableDrawingInfo> retrieve_editable_drawings_with_falloff(const Scene &
if (!layer.is_editable()) {
continue;
}
const Array<int> frame_numbers = get_frame_numbers_for_layer(
const Array<int> 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<MutableDrawingInfo> retrieve_editable_drawings_from_layer(
GP_USE_MULTI_FRAME_EDITING) != 0;
Vector<MutableDrawingInfo> editable_drawings;
const Array<int> frame_numbers = get_frame_numbers_for_layer(
const Array<int> 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<MutableDrawingInfo> retrieve_editable_drawings_from_layer(
}
Vector<DrawingInfo> 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<DrawingInfo> retrieve_visible_drawings(const Scene &scene,
if (!layer.is_visible()) {
continue;
}
const Array<int> frame_numbers = get_frame_numbers_for_layer(
layer, current_frame, use_multi_frame_editing);
for (const int frame_number : frame_numbers) {
const Array<std::pair<int, int>> 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});
}
}
}

View File

@@ -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<MutableDrawingInfo> retrieve_editable_drawings_with_falloff(const Scene &
Vector<MutableDrawingInfo> retrieve_editable_drawings_from_layer(
const Scene &scene, GreasePencil &grease_pencil, const bke::greasepencil::Layer &layer);
Vector<DrawingInfo> 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,

View File

@@ -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());

View File

@@ -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 */

View File

@@ -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.
*/

View File

@@ -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),

View File

@@ -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)