Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPullusb <bernou.samuel@gmail.com>2021-03-15 03:09:22 +0300
committerPullusb <bernou.samuel@gmail.com>2021-03-15 03:09:22 +0300
commit822a1da41c31537b1f864898d5ab901c321830d5 (patch)
treea1479609ec42d6cb9f5fa96cccb8abe639dad03c /greasepencil_tools
parentef3104dae302dcfb08b21e32d10b548bf304bd29 (diff)
GPencil Tools: Add rolling timeline
Rolling mode: Introduce a new alternative timeline (as an option in addon preferences, disabled by default) Display all keys without timegap (discard timing informations). Allow to quickly roll/flip over keyframes.
Diffstat (limited to 'greasepencil_tools')
-rw-r--r--greasepencil_tools/__init__.py2
-rw-r--r--greasepencil_tools/timeline_scrub.py192
2 files changed, 122 insertions, 72 deletions
diff --git a/greasepencil_tools/__init__.py b/greasepencil_tools/__init__.py
index de5bf06c..fa8e97c1 100644
--- a/greasepencil_tools/__init__.py
+++ b/greasepencil_tools/__init__.py
@@ -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, 3, 4),
+"version": (1, 4, 0),
"blender": (2, 91, 0),
"location": "Sidebar > Grease Pencil > Grease Pencil Tools",
"warning": "",
diff --git a/greasepencil_tools/timeline_scrub.py b/greasepencil_tools/timeline_scrub.py
index d742b594..3f69ebb9 100644
--- a/greasepencil_tools/timeline_scrub.py
+++ b/greasepencil_tools/timeline_scrub.py
@@ -123,6 +123,7 @@ class GPTS_OT_time_scrub(bpy.types.Operator):
self.key = prefs.keycode
self.evaluate_gp_obj_key = prefs.evaluate_gp_obj_key
self.always_snap = prefs.always_snap
+ self.rolling_mode = prefs.rolling_mode
self.dpi = context.preferences.system.dpi
self.ui_scale = context.preferences.system.ui_scale
@@ -146,7 +147,7 @@ class GPTS_OT_time_scrub(bpy.types.Operator):
self.init_mouse_x = self.cursor_x = event.mouse_region_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.cancel_frame = self.init_frame = self.new_frame = context.scene.frame_current
self.lock_range = context.scene.lock_frame_selection_to_range
if context.scene.use_preview_range:
self.f_start = context.scene.frame_preview_start
@@ -195,11 +196,28 @@ class GPTS_OT_time_scrub(bpy.types.Operator):
if not ob or not self.pos:
# Disable inverted behavior if no frame to snap
self.always_snap = False
+ if self.rolling_mode:
+ self.report({'WARNING'}, 'No Keys to flip on')
+ return {'CANCELLED'}
+
+ if self.rolling_mode:
+ # sorted and casted to int list since it's going to work with indexes
+ self.pos = sorted([int(f) for f in self.pos])
+ # find and make current frame the "starting" frame (force snap)
+ active_pos = [i for i, num in enumerate(self.pos) if num <= self.init_frame]
+ if active_pos:
+ self.init_index = active_pos[-1]
+ self.init_frame = self.new_frame = self.pos[self.init_index]
+ else:
+ self.init_index = 0
+ self.init_frame = self.new_frame = self.pos[0]
+
+ # del active_pos
+ self.index_limit = len(self.pos) - 1
# Also snap on play bounds (sliced off for keyframe display)
self.pos += [self.f_start, self.f_end]
-
# Disable Onion skin
self.active_space_data = context.space_data
self.onion_skin = None
@@ -241,43 +259,44 @@ class GPTS_OT_time_scrub(bpy.types.Operator):
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)))
+ if not self.rolling_mode:
+ # 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 + \
- (self.f_start - self.init_frame) * self.px_step
- end_x = self.init_mouse_x + \
- (self.f_end - 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]))
+ if not self.rolling_mode:
+ # Add start/end boundary bracket to HUD
+ start_x = self.init_mouse_x + \
+ (self.f_start - self.init_frame) * self.px_step
+ end_x = self.init_mouse_x + \
+ (self.f_end - 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)]
@@ -286,16 +305,24 @@ class GPTS_OT_time_scrub(bpy.types.Operator):
shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') # initiate shader
self.batch_timeline = batch_for_shader(
shader, 'LINES', {"pos": self.hud_lines})
+
+ if self.rolling_mode:
+ current_id = self.pos.index(self.new_frame)
+ # Add init_frame to "cancel" it in later UI code
+ ui_key_pos = [i - current_id + self.init_frame for i, _f in enumerate(self.pos[:-2])]
+ else:
+ ui_key_pos = self.pos[:-2]
+
# 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]:
+ for i in ui_key_pos:
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.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})
@@ -309,7 +336,7 @@ class GPTS_OT_time_scrub(bpy.types.Operator):
shaped_key = []
indices = []
idx_offset = 0
- for i in self.pos[:-2]:
+ for i in ui_key_pos:
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
@@ -339,6 +366,8 @@ class GPTS_OT_time_scrub(bpy.types.Operator):
# convert frame list to array for numpy snap utility
self.pos = np.asarray(self.pos)
+ if self.rolling_mode:
+ context.scene.frame_current = self.new_frame
args = (self, context)
self.viewtype = None
@@ -383,42 +412,52 @@ class GPTS_OT_time_scrub(bpy.types.Operator):
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
-
- ## Snapping
- if self.always_snap:
- # inverted snapping behavior
- if not self.snap_on and not mod_snap:
- self.new_frame = nearest(self.pos, self.new_frame)
- else:
- if self.snap_on or mod_snap:
- self.new_frame = nearest(self.pos, self.new_frame)
+ if self.rolling_mode:
+ # Frame Flipping mode (equidistant scrub snap)
+ self.index = self.init_index + self.offset
+ # clamp to possible index range
+ self.index = min(max(self.index, 0), self.index_limit)
+ self.new_frame = self.pos[self.index]
+ context.scene.frame_current = self.new_frame
+ self.cursor_x = self.init_mouse_x + (self.offset * self.px_step)
- # frame range restriction
- if self.lock_range:
- if self.new_frame < self.f_start:
- self.new_frame = self.f_start
- elif self.new_frame > self.f_end:
- self.new_frame = self.f_end
-
- # context.scene.frame_set(self.new_frame)
- context.scene.frame_current = self.new_frame
-
- # - 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)
+ else:
+ 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
+
+ ## Snapping
+ if self.always_snap:
+ # inverted snapping behavior
+ if not self.snap_on and not mod_snap:
+ self.new_frame = nearest(self.pos, self.new_frame)
+ else:
+ if self.snap_on or mod_snap:
+ self.new_frame = nearest(self.pos, self.new_frame)
+
+ # frame range restriction
+ if self.lock_range:
+ if self.new_frame < self.f_start:
+ self.new_frame = self.f_start
+ elif self.new_frame > self.f_end:
+ self.new_frame = self.f_end
+
+ # context.scene.frame_set(self.new_frame)
+ context.scene.frame_current = self.new_frame
+
+ # - 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)
if event.type == 'ESC':
# frame_set(self.init_frame) ?
- context.scene.frame_current = self.init_frame
+ context.scene.frame_current = self.cancel_frame
self._exit_modal(context)
return {'CANCELLED'}
@@ -505,6 +544,11 @@ class GPTS_timeline_settings(bpy.types.PropertyGroup):
description="Always snap to keys if any, modifier is used deactivate the snapping\nDisabled if no keyframe found",
default=False)
+ rolling_mode: BoolProperty(
+ name="Rolling Mode",
+ description="Alternative Gap-less timeline. No time informations to quickly roll/flip over keys\nOverride normal and 'always snap' mode",
+ default=False)
+
use_in_timeline_editor: BoolProperty(
name="Shortcut in timeline editors",
description="Add the same shortcut to scrub in timeline editor windows",
@@ -666,7 +710,7 @@ def draw_ts_pref(prefs, layout):
snap_text = 'Disable keyframes snap: '
else:
snap_text = 'Keyframes snap: '
-
+
snap_text += 'Left Mouse' if prefs.keycode == 'RIGHTMOUSE' else 'Right Mouse'
if not prefs.use_ctrl:
snap_text += ' or Ctrl'
@@ -674,12 +718,18 @@ def draw_ts_pref(prefs, layout):
snap_text += ' or Shift'
if not prefs.use_alt:
snap_text += ' or Alt'
+
+ if prefs.rolling_mode:
+ snap_text = 'Gap-less mode (always snap)'
+
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, 'always_snap')
+ row = box.row()
+ row.prop(prefs, 'always_snap')
+ row.prop(prefs, 'rolling_mode')
box.prop(prefs, 'use_in_timeline_editor',
text='Add same shortcut to scrub within timeline editors')