180 lines
4.6 KiB
Python
180 lines
4.6 KiB
Python
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||
|
|
|
||
|
|
import typing
|
||
|
|
|
||
|
|
import bpy
|
||
|
|
import gpu
|
||
|
|
from gpu_extras.batch import batch_for_shader
|
||
|
|
|
||
|
|
from .utils import (redraw_all_areas_by_type)
|
||
|
|
from .synchro import (is_secondary_window, window_id, get_main_strip)
|
||
|
|
|
||
|
|
Int3 = typing.Tuple[int, int, int]
|
||
|
|
|
||
|
|
Float2 = typing.Tuple[float, float]
|
||
|
|
Float3 = typing.Tuple[float, float, float]
|
||
|
|
Float4 = typing.Tuple[float, float, float, float]
|
||
|
|
|
||
|
|
|
||
|
|
class LineDrawer:
|
||
|
|
def __init__(self):
|
||
|
|
self._format = gpu.types.GPUVertFormat()
|
||
|
|
self._pos_id = self._format.attr_add(
|
||
|
|
id="pos", comp_type="F32", len=2, fetch_mode="FLOAT"
|
||
|
|
)
|
||
|
|
self._color_id = self._format.attr_add(
|
||
|
|
id="color", comp_type="F32", len=4, fetch_mode="FLOAT"
|
||
|
|
)
|
||
|
|
|
||
|
|
self.shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
|
||
|
|
|
||
|
|
def draw(
|
||
|
|
self,
|
||
|
|
coords: typing.List[Float2],
|
||
|
|
indices: typing.List[Int3],
|
||
|
|
color: Float4,
|
||
|
|
):
|
||
|
|
if not coords:
|
||
|
|
return
|
||
|
|
|
||
|
|
gpu.state.blend_set('ALPHA')
|
||
|
|
|
||
|
|
self.shader.uniform_float("color", color)
|
||
|
|
|
||
|
|
batch = batch_for_shader(self.shader, 'TRIS', {"pos": coords}, indices=indices)
|
||
|
|
batch.program_set(self.shader)
|
||
|
|
batch.draw()
|
||
|
|
|
||
|
|
gpu.state.blend_set('NONE')
|
||
|
|
|
||
|
|
|
||
|
|
def get_scene_strip_in_out(strip):
|
||
|
|
""" Return the in and out keyframe of the given strip in the scene time reference"""
|
||
|
|
shot_in = strip.scene.frame_start + strip.frame_offset_start
|
||
|
|
shot_out = shot_in + strip.frame_final_duration - 1
|
||
|
|
return (shot_in, shot_out)
|
||
|
|
|
||
|
|
|
||
|
|
def draw_callback_px(line_drawer: LineDrawer):
|
||
|
|
context = bpy.context
|
||
|
|
region = context.region
|
||
|
|
|
||
|
|
wm = context.window_manager
|
||
|
|
|
||
|
|
if (
|
||
|
|
not wm.storypencil_settings.active
|
||
|
|
or not wm.storypencil_settings.show_main_strip_range
|
||
|
|
or not is_secondary_window(wm, window_id(context.window))
|
||
|
|
):
|
||
|
|
return
|
||
|
|
|
||
|
|
# get main strip driving the sync
|
||
|
|
strip = get_main_strip(wm)
|
||
|
|
|
||
|
|
if not strip or strip.scene != context.scene:
|
||
|
|
return
|
||
|
|
|
||
|
|
xwin1, ywin1 = region.view2d.region_to_view(0, 0)
|
||
|
|
one_pixel_further_x = region.view2d.region_to_view(1, 1)[0]
|
||
|
|
pixel_size_x = one_pixel_further_x - xwin1
|
||
|
|
rect_width = 1
|
||
|
|
|
||
|
|
shot_in, shot_out = get_scene_strip_in_out(strip)
|
||
|
|
key_coords_in = [
|
||
|
|
(
|
||
|
|
shot_in - rect_width * pixel_size_x,
|
||
|
|
ywin1,
|
||
|
|
),
|
||
|
|
(
|
||
|
|
shot_in + rect_width * pixel_size_x,
|
||
|
|
ywin1,
|
||
|
|
),
|
||
|
|
(
|
||
|
|
shot_in + rect_width * pixel_size_x,
|
||
|
|
ywin1 + context.region.height,
|
||
|
|
),
|
||
|
|
(
|
||
|
|
shot_in - rect_width * pixel_size_x,
|
||
|
|
ywin1 + context.region.height,
|
||
|
|
),
|
||
|
|
]
|
||
|
|
|
||
|
|
key_coords_out = [
|
||
|
|
(
|
||
|
|
shot_out - rect_width * pixel_size_x,
|
||
|
|
ywin1,
|
||
|
|
),
|
||
|
|
(
|
||
|
|
shot_out + rect_width * pixel_size_x,
|
||
|
|
ywin1,
|
||
|
|
),
|
||
|
|
(
|
||
|
|
shot_out + rect_width * pixel_size_x,
|
||
|
|
ywin1 + context.region.height,
|
||
|
|
),
|
||
|
|
(
|
||
|
|
shot_out - rect_width * pixel_size_x,
|
||
|
|
ywin1 + context.region.height,
|
||
|
|
),
|
||
|
|
]
|
||
|
|
|
||
|
|
indices = [(0, 1, 2), (2, 0, 3)]
|
||
|
|
# Draw the IN frame in green
|
||
|
|
# hack: in certain cases, opengl draw state is invalid for the first drawn item
|
||
|
|
# resulting in a non-colored line
|
||
|
|
# => draw it a first time with a null alpha, so that the second one is drawn correctly
|
||
|
|
line_drawer.draw(key_coords_in, indices, (0, 0, 0, 0))
|
||
|
|
line_drawer.draw(key_coords_in, indices, (0.3, 0.99, 0.4, 0.5))
|
||
|
|
# Draw the OUT frame un red
|
||
|
|
line_drawer.draw(key_coords_out, indices, (0.99, 0.3, 0.4, 0.5))
|
||
|
|
|
||
|
|
|
||
|
|
def tag_redraw_all_dopesheets():
|
||
|
|
redraw_all_areas_by_type(bpy.context, 'DOPESHEET')
|
||
|
|
|
||
|
|
|
||
|
|
# This is a list so it can be changed instead of set
|
||
|
|
# if it is only changed, it does not have to be declared as a global everywhere
|
||
|
|
cb_handle = []
|
||
|
|
|
||
|
|
|
||
|
|
def callback_enable():
|
||
|
|
if cb_handle:
|
||
|
|
return
|
||
|
|
|
||
|
|
# Doing GPU stuff in the background crashes Blender, so let's not.
|
||
|
|
if bpy.app.background:
|
||
|
|
return
|
||
|
|
|
||
|
|
line_drawer = LineDrawer()
|
||
|
|
# POST_VIEW allow to work in time coordinate (1 unit = 1 frame)
|
||
|
|
cb_handle[:] = (
|
||
|
|
bpy.types.SpaceDopeSheetEditor.draw_handler_add(
|
||
|
|
draw_callback_px, (line_drawer,), 'WINDOW', 'POST_VIEW'
|
||
|
|
),
|
||
|
|
)
|
||
|
|
|
||
|
|
tag_redraw_all_dopesheets()
|
||
|
|
|
||
|
|
|
||
|
|
def callback_disable():
|
||
|
|
if not cb_handle:
|
||
|
|
return
|
||
|
|
|
||
|
|
try:
|
||
|
|
bpy.types.SpaceDopeSheetEditor.draw_handler_remove(cb_handle[0], 'WINDOW')
|
||
|
|
except ValueError:
|
||
|
|
# Thrown when already removed.
|
||
|
|
pass
|
||
|
|
cb_handle.clear()
|
||
|
|
|
||
|
|
tag_redraw_all_dopesheets()
|
||
|
|
|
||
|
|
|
||
|
|
def register():
|
||
|
|
callback_enable()
|
||
|
|
|
||
|
|
|
||
|
|
def unregister():
|
||
|
|
callback_disable()
|