From 6774484226083693d004f0a04a1f8c1ec3ceca47 Mon Sep 17 00:00:00 2001 From: Pullusb Date: Tue, 19 Jan 2021 01:11:18 +0100 Subject: [PATCH] keyframe jump autobind 0.9.3: - feat: keyframe jump keys are now auto-binded - UI: added keyframe jump customisation in addon pref - code: split keyframe jump in a separate file with his new key updater --- OP_helpers.py | 77 ------------------- OP_keyframe_jump.py | 177 ++++++++++++++++++++++++++++++++++++++++++++ README.md | 20 ++++- README_FR.md | 4 +- __init__.py | 86 ++++++++++++++++++++- 5 files changed, 282 insertions(+), 82 deletions(-) create mode 100644 OP_keyframe_jump.py diff --git a/OP_helpers.py b/OP_helpers.py index fab7429..9bee037 100644 --- a/OP_helpers.py +++ b/OP_helpers.py @@ -32,82 +32,6 @@ class GPTB_OT_flipx_view(bpy.types.Operator): context.scene.camera.scale.x *= -1 return {"FINISHED"} - -class GPTB_OT_jump_gp_keyframe(bpy.types.Operator): - bl_idname = "screen.gp_keyframe_jump" - bl_label = "Jump to GPencil keyframe" - bl_description = "Jump to prev/next keyframe on active and selected layers of active grease pencil object" - bl_options = {"REGISTER"} - - @classmethod - def poll(cls, context): - return context.object and context.object.type == 'GPENCIL' - - next : bpy.props.BoolProperty( - name="Next GP keyframe", description="Go to next active GP keyframe", default=True) - - target : bpy.props.EnumProperty( - name="Target layer", description="Choose wich layer to evaluate for keyframe change", default='ACTIVE',# options={'ANIMATABLE'}, update=None, get=None, set=None, - items=( - ('ACTIVE', 'Active and selected', 'jump in keyframes of active and other selected layers ', 0), - ('VISIBLE', 'Visibles layers', 'jump in keyframes of visibles layers', 1), - ('ACCESSIBLE', 'Visible and unlocked layers', 'jump in keyframe of all layers', 2), - )) - #(key, label, descr, id[, icon]) - - def execute(self, context): - if not context.object.data.layers.active: - self.report({'ERROR'}, 'No active layer on current GPencil object') - return {"CANCELLED"} - - layer = [] - if self.target == 'ACTIVE': - gpl = [l for l in context.object.data.layers if l.select and not l.hide] - if not context.object.data.layers.active in gpl: - gpl.append(context.object.data.layers.active) - - elif self.target == 'VISIBLE': - gpl = [l for l in context.object.data.layers if not l.hide] - - elif self.target == 'ACCESSIBLE': - gpl = [l for l in context.object.data.layers if not l.hide and not l.lock] - - - current = context.scene.frame_current - p = n = None - - mins = [] - maxs = [] - for l in gpl: - for f in l.frames: - if f.frame_number < current: - p = f.frame_number - if f.frame_number > current: - n = f.frame_number - break - mins.append(p) - maxs.append(n) - p = n = None - - mins = [i for i in mins if i is not None] - maxs = [i for i in maxs if i is not None] - - if mins: - p = max(mins) - if maxs: - n = min(maxs) - - if self.next and n is not None: - context.scene.frame_set(n) - elif not self.next and p is not None: - context.scene.frame_set(p) - else: - self.report({'INFO'}, 'No keyframe in this direction') - return {"CANCELLED"} - - return {"FINISHED"} - - class GPTB_OT_rename_data_from_obj(bpy.types.Operator): bl_idname = "gp.rename_data_from_obj" bl_label = "Rename GP from object" @@ -528,7 +452,6 @@ class GPTB_OT_overlay_presets(bpy.types.Operator): classes = ( GPTB_OT_copy_text, GPTB_OT_flipx_view, -GPTB_OT_jump_gp_keyframe, GPTB_OT_rename_data_from_obj, GPTB_OT_draw_cam, GPTB_OT_set_view_as_cam, diff --git a/OP_keyframe_jump.py b/OP_keyframe_jump.py new file mode 100644 index 0000000..e5ff724 --- /dev/null +++ b/OP_keyframe_jump.py @@ -0,0 +1,177 @@ +import bpy +from .utils import get_addon_prefs + +class GPTB_OT_jump_gp_keyframe(bpy.types.Operator): + bl_idname = "screen.gp_keyframe_jump" + bl_label = "Jump to GPencil keyframe" + bl_description = "Jump to prev/next keyframe on active and selected layers of active grease pencil object" + bl_options = {"REGISTER"} + + @classmethod + def poll(cls, context): + return context.object and context.object.type == 'GPENCIL' + + next : bpy.props.BoolProperty( + name="Next GP keyframe", description="Go to next active GP keyframe", default=True) + + target : bpy.props.EnumProperty( + name="Target layer", description="Choose wich layer to evaluate for keyframe change", default='ACTIVE',# options={'ANIMATABLE'}, update=None, get=None, set=None, + items=( + ('ACTIVE', 'Active and selected', 'jump in keyframes of active and other selected layers ', 0), + ('VISIBLE', 'Visibles layers', 'jump in keyframes of visibles layers', 1), + ('ACCESSIBLE', 'Visible and unlocked layers', 'jump in keyframe of all layers', 2), + )) + #(key, label, descr, id[, icon]) + + def execute(self, context): + if not context.object.data.layers.active: + self.report({'ERROR'}, 'No active layer on current GPencil object') + return {"CANCELLED"} + + if self.target == 'ACTIVE': + gpl = [l for l in context.object.data.layers if l.select and not l.hide] + if not context.object.data.layers.active in gpl: + gpl.append(context.object.data.layers.active) + + elif self.target == 'VISIBLE': + gpl = [l for l in context.object.data.layers if not l.hide] + + elif self.target == 'ACCESSIBLE': + gpl = [l for l in context.object.data.layers if not l.hide and not l.lock] + + + current = context.scene.frame_current + p = n = None + + mins = [] + maxs = [] + for l in gpl: + for f in l.frames: + if f.frame_number < current: + p = f.frame_number + if f.frame_number > current: + n = f.frame_number + break + mins.append(p) + maxs.append(n) + p = n = None + + mins = [i for i in mins if i is not None] + maxs = [i for i in maxs if i is not None] + + if mins: + p = max(mins) + if maxs: + n = min(maxs) + + if self.next and n is not None: + context.scene.frame_set(n) + elif not self.next and p is not None: + context.scene.frame_set(p) + else: + self.report({'INFO'}, 'No keyframe in this direction') + return {"CANCELLED"} + + return {"FINISHED"} + + +class KFJ_OT_rebinder(bpy.types.Operator): + bl_idname = "prefs.shortcut_rebinder" + bl_label = "rebind keyframe jump shortcut" + bl_options = {'REGISTER', 'INTERNAL'} + + s_keycode: bpy.props.StringProperty() + s_ctrl: bpy.props.StringProperty() + s_shift: bpy.props.StringProperty() + s_alt: bpy.props.StringProperty() + + + def invoke(self, context, event): + self.prefs = get_addon_prefs() + + self.init_value = getattr(self.prefs, self.s_keycode) + setattr(self.prefs, self.s_keycode, '') + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + + def modal(self, context, event): + exclude_keys = {'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE', + 'TIMER_REPORT', 'ESC', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'} + exclude_in = ('SHIFT', 'CTRL', 'ALT') + + if event.type == 'ESC': + setattr(self.prefs, self.s_keycode, self.init_value) + return {'CANCELLED'} + + if event.type not in exclude_keys and not any(x in event.type for x in exclude_in): + print('key:', event.type, 'value:', event.value) + if event.value == 'PRESS': + self.report({'INFO'}, event.type) + + setattr(self.prefs, self.s_shift, event.shift) + setattr(self.prefs, self.s_alt, event.alt) + setattr(self.prefs, self.s_ctrl, event.ctrl) + setattr(self.prefs, self.s_keycode, event.type) + + # -# maybe add an autorebind trigger at keycode propertie level + # -# now dependent of register function within the scope of the file... + + # auto_rebind() + unregister_keymaps() + register_keymaps() + + context.area.tag_redraw()# seems to do nothing, have to move the mouse to update + return {'FINISHED'} + + return {"RUNNING_MODAL"} + + +""" class KFJ_OT_rebind(bpy.types.Operator): + bl_idname = "prefs.rebind_kfj_shortcut" + bl_label = "rebind keyframe jump shortcut" + bl_options = {'REGISTER', 'INTERNAL'} + + def execute(self, context): + unregister_keymaps() + register_keymaps() + 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") + + kmi = km.keymap_items.new('screen.gp_keyframe_jump', type=pref.kfj_next_keycode, value="PRESS", alt=pref.kfj_next_alt, ctrl=pref.kfj_next_ctrl, shift=pref.kfj_next_shift, any=False) + kmi.properties.next = True + addon_keymaps.append((km, kmi)) + kmi = km.keymap_items.new('screen.gp_keyframe_jump', type=pref.kfj_prev_keycode, value="PRESS", alt=pref.kfj_prev_alt, ctrl=pref.kfj_prev_ctrl, shift=pref.kfj_prev_shift, any=False) + kmi.properties.next = False + addon_keymaps.append((km, kmi)) + +def unregister_keymaps(): + # print('UNBIND CANVAS ROTATE KEYMAPS')#Dbg + for km, kmi in addon_keymaps: + km.keymap_items.remove(kmi) + addon_keymaps.clear() + # del addon_keymaps[:] + + +classes = ( +KFJ_OT_rebinder, +GPTB_OT_jump_gp_keyframe, +) + +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/README.md b/README.md index c4c3b24..21c17ce 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,12 @@ Set path to the palette folder (there is a json palette IO but you an also put a Note about palette : For now the importer is not working with linked palette as it's not easy for animator (there are properties of the material you cannot access and the link grey-out fade the real color in UIlist preview) +### Passive action + +Add an "on save" Handler that trigger relative remap of all path. + +### function + - Mirror flip : If in cam view flip the camera X scale value (you can see and draw mnirrored to see problems) @@ -43,9 +49,10 @@ Note about palette : For now the importer is not working with linked palette as - can auto launch and/or auto open folder at finished (option in addon preferences) -- jump to GP keyframe operator +- Jump to GP keyframe : - - you need to set two keymap shortcut in _windows_ or _screen(global)_ with indentifier `screen.gp_keyframe_jump` + - Choose key to Auto bind in addon prefs (since 0.9.3). + > Manual setup: Add two keymap shortcut in _windows_ or _screen(global)_ with indentifier `screen.gp_keyframe_jump`, one should have `next` toggled off to jump back - Rotate canvas with a clic + modifier combo (default is `ctrl + alt + MID-mouse`), can be change in addon preferences. @@ -96,10 +103,17 @@ Panel in sidebar : 3D view > sidebar 'N' > Gpencil ## Changelog: +0.9.3: + +- feat: keyframe jump keys are now auto-binded +- UI: added keyframe jump customisation in addon pref +- code: split keyframe jump in a separate file with his new key updater + 0.9.2: - doc: Correct download link (important, bugged the addon install) + update -- updater: remove updater temp file, reset minimum version +- code: added tracker url +- updater: remove updater temp file, reset minimum version, turn off verbose mode 0.9.1: diff --git a/README_FR.md b/README_FR.md index 72e3818..d17cbf1 100644 --- a/README_FR.md +++ b/README_FR.md @@ -27,7 +27,9 @@ Expose les options suivantes: **Edit line opacity** - Il est pratique de pouvoir cacher l'edit line pour avoir un meilleur aperçu du rendu lors du sculpt par exemple. C'est une option lié au layer. Ce slider appelle une fonction pour affecter tout les layers de tout les objets au lieu de se cantonner au layer courant. +### Action passive +- Ajoute un handler qui déclenche un remmapage des chemins en relatif à chaque sauvegarde. ### Tools principaux d'anim: @@ -39,7 +41,7 @@ L'action cam (peut-être renommé en follow_cam ?) fonctionne sur le même princ **Box deform** (`Ctrl+T`) - Déformation 4 coins (Déjà dans _Grease pencil tools_ donc a potentiellement retirer pour éviter de possible confits/redondances, mais d'un autre côté la version intégrée peut être customisée et corriger d'éventuel souci sans attendre les updates de la version native...) -**GP keyframe jump** (raccourci a ajouter manuellement, tant qu'on a pas une keymap anim un minimum standard) - Essentiel ! Permet d'ajouter une ou plusieurs paire de raccourcis pour aller de keyframe en keyframe (filtrage du saut personnalisable). Le raccourci doit appeler l'operateur `screen.gp_keyframe_jump`, (en ajouter un second avec le `next` décoché pour faire un saut arrière) +**GP keyframe jump** (auto bind et personnalisable dans les addon prefs depuis 0.9.3) - Essentiel ! Permet d'ajouter une ou plusieurs paire de raccourcis pour aller de keyframe en keyframe (filtrage du saut personnalisable). Lance l'operateur `screen.gp_keyframe_jump`, (si ajout manuel, rajouter une seconde keymap avec la propriété `next` décoché pour faire un saut arrière) **Breakdown en mode objet** (`Shift+E`) - Breakdown en pourcentage entre les deux keyframes (pose une clé si l’auto-key est actif et utilise le keying set si actif). Même comportement et raccourci que le breakdown de bones en pose mode (juste qu'il n'existait bizzarement pas en objet) diff --git a/__init__.py b/__init__.py index 0323be9..dbcf54b 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": (0, 9, 2), +"version": (0, 9, 3), "blender": (2, 91, 0), "location": "sidebar (N menu) > Gpencil > Toolbox / Gpencil properties", "warning": "", @@ -39,6 +39,7 @@ from . import OP_canvas_rotate from . import OP_playblast_bg from . import OP_playblast from . import OP_helpers +from . import OP_keyframe_jump from . import OP_box_deform from . import OP_cursor_snap_canvas from . import OP_palettes @@ -239,6 +240,52 @@ class GPTB_prefs(bpy.types.AddonPreferences): max=10000 ) + ## KF jumper + kfj_use_shortcut: BoolProperty( + name = "Use Keyframe Jump Shortcut", + description = "Auto bind shotcut for keyframe jump (else you can bien manually using 'screen.gp_keyframe_jump' id_name)", + default = True) + + kfj_prev_keycode : StringProperty( + name="Jump Prev Shortcut", + description="Shortcut to trigger previous keyframe jump", + default="F5") + + kfj_prev_shift: BoolProperty( + name = "Shift", + description = "add shift", + default = False) + + kfj_prev_alt: BoolProperty( + name = "Alt", + description = "add alt", + default = False) + + kfj_prev_ctrl: BoolProperty( + name = "combine with ctrl", + description = "add ctrl", + default = False) + + kfj_next_keycode : StringProperty( + name="Jump Next Shortcut", + description="Shortcut to trigger keyframe jump", + default="F6") + + kfj_next_shift: BoolProperty( + name = "Shift", + description = "add shift", + default = False) + + kfj_next_alt: BoolProperty( + name = "Alt", + description = "add alt", + default = False) + + kfj_next_ctrl: BoolProperty( + name = "combine with ctrl", + description = "add ctrl", + default = False) + ## Temp cutter # temp_cutter_use_shortcut: BoolProperty( # name = "Use temp cutter Shortcut", @@ -285,6 +332,41 @@ class GPTB_prefs(bpy.types.AddonPreferences): box.prop(self, 'playblast_auto_play') box.prop(self, 'playblast_auto_open_folder') + # box.separator()## Keyframe jumper + box = layout.box() + box.label(text='Keyframe Jump option:') + + box.prop(self, "kfj_use_shortcut", text='Bind shortcuts') + if self.kfj_use_shortcut: + prompt = '[TYPE SHORTCUT TO USE (can be with modifiers)]' + if self.kfj_prev_keycode: + mods = '+'.join([m for m, b in [('Ctrl', self.kfj_prev_ctrl), ('Shift', self.kfj_prev_shift), ('Alt', self.kfj_prev_alt)] if b]) + text = f'{mods}+{self.kfj_prev_keycode}' if mods else self.kfj_prev_keycode + text = f'Jump Keyframe Prev: {text} (Click to change)' + else: + text = prompt + ops = box.operator('prefs.shortcut_rebinder', text=text, icon='FILE_REFRESH') + ops.s_keycode = 'kfj_prev_keycode' + ops.s_ctrl = 'kfj_prev_ctrl' + ops.s_shift = 'kfj_prev_shift' + ops.s_alt = 'kfj_prev_alt' + + if self.kfj_next_keycode: + mods = '+'.join([m for m, b in [('Ctrl', self.kfj_next_ctrl), ('Shift', self.kfj_next_shift), ('Alt', self.kfj_next_alt)] if b]) + text = f'{mods}+{self.kfj_next_keycode}' if mods else self.kfj_next_keycode + text = f'Jump Keyframe Next: {text} (Click to change)' + else: + text = prompt + ops = box.operator('prefs.shortcut_rebinder', text=text, icon='FILE_REFRESH') + ops.s_keycode = 'kfj_next_keycode' + ops.s_ctrl = 'kfj_next_ctrl' + ops.s_shift = 'kfj_next_shift' + ops.s_alt = 'kfj_next_alt' + + else: + box.label(text="No Jump hotkey auto set. Following operators needs to be set manually", icon="ERROR") + box.label(text="screen.gp_keyframe_jump - preferably in 'screen' category to jump from any editor") + # box.separator()## Canvas box = layout.box() box.label(text='Canvas rotate options:') @@ -386,6 +468,7 @@ def register(): bpy.utils.register_class(cls) OP_box_deform.register() OP_helpers.register() + OP_keyframe_jump.register() OP_file_checker.register() OP_breakdowner.register() OP_temp_cutter.register() @@ -418,6 +501,7 @@ def unregister(): OP_palettes.unregister() OP_file_checker.unregister() OP_helpers.unregister() + OP_keyframe_jump.unregister() OP_breakdowner.unregister() OP_temp_cutter.unregister() GP_colorize.unregister()## GP_guided_colorize.