From 6cf22b81e8743d2f590155e7c79357f589fbd281 Mon Sep 17 00:00:00 2001 From: Pullusb Date: Mon, 24 May 2021 17:06:10 +0200 Subject: [PATCH] feat: duplicate send keys 1.3.0: - feat: new duplicate send to layer feaure - `ctrl + shift + D` in GP dopesheet --- CHANGELOG.md | 4 ++ OP_key_duplicate_send.py | 139 +++++++++++++++++++++++++++++++++++++++ __init__.py | 5 +- 3 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 OP_key_duplicate_send.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fa3f80..4ba8f83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog +1.3.0: + +- feat: new duplicate send to layer feaure - `ctrl + shift + D` in GP dopesheet + 1.2.2: - fix: realign anim return error diff --git a/OP_key_duplicate_send.py b/OP_key_duplicate_send.py new file mode 100644 index 0000000..4e4931b --- /dev/null +++ b/OP_key_duplicate_send.py @@ -0,0 +1,139 @@ +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) \ No newline at end of file diff --git a/__init__.py b/__init__.py index 7577456..1eb218e 100644 --- a/__init__.py +++ b/__init__.py @@ -15,7 +15,7 @@ bl_info = { "name": "GP toolbox", "description": "Set of tools for Grease Pencil in animation production", "author": "Samuel Bernou", -"version": (1, 2, 2), +"version": (1, 3, 0), "blender": (2, 91, 0), "location": "sidebar (N menu) > Gpencil > Toolbox / Gpencil properties", "warning": "", @@ -46,6 +46,7 @@ from . import OP_render from . import OP_copy_paste from . import OP_realign from . import OP_depth_move +from . import OP_key_duplicate_send from . import keymaps from .OP_pseudo_tint import GPT_OT_auto_tint_gp_layers @@ -446,6 +447,7 @@ def register(): OP_copy_paste.register() OP_realign.register() OP_depth_move.register() + OP_key_duplicate_send.register() UI_tools.register() keymaps.register() bpy.types.Scene.gptoolprops = bpy.props.PointerProperty(type = GP_PG_ToolsSettings) @@ -467,6 +469,7 @@ def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls) UI_tools.unregister() + OP_key_duplicate_send.unregister() OP_depth_move.unregister() OP_realign.unregister() OP_copy_paste.unregister()