improved Filtered create empty frames ops
1.7.1 - feat: Improved `Create Empty Frames` operator with mutliple filters to choose source layersgpv2
parent
f7e8dce0ff
commit
b62a23858c
|
@ -1,5 +1,8 @@
|
|||
# Changelog
|
||||
|
||||
1.7.1
|
||||
|
||||
- feat: Improved `Create Empty Frames` operator with mutliple filters to choose source layers
|
||||
|
||||
1.7.0
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
## 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'}
|
||||
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]
|
||||
|
||||
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)
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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": "",
|
||||
|
|
Loading…
Reference in New Issue