GPencil Tools: code cleanup
[blender-addons.git] / greasepencil_tools / timeline_scrub.py
blobbd7edebe1c5816a3d116b91efb01760b63b42f96
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 '''Based on viewport_timeline_scrub standalone addon - Samuel Bernou'''
21 from .prefs import get_addon_prefs
23 import numpy as np
24 from time import time
25 import bpy
26 import gpu
27 import bgl
28 import blf
29 from gpu_extras.batch import batch_for_shader
31 from bpy.props import (BoolProperty,
32 StringProperty,
33 IntProperty,
34 FloatVectorProperty,
35 IntProperty,
36 PointerProperty,
37 EnumProperty)
40 def nearest(array, value):
41 '''
42 Get a numpy array and a target value
43 Return closest val found in array to passed value
44 '''
45 idx = (np.abs(array - value)).argmin()
46 return array[idx]
49 def draw_callback_px(self, context):
50 '''Draw callback use by modal to draw in viewport'''
51 if context.area != self.current_area:
52 return
54 # text
55 font_id = 0
57 shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') # initiate shader
58 bgl.glEnable(bgl.GL_BLEND)
59 bgl.glLineWidth(1)
61 # Draw HUD
62 if self.use_hud_time_line:
63 shader.bind()
64 shader.uniform_float("color", self.color_timeline)
65 self.batch_timeline.draw(shader)
67 # Display keyframes
68 if self.use_hud_keyframes:
69 if self.keyframe_aspect == 'LINE':
70 bgl.glLineWidth(3)
71 shader.bind()
72 shader.uniform_float("color", self.color_timeline)
73 self.batch_keyframes.draw(shader)
74 else:
75 bgl.glLineWidth(1)
76 shader.bind()
77 shader.uniform_float("color", self.color_timeline)
78 self.batch_keyframes.draw(shader)
80 # Show current frame line
81 bgl.glLineWidth(1)
82 if self.use_hud_playhead:
83 playhead = [(self.cursor_x, self.my + self.playhead_size/2),
84 (self.cursor_x, self.my - self.playhead_size/2)]
85 batch = batch_for_shader(shader, 'LINES', {"pos": playhead})
86 shader.bind()
87 shader.uniform_float("color", self.color_playhead)
88 batch.draw(shader)
90 # restore opengl defaults
91 bgl.glDisable(bgl.GL_BLEND)
93 # Display current frame text
94 blf.color(font_id, *self.color_text)
95 if self.use_hud_frame_current:
96 blf.position(font_id, self.mouse[0]+10, self.mouse[1]+10, 0)
97 blf.size(font_id, 30, self.dpi)
98 blf.draw(font_id, f'{self.new_frame:.0f}')
100 # Display frame offset text
101 if self.use_hud_frame_offset:
102 blf.position(font_id, self.mouse[0]+10,
103 self.mouse[1]+(40*self.ui_scale), 0)
104 blf.size(font_id, 16, self.dpi)
105 sign = '+' if self.offset > 0 else ''
106 blf.draw(font_id, f'{sign}{self.offset:.0f}')
109 class GPTS_OT_time_scrub(bpy.types.Operator):
110 bl_idname = "animation.time_scrub"
111 bl_label = "Time scrub"
112 bl_description = "Quick time scrubbing with a shortcut"
113 bl_options = {"REGISTER", "INTERNAL", "UNDO"}
115 @classmethod
116 def poll(cls, context):
117 return context.space_data.type in ('VIEW_3D', 'SEQUENCE_EDITOR', 'CLIP_EDITOR')
119 def invoke(self, context, event):
120 prefs = get_addon_prefs().ts
122 self.current_area = context.area
123 self.key = prefs.keycode
124 self.evaluate_gp_obj_key = prefs.evaluate_gp_obj_key
126 self.dpi = context.preferences.system.dpi
127 self.ui_scale = context.preferences.system.ui_scale
128 # hud prefs
129 self.color_timeline = prefs.color_timeline
130 self.color_playhead = prefs.color_playhead
131 self.color_text = prefs.color_playhead
132 self.use_hud_time_line = prefs.use_hud_time_line
133 self.use_hud_keyframes = prefs.use_hud_keyframes
134 self.keyframe_aspect = prefs.keyframe_aspect
135 self.use_hud_playhead = prefs.use_hud_playhead
136 self.use_hud_frame_current = prefs.use_hud_frame_current
137 self.use_hud_frame_offset = prefs.use_hud_frame_offset
139 self.playhead_size = prefs.playhead_size
140 self.lines_size = prefs.lines_size
142 self.px_step = prefs.pixel_step
143 self.snap_on = False
144 self.mouse = (event.mouse_region_x, event.mouse_region_y)
145 self.init_mouse_x = self.cursor_x = event.mouse_region_x
146 # self.init_mouse_y = event.mouse_region_y # only to display init frame text
147 self.init_frame = self.new_frame = context.scene.frame_current
148 self.offset = 0
149 self.pos = []
151 # Snap control
152 self.snap_ctrl = not prefs.use_ctrl
153 self.snap_shift = not prefs.use_shift
154 self.snap_alt = not prefs.use_alt
155 self.snap_mouse_key = 'LEFTMOUSE' if self.key == 'RIGHTMOUSE' else 'RIGHTMOUSE'
157 ob = context.object
159 if context.space_data.type != 'VIEW_3D':
160 ob = None # do not consider any key
162 if ob: # condition to allow empty scrubing
163 if ob.type != 'GPENCIL' or self.evaluate_gp_obj_key:
164 # Get objet keyframe position
165 anim_data = ob.animation_data
166 action = None
168 if anim_data:
169 action = anim_data.action
170 if action:
171 for fcu in action.fcurves:
172 for kf in fcu.keyframe_points:
173 if kf.co.x not in self.pos:
174 self.pos.append(kf.co.x)
176 if ob.type == 'GPENCIL':
177 # Get GP frame position
178 gpl = ob.data.layers
179 layer = gpl.active
180 if layer:
181 for frame in layer.frames:
182 if frame.frame_number not in self.pos:
183 self.pos.append(frame.frame_number)
185 # Add start and end to snap on
186 if context.scene.use_preview_range:
187 play_bounds = [context.scene.frame_preview_start,
188 context.scene.frame_preview_end]
189 else:
190 play_bounds = [context.scene.frame_start, context.scene.frame_end]
192 # Also snap on play bounds (sliced off for keyframe display)
193 self.pos += play_bounds
194 self.pos = np.asarray(self.pos)
196 # Disable Onion skin
197 self.active_space_data = context.space_data
198 self.onion_skin = None
199 if context.space_data.type == 'VIEW_3D': # and 'GPENCIL' in context.mode
200 self.onion_skin = self.active_space_data.overlay.use_gpencil_onion_skin
201 self.active_space_data.overlay.use_gpencil_onion_skin = False
203 self.hud = prefs.use_hud
204 if not self.hud:
205 context.window_manager.modal_handler_add(self)
206 return {'RUNNING_MODAL'}
208 # - HUD params
209 width = context.area.width
210 right = int((width - self.init_mouse_x) / self.px_step)
211 left = int(self.init_mouse_x / self.px_step)
213 hud_pos_x = []
214 for i in range(1, left):
215 hud_pos_x.append(self.init_mouse_x - i*self.px_step)
216 for i in range(1, right):
217 hud_pos_x.append(self.init_mouse_x + i*self.px_step)
219 # - list of double coords
221 init_height = 60
222 frame_height = self.lines_size
223 key_height = 14
224 bound_h = key_height + 19
225 bound_bracket_l = self.px_step/2
227 self.my = my = event.mouse_region_y
229 self.hud_lines = []
231 # frame marks
232 for x in hud_pos_x:
233 self.hud_lines.append((x, my - (frame_height/2)))
234 self.hud_lines.append((x, my + (frame_height/2)))
236 # init frame mark
237 self.hud_lines += [(self.init_mouse_x, my - (init_height/2)),
238 (self.init_mouse_x, my + (init_height/2))]
240 # Add start/end boundary bracket to HUD
242 start_x = self.init_mouse_x + \
243 (play_bounds[0] - self.init_frame) * self.px_step
244 end_x = self.init_mouse_x + \
245 (play_bounds[1] - self.init_frame) * self.px_step
247 # start
248 up = (start_x, my - (bound_h/2))
249 dn = (start_x, my + (bound_h/2))
250 self.hud_lines.append(up)
251 self.hud_lines.append(dn)
253 self.hud_lines.append(up)
254 self.hud_lines.append((up[0] + bound_bracket_l, up[1]))
255 self.hud_lines.append(dn)
256 self.hud_lines.append((dn[0] + bound_bracket_l, dn[1]))
258 # end
259 up = (end_x, my - (bound_h/2))
260 dn = (end_x, my + (bound_h/2))
261 self.hud_lines.append(up)
262 self.hud_lines.append(dn)
264 self.hud_lines.append(up)
265 self.hud_lines.append((up[0] - bound_bracket_l, up[1]))
266 self.hud_lines.append(dn)
267 self.hud_lines.append((dn[0] - bound_bracket_l, dn[1]))
269 # Horizontal line
270 self.hud_lines += [(0, my), (width, my)]
272 # Prepare batchs to draw static parts
273 shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') # initiate shader
274 self.batch_timeline = batch_for_shader(
275 shader, 'LINES', {"pos": self.hud_lines})
277 # keyframe display
278 if self.keyframe_aspect == 'LINE':
279 key_lines = []
280 # Slice off position of start/end frame added last (in list for snapping)
281 for i in self.pos[:-2]:
282 key_lines.append(
283 (self.init_mouse_x + ((i-self.init_frame) * self.px_step), my - (key_height/2)))
284 key_lines.append(
285 (self.init_mouse_x + ((i-self.init_frame)*self.px_step), my + (key_height/2)))
287 self.batch_keyframes = batch_for_shader(
288 shader, 'LINES', {"pos": key_lines})
290 else:
291 # diamond and square
292 # keysize5 for square, 4 or 6 for diamond
293 keysize = 6 if self.keyframe_aspect == 'DIAMOND' else 5
294 upper = 0
296 shaped_key = []
297 indices = []
298 idx_offset = 0
299 for i in self.pos[:-2]:
300 center = self.init_mouse_x + ((i-self.init_frame)*self.px_step)
301 if self.keyframe_aspect == 'DIAMOND':
302 # +1 on x is to correct pixel alignement
303 shaped_key += [(center-keysize, my+upper),
304 (center+1, my+keysize+upper),
305 (center+keysize, my+upper),
306 (center+1, my-keysize+upper)]
308 elif self.keyframe_aspect == 'SQUARE':
309 shaped_key += [(center-keysize+1, my-keysize+upper),
310 (center-keysize+1, my+keysize+upper),
311 (center+keysize, my+keysize+upper),
312 (center+keysize, my-keysize+upper)]
314 indices += [(0+idx_offset, 1+idx_offset, 2+idx_offset),
315 (0+idx_offset, 2+idx_offset, 3+idx_offset)]
316 idx_offset += 4
318 self.batch_keyframes = batch_for_shader(
319 shader, 'TRIS', {"pos": shaped_key}, indices=indices)
321 args = (self, context)
322 self.viewtype = None
323 self.spacetype = 'WINDOW' # is PREVIEW for VSE, needed for handler remove
325 if context.space_data.type == 'VIEW_3D':
326 self.viewtype = bpy.types.SpaceView3D
327 self._handle = bpy.types.SpaceView3D.draw_handler_add(
328 draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
330 elif context.space_data.type == 'SEQUENCE_EDITOR':
331 self.viewtype = bpy.types.SpaceSequenceEditor
332 self.spacetype = 'PREVIEW'
333 self._handle = bpy.types.SpaceSequenceEditor.draw_handler_add(
334 draw_callback_px, args, 'PREVIEW', 'POST_PIXEL')
336 elif context.space_data.type == 'CLIP_EDITOR':
337 self.viewtype = bpy.types.SpaceClipEditor
338 self._handle = bpy.types.SpaceClipEditor.draw_handler_add(
339 draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
341 context.window_manager.modal_handler_add(self)
342 return {'RUNNING_MODAL'}
344 def _exit_modal(self, context):
345 if self.onion_skin is not None:
346 self.active_space_data.overlay.use_gpencil_onion_skin = self.onion_skin
348 if self.hud and self.viewtype:
349 self.viewtype.draw_handler_remove(self._handle, self.spacetype)
350 context.area.tag_redraw()
352 def modal(self, context, event):
354 if event.type == 'MOUSEMOVE':
355 # - calculate frame offset from pixel offset
356 # - get mouse.x and add it to initial frame num
357 self.mouse = (event.mouse_region_x, event.mouse_region_y)
359 px_offset = (event.mouse_region_x - self.init_mouse_x)
360 self.offset = int(px_offset / self.px_step)
361 self.new_frame = self.init_frame + self.offset
363 mod_snap = False
364 if self.snap_ctrl and event.ctrl:
365 mod_snap = True
366 if self.snap_shift and event.shift:
367 mod_snap = True
368 if self.snap_alt and event.alt:
369 mod_snap = True
371 if self.snap_on or mod_snap:
372 self.new_frame = nearest(self.pos, self.new_frame)
374 context.scene.frame_current = self.new_frame
376 # - recalculate offset to snap cursor to frame
377 self.offset = self.new_frame - self.init_frame
379 # - calculate cursor pixel position from frame offset
380 self.cursor_x = self.init_mouse_x + (self.offset * self.px_step)
382 if event.type == 'ESC':
383 # frame_set(self.init_frame) ?
384 context.scene.frame_current = self.init_frame
385 self._exit_modal(context)
386 return {'CANCELLED'}
388 # Snap if pressing NOT used mouse key (right or mid)
389 if event.type == self.snap_mouse_key:
390 if event.value == "PRESS":
391 self.snap_on = True
392 else:
393 self.snap_on = False
395 if event.type == self.key and event.value == 'RELEASE':
396 self._exit_modal(context)
397 return {'FINISHED'}
399 return {"RUNNING_MODAL"}
402 # --- addon prefs
404 def auto_rebind(self, context):
405 unregister_keymaps()
406 register_keymaps()
409 class GPTS_OT_set_scrub_keymap(bpy.types.Operator):
410 bl_idname = "animation.ts_set_keymap"
411 bl_label = "Change keymap"
412 bl_description = "Quick time scrubbing with a shortcut"
413 bl_options = {"REGISTER", "INTERNAL"}
415 def invoke(self, context, event):
416 self.prefs = get_addon_prefs().ts
417 self.ctrl = False
418 self.shift = False
419 self.alt = False
421 self.init_value = self.prefs.keycode
422 self.prefs.keycode = ''
423 context.window_manager.modal_handler_add(self)
424 return {'RUNNING_MODAL'}
426 def modal(self, context, event):
427 exclude_keys = {'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE',
428 'TIMER_REPORT', 'ESC', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}
429 exclude_in = ('SHIFT', 'CTRL', 'ALT')
430 if event.type == 'ESC':
431 self.prefs.keycode = self.init_value
432 return {'CANCELLED'}
434 self.ctrl = event.ctrl
435 self.shift = event.shift
436 self.alt = event.alt
438 if event.type not in exclude_keys and not any(x in event.type for x in exclude_in):
439 print('key:', event.type, 'value:', event.value)
440 if event.value == 'PRESS':
441 self.report({'INFO'}, event.type)
442 # set the chosen key
443 self.prefs.keycode = event.type
444 # Following condition to avoid unnecessary rebind update
445 if self.prefs.use_shift != event.shift:
446 self.prefs.use_shift = event.shift
448 if self.prefs.use_alt != event.alt:
449 self.prefs.use_alt = event.alt
451 # -# Trigger rebind update with last
452 self.prefs.use_ctrl = event.ctrl
454 return {'FINISHED'}
456 return {"RUNNING_MODAL"}
459 class GPTS_timeline_settings(bpy.types.PropertyGroup):
461 keycode: StringProperty(
462 name="Shortcut",
463 description="Shortcut to trigger the scrub in viewport during press",
464 default="MIDDLEMOUSE")
466 use_in_timeline_editor: BoolProperty(
467 name="Shortcut in timeline editors",
468 description="Add the same shortcut to scrub in timeline editor windows",
469 default=True,
470 update=auto_rebind)
472 use_shift: BoolProperty(
473 name="Combine With Shift",
474 description="Add shift",
475 default=False,
476 update=auto_rebind)
478 use_alt: BoolProperty(
479 name="Combine With Alt",
480 description="Add alt",
481 default=True,
482 update=auto_rebind)
484 use_ctrl: BoolProperty(
485 name="Combine With Ctrl",
486 description="Add ctrl",
487 default=False,
488 update=auto_rebind)
490 evaluate_gp_obj_key: BoolProperty(
491 name='Use Gpencil Object Keyframes',
492 description="Also snap on greasepencil object keyframe (else only active layer frames)",
493 default=True)
495 pixel_step: IntProperty(
496 name="Frame Interval On Screen",
497 description="Pixel steps on screen that represent a frame intervals",
498 default=10,
499 min=1,
500 max=500,
501 soft_min=2,
502 soft_max=100,
503 step=1,
504 subtype='PIXEL')
506 use_hud: BoolProperty(
507 name='Display Timeline Overlay',
508 description="Display overlays with timeline information when scrubbing time in viewport",
509 default=True)
511 use_hud_time_line: BoolProperty(
512 name='Timeline',
513 description="Display a static marks overlay to represent timeline when scrubbing",
514 default=True)
516 use_hud_keyframes: BoolProperty(
517 name='Keyframes',
518 description="Display shapes overlay to show keyframe position when scrubbing",
519 default=True)
521 use_hud_playhead: BoolProperty(
522 name='Playhead',
523 description="Display the playhead as a vertical line to show position in time",
524 default=True)
526 use_hud_frame_current: BoolProperty(
527 name='Text Frame Current',
528 description="Display the current frame as text above mouse cursor",
529 default=True)
531 use_hud_frame_offset: BoolProperty(
532 name='Text Frame Offset',
533 description="Display frame offset from initial position as text above mouse cursor",
534 default=True)
536 color_timeline: FloatVectorProperty(
537 name="Timeline Color",
538 subtype='COLOR_GAMMA',
539 size=4,
540 default=(0.5, 0.5, 0.5, 0.6),
541 min=0.0, max=1.0,
542 description="Color of the temporary timeline")
544 color_playhead: FloatVectorProperty(
545 name="Cusor Color",
546 subtype='COLOR_GAMMA',
547 size=4,
548 default=(0.01, 0.64, 1.0, 0.8),
549 min=0.0, max=1.0,
550 description="Color of the temporary line cursor and text")
552 # sizes
553 playhead_size: IntProperty(
554 name="Playhead Size",
555 description="Playhead height in pixels",
556 default=100,
557 min=2,
558 max=10000,
559 soft_min=10,
560 soft_max=5000,
561 step=1,
562 subtype='PIXEL')
564 lines_size: IntProperty(
565 name="Frame Lines Size",
566 description="Frame lines height in pixels",
567 default=10,
568 min=1,
569 max=10000,
570 soft_min=5,
571 soft_max=40,
572 step=1,
573 subtype='PIXEL')
575 keyframe_aspect: EnumProperty(
576 name="Keyframe Display",
577 description="Customize aspect of the keyframes",
578 default='LINE',
579 items=(
580 ('LINE', 'Line',
581 'Keyframe displayed as thick lines', 'SNAP_INCREMENT', 0),
582 ('SQUARE', 'Square',
583 'Keyframe displayed as squares', 'HANDLETYPE_VECTOR_VEC', 1),
584 ('DIAMOND', 'Diamond',
585 'Keyframe displayed as diamonds', 'HANDLETYPE_FREE_VEC', 2),
589 def draw_ts_pref(prefs, layout):
590 # - General settings
591 layout.label(text='Timeline Scrub:')
592 layout.prop(prefs, 'evaluate_gp_obj_key')
593 layout.prop(prefs, 'pixel_step')
595 # -/ Keymap -
596 box = layout.box()
597 box.label(text='Keymap:')
598 box.operator('animation.ts_set_keymap',
599 text='Click here to change shortcut')
601 if prefs.keycode:
602 row = box.row(align=True)
603 row.prop(prefs, 'use_ctrl', text='Ctrl')
604 row.prop(prefs, 'use_alt', text='Alt')
605 row.prop(prefs, 'use_shift', text='Shift')
606 # -/Cosmetic-
607 icon = None
608 if prefs.keycode == 'LEFTMOUSE':
609 icon = 'MOUSE_LMB'
610 elif prefs.keycode == 'MIDDLEMOUSE':
611 icon = 'MOUSE_MMB'
612 elif prefs.keycode == 'RIGHTMOUSE':
613 icon = 'MOUSE_RMB'
614 if icon:
615 row.label(text=f'{prefs.keycode}', icon=icon)
616 # -Cosmetic-/
617 else:
618 row.label(text=f'Key: {prefs.keycode}')
620 else:
621 box.label(text='[ NOW TYPE KEY OR CLICK TO USE, WITH MODIFIER ]')
623 snap_text = 'Snap to keyframes: '
624 snap_text += 'Left Mouse' if prefs.keycode == 'RIGHTMOUSE' else 'Right Mouse'
625 if not prefs.use_ctrl:
626 snap_text += ' or Ctrl'
627 if not prefs.use_shift:
628 snap_text += ' or Shift'
629 if not prefs.use_alt:
630 snap_text += ' or Alt'
631 box.label(text=snap_text, icon='SNAP_ON')
632 if prefs.keycode in ('LEFTMOUSE', 'RIGHTMOUSE', 'MIDDLEMOUSE') and not prefs.use_ctrl and not prefs.use_alt and not prefs.use_shift:
633 box.label(
634 text="Recommended to choose at least one modifier to combine with clicks (default: Ctrl+Alt)", icon="ERROR")
636 box.prop(prefs, 'use_in_timeline_editor',
637 text='Add same shortcut to scrub within timeline editors')
639 # - HUD/OSD
640 box = layout.box()
641 box.prop(prefs, 'use_hud')
643 col = box.column()
644 row = col.row()
645 row.prop(prefs, 'color_timeline')
646 row.prop(prefs, 'color_playhead', text='Cursor And Text Color')
647 col.label(text='Show:')
648 row = col.row()
649 row.prop(prefs, 'use_hud_time_line')
650 row.prop(prefs, 'lines_size')
651 row = col.row()
652 row.prop(prefs, 'use_hud_playhead')
653 row.prop(prefs, 'playhead_size')
654 row = col.row()
655 row.prop(prefs, 'use_hud_keyframes')
656 row.prop(prefs, 'keyframe_aspect', text='')
657 row = col.row()
658 row.prop(prefs, 'use_hud_frame_current')
659 row.prop(prefs, 'use_hud_frame_offset')
660 col.enabled = prefs.use_hud
663 # --- Keymap
665 addon_keymaps = []
667 def register_keymaps():
668 prefs = get_addon_prefs().ts
669 addon = bpy.context.window_manager.keyconfigs.addon
670 km = addon.keymaps.new(name="Grease Pencil",
671 space_type="EMPTY", region_type='WINDOW')
673 if not prefs.keycode:
674 print(r'/!\ Timeline scrub: no keycode entered for keymap')
675 return
676 kmi = km.keymap_items.new(
677 'animation.time_scrub',
678 type=prefs.keycode, value='PRESS',
679 alt=prefs.use_alt, ctrl=prefs.use_ctrl, shift=prefs.use_shift, any=False)
680 kmi.repeat = False
681 addon_keymaps.append((km, kmi))
683 # - Add keymap in timeline editors
684 if prefs.use_in_timeline_editor:
686 editor_l = [
687 ('Dopesheet', 'DOPESHEET_EDITOR', 'anim.change_frame'),
688 ('Graph Editor', 'GRAPH_EDITOR', 'graph.cursor_set'),
689 ("NLA Editor", "NLA_EDITOR", 'anim.change_frame'),
690 ("Sequencer", "SEQUENCE_EDITOR", 'anim.change_frame')
691 # ("Clip Graph Editor", "CLIP_EDITOR", 'clip.change_frame'),
694 for editor, space, operator in editor_l:
695 km = addon.keymaps.new(name=editor, space_type=space)
696 kmi = km.keymap_items.new(
697 operator, type=prefs.keycode, value='PRESS',
698 alt=prefs.use_alt, ctrl=prefs.use_ctrl, shift=prefs.use_shift)
699 addon_keymaps.append((km, kmi))
702 def unregister_keymaps():
703 for km, kmi in addon_keymaps:
704 km.keymap_items.remove(kmi)
705 addon_keymaps.clear()
707 # --- REGISTER ---
709 classes = (
710 GPTS_OT_time_scrub,
711 GPTS_OT_set_scrub_keymap,
714 def register():
715 for cls in classes:
716 bpy.utils.register_class(cls)
717 register_keymaps()
719 def unregister():
720 unregister_keymaps()
721 for cls in reversed(classes):
722 bpy.utils.unregister_class(cls)