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
|
# Changelog
|
||||||
|
|
||||||
|
1.7.1
|
||||||
|
|
||||||
|
- feat: Improved `Create Empty Frames` operator with mutliple filters to choose source layers
|
||||||
|
|
||||||
1.7.0
|
1.7.0
|
||||||
|
|
||||||
|
|
|
@ -1,33 +1,156 @@
|
||||||
## Create empty keyframe where keyframe exists in layers above.
|
## Create empty keyframe where keyframe exists in layers above.
|
||||||
import bpy
|
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):
|
class GP_OT_create_empty_frames(bpy.types.Operator):
|
||||||
bl_idname = "gp.create_empty_frames"
|
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_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'}
|
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
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return context.active_object is not None and context.active_object.type == 'GPENCIL'
|
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):
|
def execute(self, context):
|
||||||
obj = context.object
|
obj = context.object
|
||||||
gpl = obj.data.layers
|
gpl = obj.data.layers
|
||||||
gpl.active_index
|
|
||||||
|
|
||||||
## Only possible on 'fill' layer ??
|
print(self.targeted_layers)
|
||||||
# if not 'fill' in gpl.active.info.lower():
|
if self.targeted_layers == 'ALL_ABOVE':
|
||||||
# self.report({'ERROR'}, f"There must be 'fill' text in layer name")
|
tgt_layers = [l for i, l in enumerate(gpl) if i > gpl.active_index]
|
||||||
# return {'CANCELLED'}
|
|
||||||
|
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 = []
|
frame_id_list = []
|
||||||
for i, l in enumerate(gpl):
|
for l in tgt_layers:
|
||||||
# don't list layer below
|
|
||||||
if i <= gpl.active_index:
|
|
||||||
continue
|
|
||||||
# print(l.info, "index:", i)
|
|
||||||
for f in l.frames:
|
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.append(f.frame_number)
|
||||||
|
|
||||||
frame_id_list = list(set(frame_id_list))
|
frame_id_list = list(set(frame_id_list))
|
||||||
|
@ -50,7 +173,6 @@ class GP_OT_create_empty_frames(bpy.types.Operator):
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
bpy.utils.register_class(GP_OT_create_empty_frames)
|
bpy.utils.register_class(GP_OT_create_empty_frames)
|
||||||
|
|
||||||
|
|
|
@ -110,9 +110,9 @@ def register_keymaps():
|
||||||
addon = bpy.context.window_manager.keyconfigs.addon
|
addon = bpy.context.window_manager.keyconfigs.addon
|
||||||
# km = addon.keymaps.new(name = "Screen", space_type = "EMPTY")
|
# km = addon.keymaps.new(name = "Screen", space_type = "EMPTY")
|
||||||
km = addon.keymaps.new(name = "Dopesheet", space_type = "DOPESHEET_EDITOR")
|
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))
|
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
|
kmi.properties.delete_source = True
|
||||||
addon_keymaps.append((km,kmi))
|
addon_keymaps.append((km,kmi))
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ bl_info = {
|
||||||
"name": "GP toolbox",
|
"name": "GP toolbox",
|
||||||
"description": "Set of tools for Grease Pencil in animation production",
|
"description": "Set of tools for Grease Pencil in animation production",
|
||||||
"author": "Samuel Bernou, Christophe Seux",
|
"author": "Samuel Bernou, Christophe Seux",
|
||||||
"version": (1, 7, 0),
|
"version": (1, 7, 1),
|
||||||
"blender": (2, 91, 0),
|
"blender": (2, 91, 0),
|
||||||
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
||||||
"warning": "",
|
"warning": "",
|
||||||
|
|
Loading…
Reference in New Issue