import bpy from .utils import get_addon_prefs, is_locked, is_hidden from bpy.props import BoolProperty ,EnumProperty ,StringProperty 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 == 'GREASEPENCIL' next : BoolProperty( name="Next GP keyframe", description="Go to next active GP keyframe", default=True, options={'HIDDEN', 'SKIP_SAVE'}) target : EnumProperty( name="Target layer", description="Choose wich layer to evaluate for keyframe change", default='ACTIVE', options={'HIDDEN', 'SKIP_SAVE'}, 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), )) keyframe_type : EnumProperty( name="Keyframe Filter", description="Jump to choosen keyframe type, else use the UI jump filter", default='NONE', options={'HIDDEN', 'SKIP_SAVE'}, items=( ('NONE', 'Use UI Filter', '', 0), # 'KEYFRAME' ('ALL', 'All', '', 1), ('KEYFRAME', 'Keyframe', '', 'KEYTYPE_KEYFRAME_VEC', 2), ('BREAKDOWN', 'Breakdown', '', 'KEYTYPE_BREAKDOWN_VEC', 3), ('MOVING_HOLD', 'Moving Hold', '', 'KEYTYPE_MOVING_HOLD_VEC', 4), ('EXTREME', 'Extreme', '', 'KEYTYPE_EXTREME_VEC', 5), ('JITTER', 'Jitter', '', 'KEYTYPE_JITTER_VEC', 6), ('GENERATED', 'Generated', '', 'KEYTYPE_GENERATED_VEC', 7), )) 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 is_hidden(l)] 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 is_hidden(l)] elif self.target == 'ACCESSIBLE': gpl = [l for l in context.object.data.layers if not is_hidden(l) and not is_locked(l)] if self.keyframe_type != 'NONE': # use shortcut choice override kftype = self.keyframe_type else: kftype = context.scene.gptoolprops.keyframe_type current = context.scene.frame_current p = n = None mins = [] maxs = [] for l in gpl: for f in l.frames: # keyframe type filter if kftype != 'ALL' and f.keyframe_type != kftype: continue 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) ## Double the frame set to avoid refresh problem (had one in 2.91.2) if self.next and n is not None: context.scene.frame_set(n) context.scene.frame_current = n elif not self.next and p is not None: context.scene.frame_set(p) context.scene.frame_current = 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: StringProperty() s_ctrl: StringProperty() s_shift: StringProperty() s_alt: 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_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)) 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)) 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)