GPv3: Onion Skinning #119792

Merged
Falk David merged 20 commits from filedescriptor/blender:gpv3-old-onion-skinning into main 2024-04-03 15:34:51 +02:00
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
filedescriptor marked this conversation as resolved Outdated

What's the purpose of this check? Maybe add a comment.

What's the purpose of this check? Maybe add a comment.

My guess is that this is to indicate that the onion skinning settings are used by multiple users and shouldn't be edited? I don't know. I can also remove the check. It was just taken from the GPv2 code.

My guess is that this is to indicate that the onion skinning settings are used by multiple users and shouldn't be edited? I don't know. I can also remove the check. It was just taken from the GPv2 code.
# 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()) {
filedescriptor marked this conversation as resolved Outdated

else after return

else after return
/* 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;
filedescriptor marked this conversation as resolved Outdated

Add a comment what the onion_id is (distance from current frame i suppose).

Add a comment what the `onion_id` is (distance from current frame i suppose).
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),
filedescriptor marked this conversation as resolved
Review

What's the reason for using a negative flag here? The RNA property is positive too.

What's the reason for using a negative flag here? The RNA property is positive too.
Review

The flags on tree nodes are "inherited" when you access them (e.g. if the parent group of a layer is not visible, the layer is also not visible). This also applies to onion skinning. So to avoid having to change the root group value, it seemed easier to just invert the flag instead so that the default of 0 is sensible (onion skinning is shown).

The flags on tree nodes are "inherited" when you access them (e.g. if the parent group of a layer is not visible, the layer is also not visible). This also applies to onion skinning. So to avoid having to change the root group value, it seemed easier to just invert the flag instead so that the default of `0` is sensible (onion skinning is shown).
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;
filedescriptor marked this conversation as resolved Outdated

Usually we just comment flags with /* #GreasePencilOnionSkinningFlag. */

Usually we just comment flags with `/* #GreasePencilOnionSkinningFlag. */`
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)