WIP: integration of the Attract addon into the Blender Cloud adddon.
This commit is contained in:
parent
63b976cb44
commit
c57da7ab2b
@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
bl_info = {
|
bl_info = {
|
||||||
'name': 'Blender Cloud',
|
'name': 'Blender Cloud',
|
||||||
'author': 'Sybren A. Stüvel and Francesco Siddi',
|
"author": "Sybren A. Stüvel, Francesco Siddi, Inês Almeida, Antony Riakiotakis",
|
||||||
'version': (1, 4, 3),
|
'version': (1, 4, 3),
|
||||||
'blender': (2, 77, 0),
|
'blender': (2, 77, 0),
|
||||||
'location': 'Addon Preferences panel, and Ctrl+Shift+Alt+A anywhere for texture browser',
|
'location': 'Addon Preferences panel, and Ctrl+Shift+Alt+A anywhere for texture browser',
|
||||||
@ -42,12 +42,13 @@ if 'pillar' in locals():
|
|||||||
|
|
||||||
pillar = importlib.reload(pillar)
|
pillar = importlib.reload(pillar)
|
||||||
cache = importlib.reload(cache)
|
cache = importlib.reload(cache)
|
||||||
|
attract = importlib.reload(attract)
|
||||||
else:
|
else:
|
||||||
from . import wheels
|
from . import wheels
|
||||||
|
|
||||||
wheels.load_wheels()
|
wheels.load_wheels()
|
||||||
|
|
||||||
from . import pillar, cache
|
from . import pillar, cache, attract
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -89,6 +90,7 @@ def register():
|
|||||||
blender.register()
|
blender.register()
|
||||||
settings_sync.register()
|
settings_sync.register()
|
||||||
image_sharing.register()
|
image_sharing.register()
|
||||||
|
attract.register()
|
||||||
|
|
||||||
|
|
||||||
def _monkey_patch_requests():
|
def _monkey_patch_requests():
|
||||||
@ -112,6 +114,7 @@ def unregister():
|
|||||||
from . import blender, texture_browser, async_loop, settings_sync, image_sharing
|
from . import blender, texture_browser, async_loop, settings_sync, image_sharing
|
||||||
|
|
||||||
image_sharing.unregister()
|
image_sharing.unregister()
|
||||||
|
attract.unregister()
|
||||||
settings_sync.unregister()
|
settings_sync.unregister()
|
||||||
blender.unregister()
|
blender.unregister()
|
||||||
texture_browser.unregister()
|
texture_browser.unregister()
|
||||||
|
339
blender_cloud/attract/__init__.py
Normal file
339
blender_cloud/attract/__init__.py
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
# ##### 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 #####
|
||||||
|
|
||||||
|
# <pep8 compliant>
|
||||||
|
|
||||||
|
# Old info, kept here for reference, so that we can merge wiki pages,
|
||||||
|
# descriptions, etc.
|
||||||
|
#
|
||||||
|
# bl_info = {
|
||||||
|
# "name": "Attract",
|
||||||
|
# "author": "Francesco Siddi, Inês Almeida, Antony Riakiotakis",
|
||||||
|
# "version": (0, 2, 0),
|
||||||
|
# "blender": (2, 76, 0),
|
||||||
|
# "location": "Video Sequence Editor",
|
||||||
|
# "description":
|
||||||
|
# "Blender integration with the Attract task tracking service"
|
||||||
|
# ". *requires the Blender ID add-on",
|
||||||
|
# "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
|
||||||
|
# "Scripts/Workflow/Attract",
|
||||||
|
# "category": "Workflow",
|
||||||
|
# "support": "TESTING"
|
||||||
|
# }
|
||||||
|
|
||||||
|
if "bpy" in locals():
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
importlib.reload(draw)
|
||||||
|
else:
|
||||||
|
from . import draw
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from pillarsdk.api import Api
|
||||||
|
from pillarsdk.nodes import Node
|
||||||
|
from pillarsdk.nodes import NodeType
|
||||||
|
from pillarsdk import utils
|
||||||
|
from pillarsdk.exceptions import ResourceNotFound
|
||||||
|
|
||||||
|
from bpy.props import StringProperty
|
||||||
|
from bpy.types import Operator, Panel, AddonPreferences
|
||||||
|
|
||||||
|
|
||||||
|
def active_strip(context):
|
||||||
|
try:
|
||||||
|
return context.scene.sequence_editor.active_strip
|
||||||
|
except AttributeError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def remove_atc_props(strip):
|
||||||
|
"""Resets the attract custom properties assigned to a VSE strip"""
|
||||||
|
strip.atc_cut_in = 0
|
||||||
|
strip.atc_cut_out = 0
|
||||||
|
strip.atc_name = ""
|
||||||
|
strip.atc_description = ""
|
||||||
|
strip.atc_object_id = ""
|
||||||
|
strip.atc_is_synced = False
|
||||||
|
|
||||||
|
|
||||||
|
class ToolsPanel(Panel):
|
||||||
|
bl_label = 'Attract'
|
||||||
|
bl_space_type = 'SEQUENCE_EDITOR'
|
||||||
|
bl_region_type = 'UI'
|
||||||
|
|
||||||
|
def draw_header(self, context):
|
||||||
|
strip = active_strip(context)
|
||||||
|
if strip and strip.atc_object_id:
|
||||||
|
self.layout.prop(strip, 'atc_is_synced', text='')
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
strip = active_strip(context)
|
||||||
|
layout = self.layout
|
||||||
|
strip_types = {'MOVIE', 'IMAGE'}
|
||||||
|
if strip and strip.atc_object_id and strip.type in strip_types:
|
||||||
|
layout.prop(strip, 'atc_name', text='Name')
|
||||||
|
layout.prop(strip, 'atc_description', text='Description')
|
||||||
|
layout.prop(strip, 'atc_notes', text='Notes')
|
||||||
|
layout.prop(strip, 'atc_status', text='Status')
|
||||||
|
layout.prop(strip, 'atc_cut_in', text='Cut in')
|
||||||
|
# layout.prop(strip, 'atc_cut_out', text='Cut out')
|
||||||
|
|
||||||
|
if strip.atc_is_synced:
|
||||||
|
layout.operator('attract.shot_submit_update')
|
||||||
|
layout.operator('attract.shot_delete')
|
||||||
|
layout.operator('attract.strip_unlink')
|
||||||
|
|
||||||
|
elif strip and strip.type in strip_types:
|
||||||
|
layout.operator('attract.shot_submit_new')
|
||||||
|
layout.operator('attract.shot_relink')
|
||||||
|
else:
|
||||||
|
layout.label(text='Select a Movie or Image strip')
|
||||||
|
layout.operator('attract.shots_order_update')
|
||||||
|
|
||||||
|
|
||||||
|
class AttractShotSubmitNew(Operator):
|
||||||
|
bl_idname = "attract.shot_submit_new"
|
||||||
|
bl_label = "Submit to Attract"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
strip = active_strip(context)
|
||||||
|
return not strip.atc_object_id
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
from blender_cloud import pillar
|
||||||
|
import blender_id
|
||||||
|
|
||||||
|
strip = active_strip(context)
|
||||||
|
if strip.atc_object_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Filter the NodeType collection, but it's still a list
|
||||||
|
node_type_list = pillar.call(NodeType.all, {'where': "name=='shot'"})
|
||||||
|
# Get the 'shot' node type
|
||||||
|
node_type = node_type_list['_items'][0]
|
||||||
|
# Define the shot properties
|
||||||
|
prop = {'name': strip.name,
|
||||||
|
'description': '',
|
||||||
|
'properties': {'status': 'on_hold',
|
||||||
|
'notes': '',
|
||||||
|
'cut_in': strip.frame_offset_start,
|
||||||
|
'cut_out': strip.frame_offset_start + strip.frame_final_duration},
|
||||||
|
'order': 0,
|
||||||
|
'node_type': node_type['_id'],
|
||||||
|
'user': blender_id.get_active_user_id()}
|
||||||
|
|
||||||
|
# Create a Node item with the attract API
|
||||||
|
node = Node(prop)
|
||||||
|
post = pillar.call(node.create)
|
||||||
|
|
||||||
|
# Populate the strip with the freshly generated ObjectID and info
|
||||||
|
if not post:
|
||||||
|
self.report({'ERROR'}, 'Error creating node! Check the console for now.')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
strip.atc_object_id = node['_id']
|
||||||
|
strip.atc_is_synced = True
|
||||||
|
strip.atc_name = node['name']
|
||||||
|
strip.atc_cut_in = node['properties']['cut_in']
|
||||||
|
strip.atc_cut_out = node['properties']['cut_out']
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class AttractShotRelink(Operator):
|
||||||
|
bl_idname = "attract.shot_relink"
|
||||||
|
bl_label = "Relink to Attract"
|
||||||
|
strip_atc_object_id = bpy.props.StringProperty()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
from .. import pillar
|
||||||
|
|
||||||
|
strip = active_strip(context)
|
||||||
|
try:
|
||||||
|
node = pillar.call(Node.find, self.strip_atc_object_id)
|
||||||
|
except ResourceNotFound:
|
||||||
|
self.report({'ERROR'}, 'No shot found on the server')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
strip.atc_object_id = self.strip_atc_object_id
|
||||||
|
strip.atc_is_synced = True
|
||||||
|
strip.atc_name = node.name
|
||||||
|
strip.atc_cut_in = node.properties.cut_in
|
||||||
|
strip.atc_cut_out = node.properties.cut_out
|
||||||
|
strip.atc_description = node.description
|
||||||
|
|
||||||
|
self.report({'INFO'}, "Shot {0} relinked".format(node.name))
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
col = layout.column()
|
||||||
|
col.prop(self, 'strip_atc_object_id', text='Shot ID')
|
||||||
|
|
||||||
|
|
||||||
|
class AttractShotSubmitUpdate(Operator):
|
||||||
|
bl_idname = 'attract.shot_submit_update'
|
||||||
|
bl_label = 'Update'
|
||||||
|
bl_description = 'Syncronizes local and remote changes'
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
strip = active_strip(context)
|
||||||
|
# Update cut_in and cut_out properties on the strip
|
||||||
|
# strip.atc_cut_in = strip.frame_offset_start
|
||||||
|
# strip.atc_cut_out = strip.frame_offset_start + strip.frame_final_duration
|
||||||
|
# print("Query Attract server with {0}".format(strip.atc_object_id))
|
||||||
|
strip.atc_cut_out = strip.atc_cut_in + strip.frame_final_duration - 1
|
||||||
|
node = Node.find(strip.atc_object_id)
|
||||||
|
node.name = strip.atc_name
|
||||||
|
node.description = strip.atc_description
|
||||||
|
node.properties.cut_in = strip.atc_cut_in
|
||||||
|
node.properties.cut_out = strip.atc_cut_out
|
||||||
|
node.update()
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class AttractShotDelete(Operator):
|
||||||
|
bl_idname = 'attract.shot_delete'
|
||||||
|
bl_label = 'Delete'
|
||||||
|
bl_description = 'Remove from Attract'
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
strip = active_strip(context)
|
||||||
|
node = Node.find(strip.atc_object_id)
|
||||||
|
if node.delete():
|
||||||
|
remove_atc_props(strip)
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class AttractStripUnlink(Operator):
|
||||||
|
bl_idname = 'attract.strip_unlink'
|
||||||
|
bl_label = 'Unlink'
|
||||||
|
bl_description = 'Remove Attract props from the strip'
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
strip = active_strip(context)
|
||||||
|
remove_atc_props(strip)
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class AttractShotsOrderUpdate(Operator):
|
||||||
|
bl_idname = 'attract.shots_order_update'
|
||||||
|
bl_label = 'Update shots order'
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
from .. import pillar
|
||||||
|
|
||||||
|
# Get all shot nodes from server, build dictionary using ObjectID
|
||||||
|
# as indexes
|
||||||
|
node_type_list = pillar.call(NodeType.all, {'where': "name=='shot'"})
|
||||||
|
node_type = node_type_list._items[0]
|
||||||
|
|
||||||
|
shots = pillar.call(Node.all, {
|
||||||
|
'where': {'node_type': node_type._id},
|
||||||
|
'max_results': 100})
|
||||||
|
|
||||||
|
shots = shots._items
|
||||||
|
|
||||||
|
# TODO (fsiddi) take into account pagination. Currently we do not do it
|
||||||
|
# and it makes this dict useless.
|
||||||
|
# We should use the pagination info from the node_type_list query and
|
||||||
|
# keep querying until we have all the items.
|
||||||
|
shots_dict = {}
|
||||||
|
for shot in shots:
|
||||||
|
shots_dict[shot._id] = shot
|
||||||
|
|
||||||
|
# Build ordered list of strips from the edit.
|
||||||
|
strips_with_atc_object_id = [strip
|
||||||
|
for strip in context.scene.sequence_editor.sequences_all
|
||||||
|
if strip.atc_object_id]
|
||||||
|
|
||||||
|
strips_with_atc_object_id.sort(
|
||||||
|
key=lambda strip: strip.frame_start + strip.frame_offset_start)
|
||||||
|
index = 1
|
||||||
|
for strip in strips_with_atc_object_id:
|
||||||
|
"""
|
||||||
|
# Currently we use the code below to force update all nodes.
|
||||||
|
# Check that the shot is in the list of retrieved shots
|
||||||
|
if strip.atc_order != index: #or shots_dict[strip.atc_object_id]['order'] != index:
|
||||||
|
# If there is an update in the order, retrieve and update
|
||||||
|
# the node, as well as the VSE strip
|
||||||
|
# shot_node = Node.find(strip.atc_object_id)
|
||||||
|
# shot_node.order = index
|
||||||
|
# shot_node.update()
|
||||||
|
# strip.atc_order = index
|
||||||
|
print ("{0} > {1}".format(strip.atc_order, index))
|
||||||
|
"""
|
||||||
|
# We get all nodes one by one. This is bad and stupid.
|
||||||
|
try:
|
||||||
|
shot_node = pillar.call(Node.find, strip.atc_object_id)
|
||||||
|
# if shot_node.properties.order != index:
|
||||||
|
shot_node.order = index
|
||||||
|
shot_node.update()
|
||||||
|
print('{0} - updating {1}'.format(shot_node.order, shot_node.name))
|
||||||
|
strip.atc_order = index
|
||||||
|
index += 1
|
||||||
|
except ResourceNotFound:
|
||||||
|
# Reset the attract properties for any shot not found on the server
|
||||||
|
# print("Error: shot {0} not found".format(strip.atc_object_id))
|
||||||
|
remove_atc_props(strip)
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
bpy.types.Sequence.atc_is_synced = bpy.props.BoolProperty(name="Is synced")
|
||||||
|
bpy.types.Sequence.atc_object_id = bpy.props.StringProperty(name="Attract Object ID")
|
||||||
|
bpy.types.Sequence.atc_name = bpy.props.StringProperty(name="Shot Name")
|
||||||
|
bpy.types.Sequence.atc_description = bpy.props.StringProperty(name="Shot description")
|
||||||
|
bpy.types.Sequence.atc_notes = bpy.props.StringProperty(name="Shot notes")
|
||||||
|
bpy.types.Sequence.atc_cut_in = bpy.props.IntProperty(name="Cut in")
|
||||||
|
bpy.types.Sequence.atc_cut_out = bpy.props.IntProperty(name="Cut out")
|
||||||
|
bpy.types.Sequence.atc_status = bpy.props.EnumProperty(
|
||||||
|
items=[
|
||||||
|
('on_hold', 'On hold', 'The shot is on hold'),
|
||||||
|
('todo', 'Todo', 'Waiting'),
|
||||||
|
('in_progress', 'In progress', 'The show has been assigned')],
|
||||||
|
name="Status")
|
||||||
|
bpy.types.Sequence.atc_order = bpy.props.IntProperty(name="Order")
|
||||||
|
|
||||||
|
bpy.utils.register_class(ToolsPanel)
|
||||||
|
bpy.utils.register_class(AttractShotSubmitNew)
|
||||||
|
bpy.utils.register_class(AttractShotRelink)
|
||||||
|
bpy.utils.register_class(AttractShotSubmitUpdate)
|
||||||
|
bpy.utils.register_class(AttractShotDelete)
|
||||||
|
bpy.utils.register_class(AttractStripUnlink)
|
||||||
|
bpy.utils.register_class(AttractShotsOrderUpdate)
|
||||||
|
draw.callback_enable()
|
||||||
|
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
draw.callback_disable()
|
||||||
|
del bpy.types.Sequence.atc_is_synced
|
||||||
|
del bpy.types.Sequence.atc_object_id
|
||||||
|
del bpy.types.Sequence.atc_name
|
||||||
|
del bpy.types.Sequence.atc_description
|
||||||
|
del bpy.types.Sequence.atc_notes
|
||||||
|
del bpy.types.Sequence.atc_cut_in
|
||||||
|
del bpy.types.Sequence.atc_cut_out
|
||||||
|
del bpy.types.Sequence.atc_status
|
||||||
|
del bpy.types.Sequence.atc_order
|
||||||
|
bpy.utils.unregister_module(__name__)
|
142
blender_cloud/attract/draw.py
Normal file
142
blender_cloud/attract/draw.py
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
# ##### 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 #####
|
||||||
|
|
||||||
|
# <pep8-80 compliant>
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
def get_strip_rectf(strip):
|
||||||
|
# Get x and y in terms of the grid's frames and channels
|
||||||
|
x1 = strip.frame_final_start
|
||||||
|
x2 = strip.frame_final_end
|
||||||
|
y1 = strip.channel + 0.2
|
||||||
|
y2 = y1 + 0.25
|
||||||
|
|
||||||
|
return [x1, y1, x2, y2]
|
||||||
|
|
||||||
|
|
||||||
|
def draw_underline_in_strip(scroller_width, strip_coords, curx, color):
|
||||||
|
from bgl import glColor4f, glRectf, glEnable, glDisable, GL_BLEND
|
||||||
|
|
||||||
|
context = bpy.context
|
||||||
|
|
||||||
|
# Strip coords
|
||||||
|
s_x1, s_y1, s_x2, s_y2 = strip_coords
|
||||||
|
|
||||||
|
# Drawing coords
|
||||||
|
x = 0
|
||||||
|
d_y1 = s_y1
|
||||||
|
d_y2 = s_y2
|
||||||
|
d_x1 = s_x1
|
||||||
|
d_x2 = s_x2
|
||||||
|
|
||||||
|
# be careful not to override the current frame line
|
||||||
|
cf_x = context.scene.frame_current_final
|
||||||
|
y = 0
|
||||||
|
|
||||||
|
r, g, b, a = color
|
||||||
|
glColor4f(r, g, b, a)
|
||||||
|
glEnable(GL_BLEND)
|
||||||
|
|
||||||
|
# // this checks if the strip range overlaps the current f. label range
|
||||||
|
# // then it would need a polygon? to draw around it
|
||||||
|
# // TODO: check also if label display is ON
|
||||||
|
# Check if the current frame label overlaps the strip
|
||||||
|
# label_height = scroller_width * 2
|
||||||
|
# if d_y1 < label_height:
|
||||||
|
# if cf_x < d_x2 and d_x1 < cf_x + label_height:
|
||||||
|
# print("ALARM!!")
|
||||||
|
|
||||||
|
if d_x1 < cf_x and cf_x < d_x2:
|
||||||
|
# Bad luck, the line passes our strip
|
||||||
|
glRectf(d_x1, d_y1, cf_x - curx, d_y2)
|
||||||
|
glRectf(cf_x + curx, d_y1, d_x2, d_y2)
|
||||||
|
else:
|
||||||
|
# Normal, full rectangle draw
|
||||||
|
glRectf(d_x1, d_y1, d_x2, d_y2)
|
||||||
|
|
||||||
|
glDisable(GL_BLEND)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_callback_px():
|
||||||
|
context = bpy.context
|
||||||
|
|
||||||
|
if not context.scene.sequence_editor:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Calculate scroller width, dpi and pixelsize dependent
|
||||||
|
pixel_size = context.user_preferences.system.pixel_size
|
||||||
|
dpi = context.user_preferences.system.dpi
|
||||||
|
dpi_fac = pixel_size * dpi / 72
|
||||||
|
# A normal widget unit is 20, but the scroller is apparently 16
|
||||||
|
scroller_width = 16 * dpi_fac
|
||||||
|
|
||||||
|
region = context.region
|
||||||
|
xwin1, ywin1 = region.view2d.region_to_view(0, 0)
|
||||||
|
xwin2, ywin2 = region.view2d.region_to_view(region.width, region.height)
|
||||||
|
curx, cury = region.view2d.region_to_view(1, 0)
|
||||||
|
curx = curx - xwin1
|
||||||
|
|
||||||
|
for strip in context.scene.sequence_editor.sequences:
|
||||||
|
if strip.atc_object_id:
|
||||||
|
|
||||||
|
# Get corners (x1, y1), (x2, y2) of the strip rectangle in px region coords
|
||||||
|
strip_coords = get_strip_rectf(strip)
|
||||||
|
|
||||||
|
#check if any of the coordinates are out of bounds
|
||||||
|
if strip_coords[0] > xwin2 or strip_coords[2] < xwin1 or strip_coords[1] > ywin2 or strip_coords[3] < ywin1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Draw
|
||||||
|
color = [1.0, 0, 1.0, 0.5]
|
||||||
|
draw_underline_in_strip(scroller_width, strip_coords, curx, color)
|
||||||
|
|
||||||
|
|
||||||
|
def tag_redraw_all_sequencer_editors():
|
||||||
|
context = bpy.context
|
||||||
|
|
||||||
|
# Py cant access notifiers
|
||||||
|
for window in context.window_manager.windows:
|
||||||
|
for area in window.screen.areas:
|
||||||
|
if area.type == 'SEQUENCE_EDITOR':
|
||||||
|
for region in area.regions:
|
||||||
|
if region.type == 'WINDOW':
|
||||||
|
region.tag_redraw()
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
cb_handle[:] = bpy.types.SpaceSequenceEditor.draw_handler_add(
|
||||||
|
draw_callback_px, (), 'WINDOW', 'POST_VIEW'),
|
||||||
|
|
||||||
|
tag_redraw_all_sequencer_editors()
|
||||||
|
|
||||||
|
|
||||||
|
def callback_disable():
|
||||||
|
if not cb_handle:
|
||||||
|
return
|
||||||
|
|
||||||
|
bpy.types.SpaceSequenceEditor.draw_handler_remove(cb_handle[0], 'WINDOW')
|
||||||
|
|
||||||
|
tag_redraw_all_sequencer_editors()
|
@ -198,6 +198,12 @@ pillar_semaphore = asyncio.Semaphore(3)
|
|||||||
|
|
||||||
|
|
||||||
async def pillar_call(pillar_func, *args, caching=True, **kwargs):
|
async def pillar_call(pillar_func, *args, caching=True, **kwargs):
|
||||||
|
"""Calls a Pillar function.
|
||||||
|
|
||||||
|
A semaphore is used to ensure that there won't be too many
|
||||||
|
calls to Pillar simultaneously.
|
||||||
|
"""
|
||||||
|
|
||||||
partial = functools.partial(pillar_func, *args, api=pillar_api(caching=caching), **kwargs)
|
partial = functools.partial(pillar_func, *args, api=pillar_api(caching=caching), **kwargs)
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
@ -205,6 +211,12 @@ async def pillar_call(pillar_func, *args, caching=True, **kwargs):
|
|||||||
return await loop.run_in_executor(None, partial)
|
return await loop.run_in_executor(None, partial)
|
||||||
|
|
||||||
|
|
||||||
|
def call(pillar_func, *args, **kwargs):
|
||||||
|
"""Synchronous call to Pillar, ensures the correct Api object is used."""
|
||||||
|
|
||||||
|
return pillar_func(*args, api=pillar_api(), **kwargs)
|
||||||
|
|
||||||
|
|
||||||
async def check_pillar_credentials(required_roles: set):
|
async def check_pillar_credentials(required_roles: set):
|
||||||
"""Tries to obtain the user at Pillar using the user's credentials.
|
"""Tries to obtain the user at Pillar using the user's credentials.
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user