This repository has been archived on 2023-10-03. You can view files and clone it, but cannot push or open issues or pull requests.

340 lines
12 KiB
Python

# ##### 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__)