import bpy from bpy.types import Operator from . import utils def get_layer_list(self, context): '''return (identifier, name, description) of enum content''' if not context: return [('None', 'None','None')] if not context.object: return [('None', 'None','None')] return [(l.name, l.name, '') for l in context.object.data.layers if l != context.object.data.layers.active] # 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" bl_label = 'Duplicate Send To Layer' bl_description = 'Duplicate selected keys in active layer and send to chosen layer' # important to have the updated enum here as bl_property bl_property = "layers_enum" layers_enum : bpy.props.EnumProperty( name="Duplicate to layers", description="Duplicate selected keys in active layer and send them to chosen layer", items=get_layer_list, options={'HIDDEN'}, ) delete_source : bpy.props.BoolProperty(default=False, options={'SKIP_SAVE'}) @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" @classmethod def poll(cls, context): return context.object and context.object.type == 'GREASEPENCIL'\ and context.space_data.bl_rna.identifier == 'SpaceDopeSheetEditor' and context.space_data.ui_mode == 'GPENCIL' # 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) ## Remove overlapping frames for f in reversed(to_replace): target_layer.frames.remove(f.frame_number) ## Copy original frames for f in selected_frames: utils.copy_frame_at(f, target_layer, f.frame_number) # target_layer.frames.copy(f) # GPv2 sent = len(selected_frames) ## Delete original frames as an option if self.delete_source: for f in reversed(selected_frames): act_layer.frames.remove(f.frame_number) mess = f'{sent} keys moved' else: mess = f'{sent} keys copied' if replaced: mess += f' ({replaced} replaced)' 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") kmi = km.keymap_items.new('gp.duplicate_send_to_layer', type='D', value="PRESS", ctrl=True, shift=True) addon_keymaps.append((km,kmi)) # km = addon.keymaps.new(name = "Dopesheet", space_type = "DOPESHEET_EDITOR") # try duplicating km (seem to be error at unregsiter) 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)) def unregister_keymaps(): for km, kmi in addon_keymaps: km.keymap_items.remove(kmi) addon_keymaps.clear() def menu_duplicate_and_send_to_layer(self, context): if context.space_data.ui_mode == 'GPENCIL': 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') classes = ( GPTB_OT_duplicate_send_to_layer, ) def register(): if bpy.app.background: return for cls in classes: bpy.utils.register_class(cls) register_keymaps() bpy.types.DOPESHEET_MT_key.append(menu_duplicate_and_send_to_layer) bpy.types.DOPESHEET_MT_context_menu.append(menu_duplicate_and_send_to_layer) def unregister(): if bpy.app.background: return bpy.types.DOPESHEET_MT_context_menu.remove(menu_duplicate_and_send_to_layer) bpy.types.DOPESHEET_MT_key.remove(menu_duplicate_and_send_to_layer) unregister_keymaps() for cls in reversed(classes): bpy.utils.unregister_class(cls)