improved Filtered create empty frames ops

1.7.1

- feat: Improved `Create Empty Frames` operator with mutliple filters to choose source layers
gpv2
Pullusb 2021-10-20 16:05:56 +02:00
parent f7e8dce0ff
commit b62a23858c
4 changed files with 140 additions and 15 deletions

View File

@ -1,5 +1,8 @@
# Changelog
1.7.1
- feat: Improved `Create Empty Frames` operator with mutliple filters to choose source layers
1.7.0

View File

@ -1,33 +1,156 @@
## Create empty keyframe where keyframe exists in layers above.
import bpy
from bpy.props import (FloatProperty,
BoolProperty,
EnumProperty,
StringProperty,
IntProperty)
## copied from OP_key_duplicate_send
def get_layer_list(self, context):
'''return (identifier, name, description) of enum content'''
return [(l.info, l.info, '') for l in context.object.data.layers if l != context.object.data.layers.active]
class GP_OT_create_empty_frames(bpy.types.Operator):
bl_idname = "gp.create_empty_frames"
bl_label = "Create empty frames"
bl_label = "Create Empty Frames"
bl_description = "Create new empty frames on active layer where there is a frame in layer above\n(usefull in color layers to match line frames)"
bl_options = {'REGISTER','UNDO'}
layers_enum : EnumProperty(
name="Duplicate to layers",
description="Duplicate selected keys in active layer and send them to choosen layer",
items=get_layer_list
)
targeted_layers : EnumProperty(
name="Sources", # Empty keys from targets
description="Duplicate keys as empty on current layer from selected targets",
default="ALL_ABOVE",
items=(
('ALL_ABOVE', 'All Layers Above', 'Empty frames from all layers above'),
('ALL_BELOW', 'All Layers Below', 'Empty frames from all layers below'),
('NUMBER', 'Number Above Or Below', 'Positive number above layers\nNegative number below layers'),
('ABOVE', 'Layer Directly Above', 'Empty frames from layer directly above'),
('BELOW', 'Layer Directly Below', 'Empty frames from layer directly below'),
('ALL_VISIBLE', 'Visible', 'Empty frames from all visible layers'),
('CHOSEN', 'Chosen layer', ''),
)
)
range : EnumProperty(
name="Range",
description="Restraint empty copy from a defined range",
default="FULL",
items=(
('FULL', 'Full range', 'Empty frames from all layers above'),
('BEFORE', 'Before Time Cursor', 'Empty frames from all layers below'),
('AFTER', 'After Time Cursor', 'Only After time cursor'),
('SCENE', 'On scene range', 'Restric to Scene/Preview range'),
)
)
number : IntProperty(name='Number',
default=1,
description='Number of layer to create empty key from\nabove (positive) or layer below (negative)',
options={'SKIP_SAVE'})
@classmethod
def poll(cls, context):
return context.active_object is not None and context.active_object.type == 'GPENCIL'
def invoke(self, context, event):
# Possible preset with shortcut
# if event.alt:
# self.targeted_layers = 'ALL_VISIBLE'
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
# layout.label(text='Create Empty Frames From Other Layers')
# target
layout.prop(self, 'targeted_layers')
if self.targeted_layers == 'CHOSEN':
if self.layers_enum:
layout.prop(self, 'layers_enum')
else:
layout.label(text='No other layers to match keyframe')
elif self.targeted_layers == 'NUMBER':
row = layout.row()
row.prop(self, 'number')
row.active = self.number != 0
if self.number == 0:
layout.label(text="Can't have 0 as value")
layout.separator()
layout.prop(self, 'range')
if self.range == 'SCENE':
if context.scene.use_preview_range:
layout.label(text='Using preview range', icon='INFO')
def execute(self, context):
obj = context.object
gpl = obj.data.layers
gpl.active_index
print(self.targeted_layers)
if self.targeted_layers == 'ALL_ABOVE':
tgt_layers = [l for i, l in enumerate(gpl) if i > gpl.active_index]
elif self.targeted_layers == 'ALL_BELOW':
tgt_layers = [l for i, l in enumerate(gpl) if i < gpl.active_index]
elif self.targeted_layers == 'ABOVE':
tgt_layers = [l for i, l in enumerate(gpl) if i == gpl.active_index + 1]
elif self.targeted_layers == 'BELOW':
tgt_layers = [l for i, l in enumerate(gpl) if i == gpl.active_index - 1]
elif self.targeted_layers == 'ALL_VISIBLE':
tgt_layers = [l for l in gpl if not l.hide and l != gpl.active]
elif self.targeted_layers == 'CHOSEN':
if not self.layers_enum:
self.report({'ERROR'}, f"No chosen layers, aborted")
return {'CANCELLED'}
tgt_layers = [l for l in gpl if l.info == self.layers_enum]
elif self.targeted_layers == 'NUMBER':
if self.number == 0:
self.report({'ERROR'}, f"Can't have 0 as value")
return {'CANCELLED'}
l_range = gpl.active_index + self.number
print('l_range: ', l_range)
if self.number > 0: # positive
tgt_layers = [l for i, l in enumerate(gpl) if gpl.active_index < i <= l_range]
else:
tgt_layers = [l for i, l in enumerate(gpl) if gpl.active_index > i >= l_range]
## Only possible on 'fill' layer ??
# if not 'fill' in gpl.active.info.lower():
# self.report({'ERROR'}, f"There must be 'fill' text in layer name")
# return {'CANCELLED'}
if not tgt_layers:
self.report({'ERROR'}, f"No layers found with chosen Targets")
return {'CANCELLED'}
frame_id_list = []
for i, l in enumerate(gpl):
# don't list layer below
if i <= gpl.active_index:
continue
# print(l.info, "index:", i)
for l in tgt_layers:
for f in l.frames:
## frame filter
if self.range != 'FULL': # FULl = No filter
if self.range == 'BEFORE':
if not f.frame_number <= context.scene.frame_current:
continue
elif self.range == 'AFTER':
if not f.frame_number >= context.scene.frame_current:
continue
elif self.range == 'SCENE':
if context.scene.use_preview_range:
if not context.scene.frame_preview_start <= f.frame_number <= context.scene.frame_preview_end:
continue
else:
if not context.scene.frame_start <= f.frame_number <= context.scene.frame_end:
continue
frame_id_list.append(f.frame_number)
frame_id_list = list(set(frame_id_list))
@ -50,7 +173,6 @@ class GP_OT_create_empty_frames(bpy.types.Operator):
return {'FINISHED'}
def register():
bpy.utils.register_class(GP_OT_create_empty_frames)

View File

@ -110,9 +110,9 @@ def register_keymaps():
addon = bpy.context.window_manager.keyconfigs.addon
# km = addon.keymaps.new(name = "Screen", space_type = "EMPTY")
km = addon.keymaps.new(name = "Dopesheet", space_type = "DOPESHEET_EDITOR")
kmi = km.keymap_items.new('gp.duplicate_send_to_layer', type='D', value="PRESS", alt=False, ctrl=True, shift=True, any=False)
kmi = km.keymap_items.new('gp.duplicate_send_to_layer', type='D', value="PRESS", ctrl=True, shift=True)
addon_keymaps.append((km,kmi))
kmi = km.keymap_items.new('gp.duplicate_send_to_layer', type='X', value="PRESS", alt=False, ctrl=True, shift=True, any=False)
kmi = km.keymap_items.new('gp.duplicate_send_to_layer', type='X', value="PRESS", ctrl=True, shift=True)
kmi.properties.delete_source = True
addon_keymaps.append((km,kmi))

View File

@ -15,7 +15,7 @@ bl_info = {
"name": "GP toolbox",
"description": "Set of tools for Grease Pencil in animation production",
"author": "Samuel Bernou, Christophe Seux",
"version": (1, 7, 0),
"version": (1, 7, 1),
"blender": (2, 91, 0),
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
"warning": "",