1 # SPDX-FileCopyrightText: 2021-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 '''Based on viewport_timeline_scrub standalone addon - Samuel Bernou'''
7 from .prefs
import get_addon_prefs
14 from gpu_extras
.batch
import batch_for_shader
16 from bpy
.props
import (BoolProperty
,
25 def nearest(array
, value
) -> int:
27 Get a numpy array and a target value
28 Return closest val found in array to passed value
30 idx
= (np
.abs(array
- value
)).argmin()
31 return int(array
[idx
])
34 def draw_callback_px(self
, context
):
35 '''Draw callback use by modal to draw in viewport'''
36 if context
.area
!= self
.current_area
:
42 shader
= gpu
.shader
.from_builtin('UNIFORM_COLOR') # initiate shader
43 gpu
.state
.blend_set('ALPHA')
44 gpu
.state
.line_width_set(1.0)
47 if self
.use_hud_time_line
:
49 shader
.uniform_float("color", self
.color_timeline
)
50 self
.batch_timeline
.draw(shader
)
53 if self
.use_hud_keyframes
and self
.batch_keyframes
:
54 if self
.keyframe_aspect
== 'LINE':
55 gpu
.state
.line_width_set(3.0)
57 shader
.uniform_float("color", self
.color_timeline
)
58 self
.batch_keyframes
.draw(shader
)
60 gpu
.state
.line_width_set(1.0)
62 shader
.uniform_float("color", self
.color_timeline
)
63 self
.batch_keyframes
.draw(shader
)
65 # Show current frame line
66 gpu
.state
.line_width_set(1.0)
67 if self
.use_hud_playhead
:
68 playhead
= [(self
.cursor_x
, self
.my
+ self
.playhead_size
/2),
69 (self
.cursor_x
, self
.my
- self
.playhead_size
/2)]
70 batch
= batch_for_shader(shader
, 'LINES', {"pos": playhead
})
72 shader
.uniform_float("color", self
.color_playhead
)
75 # restore opengl defaults
76 gpu
.state
.blend_set('NONE')
78 # Display current frame text
79 blf
.color(font_id
, *self
.color_text
)
80 if self
.use_hud_frame_current
:
81 blf
.position(font_id
, self
.mouse
[0]+10, self
.mouse
[1]+10, 0)
82 blf
.size(font_id
, 30 * (self
.dpi
/ 72.0))
83 blf
.draw(font_id
, f
'{self.new_frame:.0f}')
85 # Display frame offset text
86 if self
.use_hud_frame_offset
:
87 blf
.position(font_id
, self
.mouse
[0]+10,
88 self
.mouse
[1]+(40*self
.ui_scale
), 0)
89 blf
.size(font_id
, 16 * (self
.dpi
/ 72.0))
90 sign
= '+' if self
.offset
> 0 else ''
91 blf
.draw(font_id
, f
'{sign}{self.offset:.0f}')
94 class GPTS_OT_time_scrub(bpy
.types
.Operator
):
95 bl_idname
= "animation.time_scrub"
96 bl_label
= "Time scrub"
97 bl_description
= "Quick time scrubbing with a shortcut"
98 bl_options
= {"REGISTER", "INTERNAL", "UNDO"}
101 def poll(cls
, context
):
102 return context
.space_data
.type in ('VIEW_3D', 'SEQUENCE_EDITOR', 'CLIP_EDITOR')
104 def invoke(self
, context
, event
):
105 prefs
= get_addon_prefs().ts
107 self
.current_area
= context
.area
108 self
.key
= prefs
.keycode
109 self
.evaluate_gp_obj_key
= prefs
.evaluate_gp_obj_key
110 self
.always_snap
= prefs
.always_snap
111 self
.rolling_mode
= prefs
.rolling_mode
113 self
.dpi
= context
.preferences
.system
.dpi
114 self
.ui_scale
= context
.preferences
.system
.ui_scale
116 self
.color_timeline
= prefs
.color_timeline
117 self
.color_playhead
= prefs
.color_playhead
118 self
.color_text
= prefs
.color_playhead
119 self
.use_hud_time_line
= prefs
.use_hud_time_line
120 self
.use_hud_keyframes
= prefs
.use_hud_keyframes
121 self
.keyframe_aspect
= prefs
.keyframe_aspect
122 self
.use_hud_playhead
= prefs
.use_hud_playhead
123 self
.use_hud_frame_current
= prefs
.use_hud_frame_current
124 self
.use_hud_frame_offset
= prefs
.use_hud_frame_offset
126 self
.playhead_size
= prefs
.playhead_size
127 self
.lines_size
= prefs
.lines_size
129 self
.px_step
= prefs
.pixel_step
131 self
.mouse
= (event
.mouse_region_x
, event
.mouse_region_y
)
132 self
.init_mouse_x
= self
.cursor_x
= event
.mouse_region_x
134 # self.init_mouse_y = event.mouse_region_y # only to display init frame text
135 self
.cancel_frame
= self
.init_frame
= self
.new_frame
= context
.scene
.frame_current
136 self
.lock_range
= context
.scene
.lock_frame_selection_to_range
137 if context
.scene
.use_preview_range
:
138 self
.f_start
= context
.scene
.frame_preview_start
139 self
.f_end
= context
.scene
.frame_preview_end
141 self
.f_start
= context
.scene
.frame_start
142 self
.f_end
= context
.scene
.frame_end
148 self
.snap_ctrl
= not prefs
.use_ctrl
149 self
.snap_shift
= not prefs
.use_shift
150 self
.snap_alt
= not prefs
.use_alt
151 self
.snap_mouse_key
= 'LEFTMOUSE' if self
.key
== 'RIGHTMOUSE' else 'RIGHTMOUSE'
155 if context
.space_data
.type != 'VIEW_3D':
156 ob
= None # do not consider any key
158 if ob
: # condition to allow empty scrubing
159 if ob
.type != 'GPENCIL' or self
.evaluate_gp_obj_key
:
160 # Get object keyframe position
161 anim_data
= ob
.animation_data
165 action
= anim_data
.action
167 for fcu
in action
.fcurves
:
168 for kf
in fcu
.keyframe_points
:
169 if kf
.co
.x
not in self
.pos
:
170 self
.pos
.append(kf
.co
.x
)
172 if ob
.type == 'GPENCIL':
173 # Get GP frame position
177 for frame
in layer
.frames
:
178 if frame
.frame_number
not in self
.pos
:
179 self
.pos
.append(frame
.frame_number
)
181 if not ob
or not self
.pos
:
182 # Disable inverted behavior if no frame to snap
183 self
.always_snap
= False
184 if self
.rolling_mode
:
185 self
.report({'WARNING'}, 'No Keys to flip on')
188 if self
.rolling_mode
:
189 # sorted and casted to int list since it's going to work with indexes
190 self
.pos
= sorted([int(f
) for f
in self
.pos
])
191 # find and make current frame the "starting" frame (force snap)
192 active_pos
= [i
for i
, num
in enumerate(self
.pos
) if num
<= self
.init_frame
]
194 self
.init_index
= active_pos
[-1]
195 self
.init_frame
= self
.new_frame
= self
.pos
[self
.init_index
]
198 self
.init_frame
= self
.new_frame
= self
.pos
[0]
201 self
.index_limit
= len(self
.pos
) - 1
203 # Also snap on play bounds (sliced off for keyframe display)
204 self
.pos
+= [self
.f_start
, self
.f_end
]
207 self
.active_space_data
= context
.space_data
208 self
.onion_skin
= None
209 self
.multi_frame
= None
210 if context
.space_data
.type == 'VIEW_3D': # and 'GPENCIL' in context.mode
211 self
.onion_skin
= self
.active_space_data
.overlay
.use_gpencil_onion_skin
212 self
.active_space_data
.overlay
.use_gpencil_onion_skin
= False
214 if ob
and ob
.type == 'GPENCIL':
215 if ob
.data
.use_multiedit
:
216 self
.multi_frame
= ob
.data
.use_multiedit
217 ob
.data
.use_multiedit
= False
219 self
.hud
= prefs
.use_hud
221 ## Same as end settings when HUD is On
223 self
.pos
= [i
for i
in self
.pos
if self
.f_start
<= i
<= self
.f_end
]
224 self
.pos
= np
.asarray(self
.pos
)
225 if self
.rolling_mode
:
226 context
.scene
.frame_current
= self
.new_frame
227 context
.window_manager
.modal_handler_add(self
)
228 return {'RUNNING_MODAL'}
231 width
= context
.area
.width
232 right
= int((width
- self
.init_mouse_x
) / self
.px_step
)
233 left
= int(self
.init_mouse_x
/ self
.px_step
)
236 for i
in range(1, left
):
237 hud_pos_x
.append(self
.init_mouse_x
- i
*self
.px_step
)
238 for i
in range(1, right
):
239 hud_pos_x
.append(self
.init_mouse_x
+ i
*self
.px_step
)
241 # - list of double coords
244 frame_height
= self
.lines_size
246 bound_h
= key_height
+ 19
247 bound_bracket_l
= self
.px_step
/2
249 self
.my
= my
= event
.mouse_region_y
253 if not self
.rolling_mode
:
256 self
.hud_lines
.append((x
, my
- (frame_height
/2)))
257 self
.hud_lines
.append((x
, my
+ (frame_height
/2)))
260 self
.hud_lines
+= [(self
.init_mouse_x
, my
- (init_height
/2)),
261 (self
.init_mouse_x
, my
+ (init_height
/2))]
263 if not self
.rolling_mode
:
264 # Add start/end boundary bracket to HUD
265 start_x
= self
.init_mouse_x
+ \
266 (self
.f_start
- self
.init_frame
) * self
.px_step
267 end_x
= self
.init_mouse_x
+ \
268 (self
.f_end
- self
.init_frame
) * self
.px_step
271 up
= (start_x
, my
- (bound_h
/2))
272 dn
= (start_x
, my
+ (bound_h
/2))
273 self
.hud_lines
.append(up
)
274 self
.hud_lines
.append(dn
)
276 self
.hud_lines
.append(up
)
277 self
.hud_lines
.append((up
[0] + bound_bracket_l
, up
[1]))
278 self
.hud_lines
.append(dn
)
279 self
.hud_lines
.append((dn
[0] + bound_bracket_l
, dn
[1]))
282 up
= (end_x
, my
- (bound_h
/2))
283 dn
= (end_x
, my
+ (bound_h
/2))
284 self
.hud_lines
.append(up
)
285 self
.hud_lines
.append(dn
)
287 self
.hud_lines
.append(up
)
288 self
.hud_lines
.append((up
[0] - bound_bracket_l
, up
[1]))
289 self
.hud_lines
.append(dn
)
290 self
.hud_lines
.append((dn
[0] - bound_bracket_l
, dn
[1]))
293 self
.hud_lines
+= [(0, my
), (width
, my
)]
295 # Prepare batchs to draw static parts
296 shader
= gpu
.shader
.from_builtin('UNIFORM_COLOR') # initiate shader
297 self
.batch_timeline
= batch_for_shader(
298 shader
, 'LINES', {"pos": self
.hud_lines
})
300 if self
.rolling_mode
:
301 current_id
= self
.pos
.index(self
.new_frame
)
302 # Add init_frame to "cancel" it in later UI code
303 ui_key_pos
= [i
- current_id
+ self
.init_frame
for i
, _f
in enumerate(self
.pos
[:-2])]
305 ui_key_pos
= self
.pos
[:-2]
307 self
.batch_keyframes
= None # init if there are no keyframe to draw
309 if self
.keyframe_aspect
== 'LINE':
311 # Slice off position of start/end frame added last (in list for snapping)
314 (self
.init_mouse_x
+ ((i
-self
.init_frame
) * self
.px_step
), my
- (key_height
/2)))
316 (self
.init_mouse_x
+ ((i
-self
.init_frame
) * self
.px_step
), my
+ (key_height
/2)))
318 self
.batch_keyframes
= batch_for_shader(
319 shader
, 'LINES', {"pos": key_lines
})
323 # keysize5 for square, 4 or 6 for diamond
324 keysize
= 6 if self
.keyframe_aspect
== 'DIAMOND' else 5
331 center
= self
.init_mouse_x
+ ((i
-self
.init_frame
)*self
.px_step
)
332 if self
.keyframe_aspect
== 'DIAMOND':
333 # +1 on x is to correct pixel alignment
334 shaped_key
+= [(center
-keysize
, my
+upper
),
335 (center
+1, my
+keysize
+upper
),
336 (center
+keysize
, my
+upper
),
337 (center
+1, my
-keysize
+upper
)]
339 elif self
.keyframe_aspect
== 'SQUARE':
340 shaped_key
+= [(center
-keysize
+1, my
-keysize
+upper
),
341 (center
-keysize
+1, my
+keysize
+upper
),
342 (center
+keysize
, my
+keysize
+upper
),
343 (center
+keysize
, my
-keysize
+upper
)]
345 indices
+= [(0+idx_offset
, 1+idx_offset
, 2+idx_offset
),
346 (0+idx_offset
, 2+idx_offset
, 3+idx_offset
)]
349 self
.batch_keyframes
= batch_for_shader(
350 shader
, 'TRIS', {"pos": shaped_key
}, indices
=indices
)
352 # Trim snapping list of frame outside of frame range if range lock activated
353 # (after drawing batch so those are still showed)
355 self
.pos
= [i
for i
in self
.pos
if self
.f_start
<= i
<= self
.f_end
]
357 # convert frame list to array for numpy snap utility
358 self
.pos
= np
.asarray(self
.pos
)
360 if self
.rolling_mode
:
361 context
.scene
.frame_current
= self
.new_frame
363 args
= (self
, context
)
365 self
.spacetype
= 'WINDOW' # is PREVIEW for VSE, needed for handler remove
367 if context
.space_data
.type == 'VIEW_3D':
368 self
.viewtype
= bpy
.types
.SpaceView3D
369 self
._handle
= bpy
.types
.SpaceView3D
.draw_handler_add(
370 draw_callback_px
, args
, 'WINDOW', 'POST_PIXEL')
372 elif context
.space_data
.type == 'SEQUENCE_EDITOR':
373 self
.viewtype
= bpy
.types
.SpaceSequenceEditor
374 self
.spacetype
= 'PREVIEW'
375 self
._handle
= bpy
.types
.SpaceSequenceEditor
.draw_handler_add(
376 draw_callback_px
, args
, 'PREVIEW', 'POST_PIXEL')
378 elif context
.space_data
.type == 'CLIP_EDITOR':
379 self
.viewtype
= bpy
.types
.SpaceClipEditor
380 self
._handle
= bpy
.types
.SpaceClipEditor
.draw_handler_add(
381 draw_callback_px
, args
, 'WINDOW', 'POST_PIXEL')
383 context
.window_manager
.modal_handler_add(self
)
384 return {'RUNNING_MODAL'}
386 def _exit_modal(self
, context
):
387 if self
.onion_skin
is not None:
388 self
.active_space_data
.overlay
.use_gpencil_onion_skin
= self
.onion_skin
390 context
.object.data
.use_multiedit
= self
.multi_frame
391 if self
.hud
and self
.viewtype
:
392 self
.viewtype
.draw_handler_remove(self
._handle
, self
.spacetype
)
393 context
.area
.tag_redraw()
395 def modal(self
, context
, event
):
397 if event
.type == 'MOUSEMOVE':
398 # - calculate frame offset from pixel offset
399 # - get mouse.x and add it to initial frame num
400 self
.mouse
= (event
.mouse_region_x
, event
.mouse_region_y
)
402 px_offset
= (event
.mouse_region_x
- self
.init_mouse_x
)
403 self
.offset
= int(px_offset
/ self
.px_step
)
404 self
.new_frame
= self
.init_frame
+ self
.offset
406 if self
.rolling_mode
:
407 # Frame Flipping mode (equidistant scrub snap)
408 self
.index
= self
.init_index
+ self
.offset
409 # clamp to possible index range
410 self
.index
= min(max(self
.index
, 0), self
.index_limit
)
411 self
.new_frame
= self
.pos
[self
.index
]
412 context
.scene
.frame_current
= self
.new_frame
413 self
.cursor_x
= self
.init_mouse_x
+ (self
.offset
* self
.px_step
)
417 if self
.snap_ctrl
and event
.ctrl
:
419 if self
.snap_shift
and event
.shift
:
421 if self
.snap_alt
and event
.alt
:
426 # inverted snapping behavior
427 if not self
.snap_on
and not mod_snap
:
428 self
.new_frame
= nearest(self
.pos
, self
.new_frame
)
430 if self
.snap_on
or mod_snap
:
431 self
.new_frame
= nearest(self
.pos
, self
.new_frame
)
433 # frame range restriction
435 if self
.new_frame
< self
.f_start
:
436 self
.new_frame
= self
.f_start
437 elif self
.new_frame
> self
.f_end
:
438 self
.new_frame
= self
.f_end
440 # context.scene.frame_set(self.new_frame)
441 context
.scene
.frame_current
= self
.new_frame
443 # - recalculate offset to snap cursor to frame
444 self
.offset
= self
.new_frame
- self
.init_frame
446 # - calculate cursor pixel position from frame offset
447 self
.cursor_x
= self
.init_mouse_x
+ (self
.offset
* self
.px_step
)
449 if event
.type == 'ESC':
450 # frame_set(self.init_frame) ?
451 context
.scene
.frame_current
= self
.cancel_frame
452 self
._exit
_modal
(context
)
455 # Snap if pressing NOT used mouse key (right or mid)
456 if event
.type == self
.snap_mouse_key
:
457 if event
.value
== "PRESS":
462 if event
.type == self
.key
and event
.value
== 'RELEASE':
463 self
._exit
_modal
(context
)
466 return {"RUNNING_MODAL"}
471 def auto_rebind(self
, context
):
476 class GPTS_OT_set_scrub_keymap(bpy
.types
.Operator
):
477 bl_idname
= "animation.ts_set_keymap"
478 bl_label
= "Change keymap"
479 bl_description
= "Quick time scrubbing with a shortcut"
480 bl_options
= {"REGISTER", "INTERNAL"}
482 def invoke(self
, context
, event
):
483 self
.prefs
= get_addon_prefs().ts
488 self
.init_value
= self
.prefs
.keycode
489 self
.prefs
.keycode
= ''
490 context
.window_manager
.modal_handler_add(self
)
491 return {'RUNNING_MODAL'}
493 def modal(self
, context
, event
):
494 exclude_keys
= {'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE',
495 'TIMER_REPORT', 'ESC', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}
496 exclude_in
= ('SHIFT', 'CTRL', 'ALT')
497 if event
.type == 'ESC':
498 self
.prefs
.keycode
= self
.init_value
501 self
.ctrl
= event
.ctrl
502 self
.shift
= event
.shift
505 if event
.type not in exclude_keys
and not any(x
in event
.type for x
in exclude_in
):
506 print('key:', event
.type, 'value:', event
.value
)
507 if event
.value
== 'PRESS':
508 self
.report({'INFO'}, event
.type)
510 self
.prefs
.keycode
= event
.type
511 # Following condition to avoid unnecessary rebind update
512 if self
.prefs
.use_shift
!= event
.shift
:
513 self
.prefs
.use_shift
= event
.shift
515 if self
.prefs
.use_alt
!= event
.alt
:
516 self
.prefs
.use_alt
= event
.alt
518 # -# Trigger rebind update with last
519 self
.prefs
.use_ctrl
= event
.ctrl
523 return {"RUNNING_MODAL"}
526 class GPTS_timeline_settings(bpy
.types
.PropertyGroup
):
528 keycode
: StringProperty(
530 description
="Shortcut to trigger the scrub in viewport during press",
531 default
="MIDDLEMOUSE")
533 always_snap
: BoolProperty(
535 description
="Always snap to keys if any, modifier is used deactivate the snapping\nDisabled if no keyframe found",
538 rolling_mode
: BoolProperty(
540 description
="Alternative Gap-less timeline. No time information to quickly roll/flip over keys\nOverride normal and 'always snap' mode",
545 description
="Enable/Disable timeline scrub",
549 use_in_timeline_editor
: BoolProperty(
550 name
="Shortcut in timeline editors",
551 description
="Add the same shortcut to scrub in timeline editor windows",
555 use_shift
: BoolProperty(
556 name
="Combine With Shift",
557 description
="Add shift",
561 use_alt
: BoolProperty(
562 name
="Combine With Alt",
563 description
="Add alt",
567 use_ctrl
: BoolProperty(
568 name
="Combine With Ctrl",
569 description
="Add ctrl",
573 evaluate_gp_obj_key
: BoolProperty(
574 name
='Use Gpencil Object Keyframes',
575 description
="Also snap on greasepencil object keyframe (else only active layer frames)",
578 pixel_step
: IntProperty(
579 name
="Frame Interval On Screen",
580 description
="Pixel steps on screen that represent a frame intervals",
589 use_hud
: BoolProperty(
590 name
='Display Timeline Overlay',
591 description
="Display overlays with timeline information when scrubbing time in viewport",
594 use_hud_time_line
: BoolProperty(
596 description
="Display a static marks overlay to represent timeline when scrubbing",
599 use_hud_keyframes
: BoolProperty(
601 description
="Display shapes overlay to show keyframe position when scrubbing",
604 use_hud_playhead
: BoolProperty(
606 description
="Display the playhead as a vertical line to show position in time",
609 use_hud_frame_current
: BoolProperty(
610 name
='Text Frame Current',
611 description
="Display the current frame as text above mouse cursor",
614 use_hud_frame_offset
: BoolProperty(
615 name
='Text Frame Offset',
616 description
="Display frame offset from initial position as text above mouse cursor",
619 color_timeline
: FloatVectorProperty(
620 name
="Timeline Color",
621 subtype
='COLOR_GAMMA',
623 default
=(0.5, 0.5, 0.5, 0.6),
625 description
="Color of the temporary timeline")
627 color_playhead
: FloatVectorProperty(
629 subtype
='COLOR_GAMMA',
631 default
=(0.01, 0.64, 1.0, 0.8),
633 description
="Color of the temporary line cursor and text")
636 playhead_size
: IntProperty(
637 name
="Playhead Size",
638 description
="Playhead height in pixels",
647 lines_size
: IntProperty(
648 name
="Frame Lines Size",
649 description
="Frame lines height in pixels",
658 keyframe_aspect
: EnumProperty(
659 name
="Keyframe Display",
660 description
="Customize aspect of the keyframes",
664 'Keyframe displayed as thick lines', 'SNAP_INCREMENT', 0),
666 'Keyframe displayed as squares', 'HANDLETYPE_VECTOR_VEC', 1),
667 ('DIAMOND', 'Diamond',
668 'Keyframe displayed as diamonds', 'HANDLETYPE_FREE_VEC', 2),
672 def draw_ts_pref(prefs
, layout
):
674 layout
.label(text
='Timeline Scrub:')
675 layout
.prop(prefs
, 'use')
678 layout
.prop(prefs
, 'evaluate_gp_obj_key')
679 layout
.prop(prefs
, 'pixel_step')
683 box
.label(text
='Keymap:')
684 box
.operator('animation.ts_set_keymap',
685 text
='Click here to change shortcut')
688 row
= box
.row(align
=True)
689 row
.prop(prefs
, 'use_ctrl', text
='Ctrl')
690 row
.prop(prefs
, 'use_alt', text
='Alt')
691 row
.prop(prefs
, 'use_shift', text
='Shift')
694 if prefs
.keycode
== 'LEFTMOUSE':
696 elif prefs
.keycode
== 'MIDDLEMOUSE':
698 elif prefs
.keycode
== 'RIGHTMOUSE':
701 row
.label(text
=f
'{prefs.keycode}', icon
=icon
)
704 row
.label(text
=f
'Key: {prefs.keycode}')
707 box
.label(text
='[ NOW TYPE KEY OR CLICK TO USE, WITH MODIFIER ]')
709 if prefs
.always_snap
:
710 snap_text
= 'Disable keyframes snap: '
712 snap_text
= 'Keyframes snap: '
714 snap_text
+= 'Left Mouse' if prefs
.keycode
== 'RIGHTMOUSE' else 'Right Mouse'
715 if not prefs
.use_ctrl
:
716 snap_text
+= ' or Ctrl'
717 if not prefs
.use_shift
:
718 snap_text
+= ' or Shift'
719 if not prefs
.use_alt
:
720 snap_text
+= ' or Alt'
722 if prefs
.rolling_mode
:
723 snap_text
= 'Gap-less mode (always snap)'
725 box
.label(text
=snap_text
, icon
='SNAP_ON')
726 if prefs
.keycode
in ('LEFTMOUSE', 'RIGHTMOUSE', 'MIDDLEMOUSE') and not prefs
.use_ctrl
and not prefs
.use_alt
and not prefs
.use_shift
:
728 text
="Recommended to choose at least one modifier to combine with clicks (default: Ctrl+Alt)", icon
="ERROR")
731 row
.prop(prefs
, 'always_snap')
732 row
.prop(prefs
, 'rolling_mode')
733 box
.prop(prefs
, 'use_in_timeline_editor',
734 text
='Add same shortcut to scrub within timeline editors')
738 box
.prop(prefs
, 'use_hud')
742 row
.prop(prefs
, 'color_timeline')
743 row
.prop(prefs
, 'color_playhead', text
='Cursor And Text Color')
744 col
.label(text
='Show:')
746 row
.prop(prefs
, 'use_hud_time_line')
747 row
.prop(prefs
, 'lines_size')
749 row
.prop(prefs
, 'use_hud_playhead')
750 row
.prop(prefs
, 'playhead_size')
752 row
.prop(prefs
, 'use_hud_keyframes')
753 row
.prop(prefs
, 'keyframe_aspect', text
='')
755 row
.prop(prefs
, 'use_hud_frame_current')
756 row
.prop(prefs
, 'use_hud_frame_offset')
757 col
.enabled
= prefs
.use_hud
765 def register_keymaps():
766 prefs
= get_addon_prefs().ts
770 kc
= bpy
.context
.window_manager
.keyconfigs
.addon
774 km
= kc
.keymaps
.new(name
="Grease Pencil", space_type
="EMPTY", region_type
='WINDOW')
776 if not prefs
.keycode
:
777 print(r
'/!\ Timeline scrub: no keycode entered for keymap')
779 kmi
= km
.keymap_items
.new(
780 'animation.time_scrub',
781 type=prefs
.keycode
, value
='PRESS',
782 alt
=prefs
.use_alt
, ctrl
=prefs
.use_ctrl
, shift
=prefs
.use_shift
, any
=False)
784 addon_keymaps
.append((km
, kmi
))
786 # - Add keymap in timeline editors
787 if prefs
.use_in_timeline_editor
:
790 ('Dopesheet', 'DOPESHEET_EDITOR', 'anim.change_frame'),
791 ('Graph Editor', 'GRAPH_EDITOR', 'graph.cursor_set'),
792 ("NLA Editor", "NLA_EDITOR", 'anim.change_frame'),
793 ("Sequencer", "SEQUENCE_EDITOR", 'anim.change_frame')
794 # ("Clip Graph Editor", "CLIP_EDITOR", 'clip.change_frame'),
797 for editor
, space
, operator
in editor_l
:
798 km
= kc
.keymaps
.new(name
=editor
, space_type
=space
)
799 kmi
= km
.keymap_items
.new(
800 operator
, type=prefs
.keycode
, value
='PRESS',
801 alt
=prefs
.use_alt
, ctrl
=prefs
.use_ctrl
, shift
=prefs
.use_shift
)
802 addon_keymaps
.append((km
, kmi
))
805 def unregister_keymaps():
806 for km
, kmi
in addon_keymaps
:
807 km
.keymap_items
.remove(kmi
)
808 addon_keymaps
.clear()
814 GPTS_OT_set_scrub_keymap
,
819 bpy
.utils
.register_class(cls
)
824 for cls
in reversed(classes
):
825 bpy
.utils
.unregister_class(cls
)