2021-05-24 17:06:10 +02:00
|
|
|
import bpy
|
|
|
|
from bpy.types import Operator
|
|
|
|
|
|
|
|
|
|
|
|
def get_layer_list(self, context):
|
|
|
|
'''return (identifier, name, description) of enum content'''
|
2021-12-22 14:11:31 +01:00
|
|
|
if not context:
|
|
|
|
return [('None', 'None','None')]
|
2021-10-29 16:49:38 +02:00
|
|
|
if not context.object:
|
2021-12-22 14:11:31 +01:00
|
|
|
return [('None', 'None','None')]
|
2024-11-11 15:56:43 +01:00
|
|
|
return [(l.name, l.name, '') for l in context.object.data.layers if l != context.object.data.layers.active]
|
2021-05-24 17:06:10 +02:00
|
|
|
# try:
|
|
|
|
# except:
|
|
|
|
# return [("", "", "")]
|
|
|
|
# return [(i, basename(i), "") for i in blends]
|
|
|
|
# return [(i.path, basename(i.path), "") for i in self.blends]
|
|
|
|
|
|
|
|
class GPTB_OT_duplicate_send_to_layer(Operator) :
|
|
|
|
bl_idname = "gp.duplicate_send_to_layer"
|
2024-03-27 12:09:57 +01:00
|
|
|
bl_label = 'Duplicate Send To Layer'
|
|
|
|
bl_description = 'Duplicate selected keys in active layer and send to chosen layer'
|
2021-05-24 17:06:10 +02:00
|
|
|
# important to have the updated enum here as bl_property
|
|
|
|
bl_property = "layers_enum"
|
|
|
|
|
|
|
|
layers_enum : bpy.props.EnumProperty(
|
|
|
|
name="Duplicate to layers",
|
2024-03-27 12:09:57 +01:00
|
|
|
description="Duplicate selected keys in active layer and send them to chosen layer",
|
2021-12-22 14:11:31 +01:00
|
|
|
items=get_layer_list,
|
|
|
|
options={'HIDDEN'},
|
2021-05-24 17:06:10 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
delete_source : bpy.props.BoolProperty(default=False, options={'SKIP_SAVE'})
|
|
|
|
|
2024-03-27 12:09:57 +01:00
|
|
|
@classmethod
|
|
|
|
def description(cls, context, properties):
|
|
|
|
if properties.delete_source:
|
|
|
|
return f"Move selected keys in active layer to chosen layer"
|
|
|
|
else:
|
|
|
|
return f"Copy selected keys in active layer and send to chosen layer"
|
|
|
|
|
2021-05-24 17:06:10 +02:00
|
|
|
@classmethod
|
|
|
|
def poll(cls, context):
|
2024-11-11 15:35:39 +01:00
|
|
|
return context.object and context.object.type == 'GREASEPENCIL'\
|
2024-11-14 19:09:52 +01:00
|
|
|
and context.space_data.bl_rna.identifier == 'SpaceDopeSheetEditor' and context.space_data.ui_mode == 'GPENCIL'
|
2021-05-24 17:06:10 +02:00
|
|
|
|
|
|
|
# history : bpy.props.StringProperty(default='', options={'SKIP_SAVE'}) # need to have a variable to store (to get it in self)
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
target_layer = self.layers_enum
|
|
|
|
if not target_layer:
|
|
|
|
self.report({'WARNING'}, 'Target layer not specified')
|
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
|
|
gpl = context.object.data.layers
|
|
|
|
target_layer = gpl.get(target_layer)
|
|
|
|
|
|
|
|
act_layer = gpl.active
|
|
|
|
selected_frames = [f for f in act_layer.frames if f.select]
|
|
|
|
|
|
|
|
act_frame_num = [f.frame_number for f in act_layer.frames if f.select]
|
|
|
|
|
|
|
|
to_replace = [f for f in target_layer.frames if f.frame_number in act_frame_num]
|
|
|
|
|
|
|
|
replaced = len(to_replace)
|
|
|
|
|
2024-03-27 12:09:57 +01:00
|
|
|
## Remove overlapping frames
|
2021-05-24 17:06:10 +02:00
|
|
|
for f in reversed(to_replace):
|
|
|
|
target_layer.frames.remove(f)
|
|
|
|
|
2024-11-14 19:09:52 +01:00
|
|
|
# FIXME : need to createa a dedicated function to copy a frame
|
2024-03-27 12:09:57 +01:00
|
|
|
## Copy original frames
|
2021-05-24 17:06:10 +02:00
|
|
|
for f in selected_frames:
|
|
|
|
target_layer.frames.copy(f)
|
|
|
|
sent = len(selected_frames)
|
|
|
|
|
2024-03-27 12:09:57 +01:00
|
|
|
## Delete original frames as an option
|
2021-05-24 17:06:10 +02:00
|
|
|
if self.delete_source:
|
|
|
|
for f in reversed(selected_frames):
|
|
|
|
act_layer.frames.remove(f)
|
|
|
|
|
|
|
|
mess = f'{sent} keys copied'
|
|
|
|
if replaced:
|
|
|
|
mess += f' ({replaced} replaced)'
|
|
|
|
|
|
|
|
mod = context.mode
|
|
|
|
bpy.ops.gpencil.editmode_toggle()
|
|
|
|
bpy.ops.object.mode_set(mode=mod)
|
|
|
|
|
|
|
|
self.report({'INFO'}, mess)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
def invoke(self, context, event):
|
|
|
|
gp = context.object.data
|
|
|
|
if not len(gp.layers):
|
|
|
|
self.report({'WARNING'}, 'No layers on current GP object')
|
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
|
|
active = gp.layers.active
|
|
|
|
|
|
|
|
if not active:
|
|
|
|
self.report({'WARNING'}, 'No active layer to take keys from')
|
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
|
|
self.selected_frames = [f for f in active.frames if f.select]
|
|
|
|
|
|
|
|
if not self.selected_frames:
|
|
|
|
self.report({'WARNING'}, 'No selected keys in active layer')
|
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
|
|
wm = context.window_manager
|
|
|
|
wm.invoke_search_popup(self) # can't specify size... width=500, height=600
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
|
addon_keymaps = []
|
|
|
|
def register_keymaps():
|
|
|
|
addon = bpy.context.window_manager.keyconfigs.addon
|
|
|
|
km = addon.keymaps.new(name = "Dopesheet", space_type = "DOPESHEET_EDITOR")
|
2021-10-20 16:05:56 +02:00
|
|
|
kmi = km.keymap_items.new('gp.duplicate_send_to_layer', type='D', value="PRESS", ctrl=True, shift=True)
|
2021-05-24 17:06:10 +02:00
|
|
|
addon_keymaps.append((km,kmi))
|
2021-12-04 13:57:32 +01:00
|
|
|
|
|
|
|
# km = addon.keymaps.new(name = "Dopesheet", space_type = "DOPESHEET_EDITOR") # try duplicating km (seem to be error at unregsiter)
|
2021-10-20 16:05:56 +02:00
|
|
|
kmi = km.keymap_items.new('gp.duplicate_send_to_layer', type='X', value="PRESS", ctrl=True, shift=True)
|
2021-05-24 17:06:10 +02:00
|
|
|
kmi.properties.delete_source = True
|
|
|
|
addon_keymaps.append((km,kmi))
|
|
|
|
|
|
|
|
|
|
|
|
def unregister_keymaps():
|
|
|
|
for km, kmi in addon_keymaps:
|
|
|
|
km.keymap_items.remove(kmi)
|
|
|
|
addon_keymaps.clear()
|
|
|
|
|
|
|
|
|
2024-03-27 12:09:57 +01:00
|
|
|
def menu_duplicate_and_send_to_layer(self, context):
|
2024-11-14 19:09:52 +01:00
|
|
|
if context.space_data.ui_mode == 'GPENCIL':
|
2024-03-27 12:09:57 +01:00
|
|
|
self.layout.operator_context = 'INVOKE_REGION_WIN'
|
|
|
|
self.layout.operator('gp.duplicate_send_to_layer', text='Move Keys To Layer').delete_source = True
|
|
|
|
self.layout.operator('gp.duplicate_send_to_layer', text='Copy Keys To Layer')
|
|
|
|
|
2021-05-24 17:06:10 +02:00
|
|
|
classes = (
|
|
|
|
GPTB_OT_duplicate_send_to_layer,
|
|
|
|
)
|
|
|
|
|
|
|
|
def register():
|
2021-12-04 13:57:32 +01:00
|
|
|
if bpy.app.background:
|
|
|
|
return
|
|
|
|
|
|
|
|
for cls in classes:
|
|
|
|
bpy.utils.register_class(cls)
|
2024-03-27 12:09:57 +01:00
|
|
|
|
2021-12-04 13:57:32 +01:00
|
|
|
register_keymaps()
|
2024-11-11 17:30:33 +01:00
|
|
|
bpy.types.DOPESHEET_MT_key.append(menu_duplicate_and_send_to_layer)
|
2024-03-27 12:09:57 +01:00
|
|
|
bpy.types.DOPESHEET_MT_context_menu.append(menu_duplicate_and_send_to_layer)
|
|
|
|
|
2021-05-24 17:06:10 +02:00
|
|
|
|
|
|
|
def unregister():
|
2021-12-04 13:57:32 +01:00
|
|
|
if bpy.app.background:
|
|
|
|
return
|
2024-03-27 12:09:57 +01:00
|
|
|
|
|
|
|
bpy.types.DOPESHEET_MT_context_menu.remove(menu_duplicate_and_send_to_layer)
|
2024-11-11 17:30:33 +01:00
|
|
|
bpy.types.DOPESHEET_MT_key.remove(menu_duplicate_and_send_to_layer)
|
2021-12-04 13:57:32 +01:00
|
|
|
unregister_keymaps()
|
2024-03-27 12:09:57 +01:00
|
|
|
|
2021-12-04 13:57:32 +01:00
|
|
|
for cls in reversed(classes):
|
|
|
|
bpy.utils.unregister_class(cls)
|