Skip to content
Snippets Groups Projects
Commit 76fba3f8 authored by Pullusb's avatar Pullusb
Browse files

GPencil Tools: Add viewport timeline scrub feature

Add timeline scrubbing from the standalone add-on : https://github.com/Pullusb/viewport_timeline_scrub
This bind a shortcut to move in timeline directly from viewport, VSE and movie clip editors.
Display keyframes and snap using `Ctrl` key.
Propagate the chosen shortcut to timeline editors.
Shortcut, behavior and overlays can be customized in addon-preferences.
parent cd176b26
No related branches found
No related tags found
No related merge requests found
......@@ -21,7 +21,7 @@ bl_info = {
"name": "Grease Pencil Tools",
"description": "Extra tools for Grease Pencil",
"author": "Samuel Bernou, Antonio Vazquez, Daniel Martinez Lara, Matias Mendiola",
"version": (1, 1, 6),
"version": (1, 2, 0),
"blender": (2, 91, 0),
"location": "Sidebar > Grease Pencil > Grease Pencil Tools",
"warning": "",
......@@ -36,12 +36,14 @@ from . import (prefs,
box_deform,
line_reshape,
rotate_canvas,
timeline_scrub,
import_brush_pack,
ui_panels,
)
def register():
prefs.register()
timeline_scrub.register()
box_deform.register()
line_reshape.register()
rotate_canvas.register()
......@@ -57,6 +59,7 @@ def unregister():
rotate_canvas.unregister()
box_deform.unregister()
line_reshape.unregister()
timeline_scrub.unregister()
prefs.unregister()
if __name__ == "__main__":
......
......@@ -22,6 +22,7 @@ from bpy.props import (
BoolProperty,
EnumProperty,
StringProperty,
PointerProperty,
# IntProperty,
)
......@@ -33,6 +34,8 @@ def get_addon_prefs():
addon_prefs = bpy.context.preferences.addons[addon_name].preferences
return (addon_prefs)
from .timeline_scrub import GPTS_timeline_settings, draw_ts_pref
## Addons Preferences Update Panel
def update_panel(self, context):
try:
......@@ -48,9 +51,11 @@ def auto_rebind(self, context):
register_keymaps()
class GreasePencilAddonPrefs(bpy.types.AddonPreferences):
bl_idname = os.path.splitext(__name__)[0]#'greasepencil-addon' ... __package__ ?
bl_idname = os.path.splitext(__name__)[0] #'greasepencil-addon' ... __package__ ?
# bl_idname = __name__
ts: PointerProperty(type=GPTS_timeline_settings)
category : StringProperty(
name="Category",
description="Choose a name for the category of the panel",
......@@ -127,6 +132,7 @@ class GreasePencilAddonPrefs(bpy.types.AddonPreferences):
update=auto_rebind)
def draw(self, context):
prefs = get_addon_prefs()
layout = self.layout
# layout.use_property_split = True
row= layout.row(align=True)
......@@ -176,6 +182,9 @@ class GreasePencilAddonPrefs(bpy.types.AddonPreferences):
box.label(text="view3d.rotate_canvas")
box.prop(self, 'canvas_use_hud')
## SCRUB TIMELINE
box = layout.box()
draw_ts_pref(prefs.ts, box)
if self.pref_tabs == 'TUTO':
......@@ -237,6 +246,7 @@ def unregister_keymaps():
### REGISTER ---
def register():
bpy.utils.register_class(GPTS_timeline_settings)
bpy.utils.register_class(GreasePencilAddonPrefs)
# Force box deform running to false
bpy.context.preferences.addons[os.path.splitext(__name__)[0]].preferences.boxdeform_running = False
......@@ -245,3 +255,4 @@ def register():
def unregister():
unregister_keymaps()
bpy.utils.unregister_class(GreasePencilAddonPrefs)
bpy.utils.unregister_class(GPTS_timeline_settings)
\ No newline at end of file
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
'''Based on viewport_timeline_scrub standalone addon - Samuel Bernou'''
from .prefs import get_addon_prefs
import numpy as np
from time import time
import bpy
import gpu
import bgl
import blf
from gpu_extras.batch import batch_for_shader
from bpy.props import (BoolProperty,
StringProperty,
IntProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
EnumProperty)
""" bl_info = {
"name": "Viewport Scrub Timeline",
"description": "Scrub on timeline from viewport and snap to nearest keyframe",
"author": "Samuel Bernou",
"version": (0, 7, 5),
"blender": (2, 91, 0),
"location": "View3D > shortcut key chosen in addon prefs",
"warning": "",
"doc_url": "https://github.com/Pullusb/scrub_timeline",
"category": "Object"}
"""
def nearest(array, value):
'''
Get a numpy array and a target value
Return closest val found in array to passed value
'''
idx = (np.abs(array - value)).argmin()
return array[idx]
def draw_callback_px(self, context):
'''Draw callback use by modal to draw in viewport'''
if context.area != self.current_area:
return
## lines and shaders
# 50% alpha, 2 pixel width line
# text
font_id = 0
shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') # initiate shader
bgl.glEnable(bgl.GL_BLEND)
bgl.glLineWidth(1)
# - # Draw HUD
if self.use_hud_time_line:
shader.bind()
shader.uniform_float("color", self.color_timeline)
self.batch_timeline.draw(shader)
# - # Display keyframes
if self.use_hud_keyframes:
if self.keyframe_aspect == 'LINE':
bgl.glLineWidth(3)
shader.bind()
shader.uniform_float("color", self.color_timeline)
self.batch_keyframes.draw(shader)
else:
# - # Display keyframe as diamonds
bgl.glLineWidth(1)
shader.bind()
shader.uniform_float("color", self.color_timeline)
# shader.uniform_float("color", list(self.color_timeline[:3]) + [1]) # timeline color full opacity
# shader.uniform_float("color", (0.8, 0.8, 0.8, 0.8)) # grey
# shader.uniform_float("color", (0.9, 0.69, 0.027, 1.0)) # yellow-ish
# shader.uniform_float("color",(1.0, 0.515, 0.033, 1.0)) # orange 'selected keyframe'
self.batch_keyframes.draw(shader)
# - # Display init frame text (under playhead)
# if self.use_hud_frame_init: # propertie not existing currently
# blf.position(font_id, self.init_mouse_x,
# self.init_mouse_y - (60 *self.ui_scale), 0)
# blf.size(font_id, 16, self.dpi)
# blf.color(font_id, *self.color_timeline)
# blf.draw(font_id, f'{self.init_frame:.0f}')
# - # Show current frame line
bgl.glLineWidth(1)
if self.use_hud_playhead:
# -# old full height playhead
# playhead = [(self.cursor_x, 0), (self.cursor_x, context.area.height)]
playhead = [(self.cursor_x, self.my + self.playhead_size/2),
(self.cursor_x, self.my - self.playhead_size/2)]
batch = batch_for_shader(shader, 'LINES', {"pos": playhead})
shader.bind()
shader.uniform_float("color", self.color_playhead)
batch.draw(shader)
# restore opengl defaults
bgl.glDisable(bgl.GL_BLEND)
# - # Display current frame text
blf.color(font_id, *self.color_text)
if self.use_hud_frame_current:
blf.position(font_id, self.mouse[0]+10, self.mouse[1]+10, 0)
# Id, Point size of the font, dots per inch value to use for drawing.
blf.size(font_id, 30, self.dpi) # 72
blf.draw(font_id, f'{self.new_frame:.0f}')
# - # Display frame offset text
if self.use_hud_frame_offset:
blf.position(font_id, self.mouse[0]+10,
self.mouse[1]+(40*self.ui_scale), 0)
blf.size(font_id, 16, self.dpi)
# blf.color(font_id, *self.color_text)
sign = '+' if self.offset > 0 else ''
blf.draw(font_id, f'{sign}{self.offset:.0f}')
class GPTS_OT_time_scrub(bpy.types.Operator):
bl_idname = "animation.time_scrub"
bl_label = "Time scrub"
bl_description = "Quick time scrubbing with a shortcut"
bl_options = {"REGISTER", "INTERNAL", "UNDO"}
@classmethod
def poll(cls, context):
return context.space_data.type in ('VIEW_3D', 'SEQUENCE_EDITOR', 'CLIP_EDITOR')
def invoke(self, context, event):
prefs = get_addon_prefs().ts
# Gpencil contexts : ('PAINT_GPENCIL', 'EDIT_GPENCIL')
# if context.space_data.type != 'VIEW_3D':
# self.report({'WARNING'}, "Work only in Viewport")
# return {'CANCELLED'}
self.current_area = context.area
self.key = prefs.keycode
self.evaluate_gp_obj_key = prefs.evaluate_gp_obj_key
self.dpi = context.preferences.system.dpi
self.ui_scale = context.preferences.system.ui_scale
# hud prefs
self.color_timeline = prefs.color_timeline
self.color_playhead = prefs.color_playhead
self.color_text = prefs.color_playhead
self.use_hud_time_line = prefs.use_hud_time_line
self.use_hud_keyframes = prefs.use_hud_keyframes
self.keyframe_aspect = prefs.keyframe_aspect
self.use_hud_playhead = prefs.use_hud_playhead
self.use_hud_frame_current = prefs.use_hud_frame_current
self.use_hud_frame_offset = prefs.use_hud_frame_offset
self.playhead_size = prefs.playhead_size
self.lines_size = prefs.lines_size
self.px_step = prefs.pixel_step
# global keycode
# self.key = keycode
self.snap_on = False
self.mouse = (event.mouse_region_x, event.mouse_region_y)
self.init_mouse_x = self.cursor_x = event.mouse_region_x # event.mouse_x
# self.init_mouse_y = event.mouse_region_y # only to display init frame text
self.init_frame = self.new_frame = context.scene.frame_current
self.offset = 0
self.pos = []
# Snap touch control
self.snap_ctrl = not prefs.use_ctrl
self.snap_shift = not prefs.use_shift
self.snap_alt = not prefs.use_alt
self.snap_mouse_key = 'LEFTMOUSE' if self.key == 'RIGHTMOUSE' else 'RIGHTMOUSE'
ob = context.object
if context.space_data.type != 'VIEW_3D':
ob = None # do not consider any key
if ob: # condition to allow empty scrubing
if ob.type != 'GPENCIL' or self.evaluate_gp_obj_key:
# Get objet keyframe position
anim_data = ob.animation_data
action = None
if anim_data:
action = anim_data.action
if action:
for fcu in action.fcurves:
for kf in fcu.keyframe_points:
if kf.co.x not in self.pos:
self.pos.append(kf.co.x)
if ob.type == 'GPENCIL':
# Get GP frame position
gpl = ob.data.layers
layer = gpl.active
if layer:
for frame in layer.frames:
if frame.frame_number not in self.pos:
self.pos.append(frame.frame_number)
# - Add start and end to snap on
if context.scene.use_preview_range:
play_bounds = [context.scene.frame_preview_start,
context.scene.frame_preview_end]
else:
play_bounds = [context.scene.frame_start, context.scene.frame_end]
# Also snap on play bounds (sliced off for keyframe display)
self.pos += play_bounds
self.pos = np.asarray(self.pos)
# Disable Onion skin
self.active_space_data = context.space_data
self.onion_skin = None
if context.space_data.type == 'VIEW_3D': # and 'GPENCIL' in context.mode
self.onion_skin = self.active_space_data.overlay.use_gpencil_onion_skin
self.active_space_data.overlay.use_gpencil_onion_skin = False
self.hud = prefs.use_hud
if not self.hud:
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
# - HUD params
# line_height = 25 # px
width = context.area.width
right = int((width - self.init_mouse_x) / self.px_step)
left = int(self.init_mouse_x / self.px_step)
hud_pos_x = []
for i in range(1, left):
hud_pos_x.append(self.init_mouse_x - i*self.px_step)
for i in range(1, right):
hud_pos_x.append(self.init_mouse_x + i*self.px_step)
# - list of double coords
init_height = 60
frame_height = self.lines_size
key_height = 14
bound_h = key_height + 19
bound_bracket_l = self.px_step/2
self.my = my = event.mouse_region_y # event.mouse_y
self.hud_lines = []
# - # frame marks
for x in hud_pos_x:
self.hud_lines.append((x, my - (frame_height/2)))
self.hud_lines.append((x, my + (frame_height/2)))
# - # init frame mark
self.hud_lines += [(self.init_mouse_x, my - (init_height/2)),
(self.init_mouse_x, my + (init_height/2))]
# Add start/end boundary bracket to HUD
start_x = self.init_mouse_x + \
(play_bounds[0] - self.init_frame) * self.px_step
end_x = self.init_mouse_x + \
(play_bounds[1] - self.init_frame) * self.px_step
# - # start
up = (start_x, my - (bound_h/2))
dn = (start_x, my + (bound_h/2))
self.hud_lines.append(up)
self.hud_lines.append(dn)
self.hud_lines.append(up)
self.hud_lines.append((up[0] + bound_bracket_l, up[1]))
self.hud_lines.append(dn)
self.hud_lines.append((dn[0] + bound_bracket_l, dn[1]))
# - # end
up = (end_x, my - (bound_h/2))
dn = (end_x, my + (bound_h/2))
self.hud_lines.append(up)
self.hud_lines.append(dn)
self.hud_lines.append(up)
self.hud_lines.append((up[0] - bound_bracket_l, up[1]))
self.hud_lines.append(dn)
self.hud_lines.append((dn[0] - bound_bracket_l, dn[1]))
# - # Horizontal line
self.hud_lines += [(0, my), (width, my)]
# - #other method with cutted H line
# leftmost = self.init_mouse_x - (left*self.px_step)
# rightmost = self.init_mouse_x + (right*self.px_step)
# self.hud_lines += [(leftmost, my), (rightmost, my)]
# - # Prepare batchs to draw static parts
shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') # initiate shader
self.batch_timeline = batch_for_shader(
shader, 'LINES', {"pos": self.hud_lines})
# - # keyframe display
if self.keyframe_aspect == 'LINE':
key_lines = []
# Slice off position of start/end frame added last (in list for snapping)
for i in self.pos[:-2]:
key_lines.append(
(self.init_mouse_x + ((i-self.init_frame) * self.px_step), my - (key_height/2)))
key_lines.append(
(self.init_mouse_x + ((i-self.init_frame)*self.px_step), my + (key_height/2)))
self.batch_keyframes = batch_for_shader(
shader, 'LINES', {"pos": key_lines})
else:
# diamond and square
# keysize = 6 # 5 for square, 4 or 6 for diamond
keysize = 6 if self.keyframe_aspect == 'DIAMOND' else 5
upper = 0
shaped_key = []
indices = []
idx_offset = 0
for i in self.pos[:-2]:
center = self.init_mouse_x + ((i-self.init_frame)*self.px_step)
if self.keyframe_aspect == 'DIAMOND':
# +1 on x is to correct pixel alignement
shaped_key += [(center-keysize, my+upper),
(center+1, my+keysize+upper),
(center+keysize, my+upper),
(center+1, my-keysize+upper)]
elif self.keyframe_aspect == 'SQUARE':
shaped_key += [(center-keysize+1, my-keysize+upper),
(center-keysize+1, my+keysize+upper),
(center+keysize, my+keysize+upper),
(center+keysize, my-keysize+upper)]
indices += [(0+idx_offset, 1+idx_offset, 2+idx_offset),
(0+idx_offset, 2+idx_offset, 3+idx_offset)]
idx_offset += 4
self.batch_keyframes = batch_for_shader(
shader, 'TRIS', {"pos": shaped_key}, indices=indices)
args = (self, context)
self.viewtype = None
self.spacetype = 'WINDOW' # is PREVIEW for VSE, needed for handler remove
if context.space_data.type == 'VIEW_3D':
self.viewtype = bpy.types.SpaceView3D
self._handle = bpy.types.SpaceView3D.draw_handler_add(
draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
# - # VSE disabling hud : Doesn't get right coordinates in preview window
elif context.space_data.type == 'SEQUENCE_EDITOR':
self.viewtype = bpy.types.SpaceSequenceEditor
self.spacetype = 'PREVIEW'
self._handle = bpy.types.SpaceSequenceEditor.draw_handler_add(
draw_callback_px, args, 'PREVIEW', 'POST_PIXEL')
elif context.space_data.type == 'CLIP_EDITOR':
self.viewtype = bpy.types.SpaceClipEditor
self._handle = bpy.types.SpaceClipEditor.draw_handler_add(
draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
def _exit_modal(self, context):
if self.onion_skin is not None:
self.active_space_data.overlay.use_gpencil_onion_skin = self.onion_skin
if self.hud and self.viewtype:
self.viewtype.draw_handler_remove(self._handle, self.spacetype)
context.area.tag_redraw()
def modal(self, context, event):
if event.type == 'MOUSEMOVE':
# - calculate frame offset from pixel offset
# - get mouse.x and add it to initial frame num
self.mouse = (event.mouse_region_x, event.mouse_region_y)
# self.mouse = (event.mouse_x, event.mouse_y)
px_offset = (event.mouse_region_x - self.init_mouse_x)
# int to overtake frame before change, use round to snap to closest (not blender style)
self.offset = int(px_offset / self.px_step)
self.new_frame = self.init_frame + self.offset
mod_snap = False
if self.snap_ctrl and event.ctrl:
mod_snap = True
if self.snap_shift and event.shift:
mod_snap = True
if self.snap_alt and event.alt:
mod_snap = True
if self.snap_on or mod_snap:
self.new_frame = nearest(self.pos, self.new_frame)
# context.scene.frame_set(self.new_frame)
context.scene.frame_current = self.new_frame
# - # follow exactly mouse
# self.cursor_x = event.mouse_x
# recalculate offset to snap cursor to frame
self.offset = self.new_frame - self.init_frame
# calculate cursor pixel position from frame offset
self.cursor_x = self.init_mouse_x + (self.offset * self.px_step)
# self._compute_timeline(context, event)
if event.type == 'ESC':
# context.scene.frame_set(self.init_frame)
context.scene.frame_current = self.init_frame
self._exit_modal(context)
return {'CANCELLED'}
# Snap if pressing NOT used mouse key (right or mid)
if event.type == self.snap_mouse_key:
if event.value == "PRESS":
self.snap_on = True
else:
self.snap_on = False
if event.type == self.key and event.value == 'RELEASE':
self._exit_modal(context)
return {'FINISHED'}
# End modal on right clic release ? (relaunched immediately if main key not released)
# if event.type == 'LEFTMOUSE':
# if event.value == "RELEASE":
# self._exit_modal(context)
# return {'FINISHED'}
return {"RUNNING_MODAL"}
# --- addon prefs
def auto_rebind(self, context):
unregister_keymaps()
register_keymaps()
class GPTS_OT_set_scrub_keymap(bpy.types.Operator):
bl_idname = "animation.ts_set_keymap"
bl_label = "Change keymap"
bl_description = "Quick time scrubbing with a shortcut"
bl_options = {"REGISTER", "INTERNAL"}
def invoke(self, context, event):
self.prefs = get_addon_prefs().ts
self.ctrl = False
self.shift = False
self.alt = False
self.init_value = self.prefs.keycode
self.prefs.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':
self.prefs.keycode = self.init_value
# self.report({'WARNING'}, 'Cancelled')
return {'CANCELLED'}
self.ctrl = event.ctrl
self.shift = event.shift
self.alt = event.alt
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)
# set the chosen key
self.prefs.keycode = event.type
# -# following condition aren't needed. Just here to avoid unnecessary rebind update (if possible)
if self.prefs.use_shift != event.shift: # condition
self.prefs.use_shift = event.shift
if self.prefs.use_alt != event.alt:
self.prefs.use_alt = event.alt
# -# Trigger rebind update with last
self.prefs.use_ctrl = event.ctrl
# -# no need to rebind updated by of the modifiers props..
# auto_rebind()
return {'FINISHED'}
return {"RUNNING_MODAL"}
class GPTS_timeline_settings(bpy.types.PropertyGroup):
keycode: StringProperty(
name="Shortcut",
description="Shortcut to trigger the scrub in viewport during press",
default="MIDDLEMOUSE")
use_in_timeline_editor: BoolProperty(
name="Shortcut in timeline editors",
description="Add the same shortcut to scrub in timeline editor windows",
default=True,
update=auto_rebind)
use_shift: BoolProperty(
name="Combine With Shift",
description="Add shift",
default=False,
update=auto_rebind)
use_alt: BoolProperty(
name="Combine With Alt",
description="Add alt",
default=True,
update=auto_rebind)
use_ctrl: BoolProperty(
name="Combine With Ctrl",
description="Add ctrl",
default=False,
update=auto_rebind)
evaluate_gp_obj_key: BoolProperty(
name='Use Gpencil Object Keyframes',
description="Also snap on greasepencil object keyframe (else only active layer frames)",
default=True)
# options (set) – Enumerator in ['HIDDEN', 'SKIP_SAVE', 'ANIMATABLE', 'LIBRARY_EDITABLE', 'PROPORTIONAL','TEXTEDIT_UPDATE'].
pixel_step: IntProperty(
name="Frame Interval On Screen",
description="Pixel steps on screen that represent a frame intervals",
default=10,
min=1,
max=500,
soft_min=2,
soft_max=100,
step=1,
subtype='PIXEL')
use_hud: BoolProperty(
name='Display Timeline Overlay',
description="Display overlays with timeline information when scrubbing time in viewport",
default=True)
use_hud_time_line: BoolProperty(
name='Timeline',
description="Display a static marks overlay to represent timeline when scrubbing",
default=True)
use_hud_keyframes: BoolProperty(
name='Keyframes',
description="Display shapes overlay to show keyframe position when scrubbing",
default=True)
use_hud_playhead: BoolProperty(
name='Playhead',
description="Display the playhead as a vertical line to show position in time",
default=True)
use_hud_frame_current: BoolProperty(
name='Text Frame Current',
description="Display the current frame as text above mouse cursor",
default=True)
use_hud_frame_offset: BoolProperty(
name='Text Frame Offset',
description="Display frame offset from initial position as text above mouse cursor",
default=True)
color_timeline: FloatVectorProperty(
name="Timeline Color",
subtype='COLOR_GAMMA',
size=4,
default=(0.5, 0.5, 0.5, 0.6),
min=0.0, max=1.0,
description="Color of the temporary timeline")
color_playhead: FloatVectorProperty(
name="Cusor Color",
subtype='COLOR_GAMMA',
size=4,
default=(0.01, 0.64, 1.0, 0.8), # red (0.9, 0.3, 0.3, 0.8)
min=0.0, max=1.0,
description="Color of the temporary line cursor and text")
# - # sizes
playhead_size: IntProperty(
name="Playhead Size",
description="Playhead height in pixels",
default=100,
min=2,
max=10000,
soft_min=10,
soft_max=5000,
step=1,
subtype='PIXEL')
lines_size: IntProperty(
name="Frame Lines Size",
description="Frame lines height in pixels",
default=10,
min=1,
max=10000,
soft_min=5,
soft_max=40,
step=1,
subtype='PIXEL')
keyframe_aspect: EnumProperty(
name="Keyframe Display",
description="Customize aspect of the keyframes",
default='LINE',
items=(
('LINE', 'Line',
'Keyframe displayed as thick lines', 'SNAP_INCREMENT', 0),
('SQUARE', 'Square',
'Keyframe displayed as squares', 'HANDLETYPE_VECTOR_VEC', 1),
('DIAMOND', 'Diamond',
'Keyframe displayed as diamonds', 'HANDLETYPE_FREE_VEC', 2),
))
def draw_ts_pref(prefs, layout):
# layout.use_property_split = True
# - # General settings
layout.label(text='Timeline Scrub:')
layout.prop(prefs, 'evaluate_gp_obj_key')
# Make a keycode capture system or find a way to display keymap with full_event=True
layout.prop(prefs, 'pixel_step')
# -/ Keymap -
box = layout.box()
box.label(text='Keymap:')
box.operator('animation.ts_set_keymap',
text='Click here to change shortcut')
if prefs.keycode:
row = box.row(align=True)
row.prop(prefs, 'use_ctrl', text='Ctrl')
row.prop(prefs, 'use_alt', text='Alt')
row.prop(prefs, 'use_shift', text='Shift')
# -/Cosmetic-
icon = None
if prefs.keycode == 'LEFTMOUSE':
icon = 'MOUSE_LMB'
elif prefs.keycode == 'MIDDLEMOUSE':
icon = 'MOUSE_MMB'
elif prefs.keycode == 'RIGHTMOUSE':
icon = 'MOUSE_RMB'
if icon:
row.label(text=f'{prefs.keycode}', icon=icon)
# -Cosmetic-/
else:
row.label(text=f'Key: {prefs.keycode}')
else:
box.label(text='[ NOW TYPE KEY OR CLICK TO USE, WITH MODIFIER ]')
snap_text = 'Snap to keyframes: '
snap_text += 'Left Mouse' if prefs.keycode == 'RIGHTMOUSE' else 'Right Mouse'
if not prefs.use_ctrl:
snap_text += ' or Ctrl'
if not prefs.use_shift:
snap_text += ' or Shift'
if not prefs.use_alt:
snap_text += ' or Alt'
box.label(text=snap_text, icon='SNAP_ON')
if prefs.keycode in ('LEFTMOUSE', 'RIGHTMOUSE', 'MIDDLEMOUSE') and not prefs.use_ctrl and not prefs.use_alt and not prefs.use_shift:
box.label(
text="Recommended to choose at least one modifier to combine with clicks (default: Ctrl+Alt)", icon="ERROR")
box.prop(prefs, 'use_in_timeline_editor',
text='Add same shortcut to scrub within timeline editors')
# - # HUD/OSD
box = layout.box()
box.prop(prefs, 'use_hud')
col = box.column()
row = col.row()
row.prop(prefs, 'color_timeline')
row.prop(prefs, 'color_playhead', text='Cursor And Text Color')
col.label(text='Show:')
row = col.row()
row.prop(prefs, 'use_hud_time_line')
row.prop(prefs, 'lines_size')
row = col.row()
row.prop(prefs, 'use_hud_playhead')
row.prop(prefs, 'playhead_size')
row = col.row()
row.prop(prefs, 'use_hud_keyframes')
row.prop(prefs, 'keyframe_aspect', text='')
row = col.row()
row.prop(prefs, 'use_hud_frame_current')
row.prop(prefs, 'use_hud_frame_offset')
col.enabled = prefs.use_hud
# --- Keymap
addon_keymaps = []
def register_keymaps():
prefs = get_addon_prefs().ts
addon = bpy.context.window_manager.keyconfigs.addon
# km = addon.keymaps.new(name = "3D View", space_type = "VIEW_3D")
km = addon.keymaps.new(name="Grease Pencil",
space_type="EMPTY", region_type='WINDOW')
if not prefs.keycode:
print(r'/!\ Timeline scrub: no keycode entered for keymap')
return
kmi = km.keymap_items.new(
'animation.time_scrub',
type=prefs.keycode, value='PRESS',
alt=prefs.use_alt, ctrl=prefs.use_ctrl, shift=prefs.use_shift, any=False)
kmi.repeat = False
addon_keymaps.append((km, kmi))
# - # Add keymap in timeline editors
if prefs.use_in_timeline_editor:
editor_l = [
('Dopesheet', 'DOPESHEET_EDITOR', 'anim.change_frame'),
('Graph Editor', 'GRAPH_EDITOR', 'graph.cursor_set'),
("NLA Editor", "NLA_EDITOR", 'anim.change_frame'),
("Sequencer", "SEQUENCE_EDITOR", 'anim.change_frame')
# ("Clip Graph Editor", "CLIP_EDITOR", 'clip.change_frame'),
]
for editor, space, operator in editor_l:
# region_type='WINDOW')
km = addon.keymaps.new(name=editor, space_type=space)
kmi = km.keymap_items.new(
operator, type=prefs.keycode, value='PRESS',
alt=prefs.use_alt, ctrl=prefs.use_ctrl, shift=prefs.use_shift)
# kmi.repeat = False
addon_keymaps.append((km, kmi))
def unregister_keymaps():
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
# --- REGISTER ---
classes = (
# GPTS_timeline_settings, ## registered in prefs.py
GPTS_OT_time_scrub,
GPTS_OT_set_scrub_keymap,
)
def register():
# other_file.register()
for cls in classes:
bpy.utils.register_class(cls)
# if not bpy.app.background:
register_keymaps()
#bpy.types.Scene.pgroup_name = bpy.props.PointerProperty(type = GPTS_PGT_settings)
def unregister():
# if not bpy.app.background:
unregister_keymaps()
# other_file.unregister()
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
#del bpy.types.Scene.pgroup_name
if __name__ == "__main__":
register()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment