import bpy from bpy.types import Operator 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] # 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 and send to 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 choosen layer", items=get_layer_list ) delete_source : bpy.props.BoolProperty(default=False, options={'SKIP_SAVE'}) @classmethod def poll(cls, context): return context.object and context.object.type == 'GPENCIL' 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) ## copy original frames for f in selected_frames: target_layer.frames.copy(f) 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) mess = f'{sent} keys copied' if replaced: mess += f' ({replaced} replaced)' # context.view_layer.update() # bpy.ops.gpencil.editmode_toggle() 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(): # pref = get_addon_prefs() # if not pref.kfj_use_shortcut: # return 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) 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.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() classes = ( GPTB_OT_duplicate_send_to_layer, ) def register(): if not bpy.app.background: for cls in classes: bpy.utils.register_class(cls) register_keymaps() def unregister(): if not bpy.app.background: unregister_keymaps() for cls in reversed(classes): bpy.utils.unregister_class(cls)