Compare commits
13 Commits
version-1.
...
version-1.
Author | SHA1 | Date | |
---|---|---|---|
68b046c714 | |||
cb20d6ee03 | |||
![]() |
645bdd950f | ||
![]() |
74a5830dae | ||
da4d4df5fb | |||
9c3098cc0d | |||
98beaf7fb7 | |||
3364371ac6 | |||
2723b07fa2 | |||
c2a037ca89 | |||
d3451d4de3 | |||
5094977614 | |||
![]() |
a11a55be22 |
@@ -21,7 +21,7 @@
|
||||
bl_info = {
|
||||
'name': 'Blender Cloud',
|
||||
"author": "Sybren A. Stüvel, Francesco Siddi, Inês Almeida, Antony Riakiotakis",
|
||||
'version': (1, 5, 0),
|
||||
'version': (1, 5, 2),
|
||||
'blender': (2, 77, 0),
|
||||
'location': 'Addon Preferences panel, and Ctrl+Shift+Alt+A anywhere for texture browser',
|
||||
'description': 'Texture library browser and Blender Sync. Requires the Blender ID addon '
|
||||
|
@@ -70,8 +70,12 @@ def active_strip(context):
|
||||
|
||||
def selected_shots(context):
|
||||
"""Generator, yields selected strips if they are Attract shots."""
|
||||
selected_sequences = context.selected_sequences
|
||||
|
||||
for strip in context.selected_sequences:
|
||||
if selected_sequences is None:
|
||||
return
|
||||
|
||||
for strip in selected_sequences:
|
||||
atc_object_id = getattr(strip, 'atc_object_id')
|
||||
if not atc_object_id:
|
||||
continue
|
||||
@@ -81,6 +85,11 @@ def selected_shots(context):
|
||||
|
||||
def all_shots(context):
|
||||
"""Generator, yields all strips if they are Attract shots."""
|
||||
sequence_editor = context.scene.sequence_editor
|
||||
|
||||
if sequence_editor is None:
|
||||
# we should throw an exception, but at least this change prevents an error
|
||||
return []
|
||||
|
||||
for strip in context.scene.sequence_editor.sequences_all:
|
||||
atc_object_id = getattr(strip, 'atc_object_id')
|
||||
@@ -170,7 +179,7 @@ class ToolsPanel(Panel):
|
||||
selshots = list(selected_shots(context))
|
||||
if strip and strip.type in strip_types and strip.atc_object_id:
|
||||
if len(selshots) > 1:
|
||||
noun = 'Selected Shots'
|
||||
noun = '%i Shots' % len(selshots)
|
||||
else:
|
||||
noun = 'This Shot'
|
||||
|
||||
@@ -193,20 +202,29 @@ class ToolsPanel(Panel):
|
||||
sub = layout.column(align=True)
|
||||
row = sub.row(align=True)
|
||||
if bpy.ops.attract.submit_selected.poll():
|
||||
row.operator('attract.submit_selected', text='Submit %s' % noun)
|
||||
row.operator('attract.submit_selected',
|
||||
text='Submit %s' % noun,
|
||||
icon='TRIA_UP')
|
||||
else:
|
||||
row.operator(ATTRACT_OT_submit_all.bl_idname)
|
||||
row.operator(AttractShotFetchUpdate.bl_idname,
|
||||
text='', icon='FILE_REFRESH')
|
||||
row.operator(ATTRACT_OT_shot_open_in_browser.bl_idname,
|
||||
text='', icon='WORLD')
|
||||
row.operator(ATTRACT_OT_copy_id_to_clipboard.bl_idname,
|
||||
text='', icon='COPYDOWN')
|
||||
sub.operator(ATTRACT_OT_make_shot_thumbnail.bl_idname,
|
||||
text='Render Thumbnail for %s' % noun)
|
||||
text='Render Thumbnail for %s' % noun,
|
||||
icon='RENDER_STILL')
|
||||
|
||||
# Group more dangerous operations.
|
||||
dangerous_sub = layout.column(align=True)
|
||||
dangerous_sub.operator(AttractShotDelete.bl_idname)
|
||||
dangerous_sub.operator('attract.strip_unlink')
|
||||
dangerous_sub = layout.split(0.6, align=True)
|
||||
dangerous_sub.operator('attract.strip_unlink',
|
||||
text='Unlink %s' % noun,
|
||||
icon='PANEL_CLOSE')
|
||||
dangerous_sub.operator(AttractShotDelete.bl_idname,
|
||||
text='Delete %s' % noun,
|
||||
icon='CANCEL')
|
||||
|
||||
elif context.selected_sequences:
|
||||
if len(context.selected_sequences) > 1:
|
||||
@@ -272,6 +290,7 @@ class AttractOperatorMixin:
|
||||
'notes': '',
|
||||
'used_in_edit': True,
|
||||
'trim_start_in_frames': strip.frame_offset_start,
|
||||
'trim_end_in_frames': strip.frame_offset_end,
|
||||
'duration_in_edit_in_frames': strip.frame_final_duration,
|
||||
'cut_in_timeline_in_frames': strip.frame_final_start},
|
||||
'order': 0,
|
||||
@@ -306,6 +325,7 @@ class AttractOperatorMixin:
|
||||
'$set': {
|
||||
'name': strip.atc_name,
|
||||
'properties.trim_start_in_frames': strip.frame_offset_start,
|
||||
'properties.trim_end_in_frames': strip.frame_offset_end,
|
||||
'properties.duration_in_edit_in_frames': strip.frame_final_duration,
|
||||
'properties.cut_in_timeline_in_frames': strip.frame_final_start,
|
||||
'properties.status': strip.atc_status,
|
||||
@@ -411,6 +431,10 @@ class ATTRACT_OT_shot_open_in_browser(AttractOperatorMixin, Operator):
|
||||
bl_label = 'Open in Browser'
|
||||
bl_description = 'Opens a webbrowser to show the shot on Attract'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bool(context.selected_sequences and active_strip(context))
|
||||
|
||||
def execute(self, context):
|
||||
from ..blender import PILLAR_WEB_SERVER_URL
|
||||
import webbrowser
|
||||
@@ -433,6 +457,10 @@ class AttractShotDelete(AttractOperatorMixin, Operator):
|
||||
|
||||
confirm = bpy.props.BoolProperty(name='confirm')
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bool(context.selected_sequences)
|
||||
|
||||
def execute(self, context):
|
||||
from .. import pillar
|
||||
|
||||
@@ -440,13 +468,22 @@ class AttractShotDelete(AttractOperatorMixin, Operator):
|
||||
self.report({'WARNING'}, 'Delete aborted.')
|
||||
return {'CANCELLED'}
|
||||
|
||||
strip = active_strip(context)
|
||||
node = pillar.sync_call(Node.find, strip.atc_object_id)
|
||||
if not pillar.sync_call(node.delete):
|
||||
print('Unable to delete the strip node on Attract.')
|
||||
return {'CANCELLED'}
|
||||
removed = kept = 0
|
||||
for strip in selected_shots(context):
|
||||
node = pillar.sync_call(Node.find, strip.atc_object_id)
|
||||
if not pillar.sync_call(node.delete):
|
||||
self.report({'ERROR'}, 'Unable to delete shot %s on Attract.' % strip.atc_name)
|
||||
kept += 1
|
||||
continue
|
||||
remove_atc_props(strip)
|
||||
removed += 1
|
||||
|
||||
if kept:
|
||||
self.report({'ERROR'}, 'Removed %i shots, but was unable to remove %i' %
|
||||
(removed, kept))
|
||||
else:
|
||||
self.report({'INFO'}, 'Removed all %i shots from Attract' % removed)
|
||||
|
||||
remove_atc_props(strip)
|
||||
draw.tag_redraw_all_sequencer_editors()
|
||||
return {'FINISHED'}
|
||||
|
||||
@@ -457,7 +494,14 @@ class AttractShotDelete(AttractOperatorMixin, Operator):
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
col = layout.column()
|
||||
col.prop(self, 'confirm', text="I hereby confirm: delete this shot from The Edit.")
|
||||
|
||||
selshots = list(selected_shots(context))
|
||||
if len(selshots) > 1:
|
||||
noun = '%i shots' % len(selshots)
|
||||
else:
|
||||
noun = 'this shot'
|
||||
|
||||
col.prop(self, 'confirm', text="I hereby confirm: delete %s from The Edit." % noun)
|
||||
|
||||
|
||||
class AttractStripUnlink(AttractOperatorMixin, Operator):
|
||||
@@ -465,6 +509,10 @@ class AttractStripUnlink(AttractOperatorMixin, Operator):
|
||||
bl_label = 'Unlink Shot From This Strip'
|
||||
bl_description = 'Remove Attract props from the selected strip(s)'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bool(context.selected_sequences)
|
||||
|
||||
def execute(self, context):
|
||||
unlinked_ids = set()
|
||||
|
||||
@@ -487,7 +535,13 @@ class AttractStripUnlink(AttractOperatorMixin, Operator):
|
||||
|
||||
node = Node({'_id': oid})
|
||||
pillar.sync_call(node.patch, {'op': 'unlink'})
|
||||
self.report({'INFO'}, 'Shot %s has been marked as Unused.' % oid)
|
||||
|
||||
if len(unlinked_ids) == 1:
|
||||
shot_id = unlinked_ids.pop()
|
||||
context.window_manager.clipboard = shot_id
|
||||
self.report({'INFO'}, 'Copied unlinked shot ID %s to clipboard' % shot_id)
|
||||
else:
|
||||
self.report({'INFO'}, '%i shots have been marked as Unused.' % len(unlinked_ids))
|
||||
|
||||
draw.tag_redraw_all_sequencer_editors()
|
||||
return {'FINISHED'}
|
||||
@@ -813,6 +867,23 @@ class ATTRACT_OT_make_shot_thumbnail(AttractOperatorMixin,
|
||||
return file_id
|
||||
|
||||
|
||||
class ATTRACT_OT_copy_id_to_clipboard(AttractOperatorMixin, Operator):
|
||||
bl_idname = 'attract.copy_id_to_clipboard'
|
||||
bl_label = 'Copy shot ID to clipboard'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bool(context.selected_sequences and active_strip(context))
|
||||
|
||||
def execute(self, context):
|
||||
strip = active_strip(context)
|
||||
|
||||
context.window_manager.clipboard = strip.atc_object_id
|
||||
self.report({'INFO'}, 'Shot ID %s copied to clipboard' % strip.atc_object_id)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def draw_strip_movie_meta(self, context):
|
||||
strip = active_strip(context)
|
||||
if not strip:
|
||||
@@ -869,6 +940,7 @@ def register():
|
||||
bpy.utils.register_class(ATTRACT_OT_open_meta_blendfile)
|
||||
bpy.utils.register_class(ATTRACT_OT_shot_open_in_browser)
|
||||
bpy.utils.register_class(ATTRACT_OT_make_shot_thumbnail)
|
||||
bpy.utils.register_class(ATTRACT_OT_copy_id_to_clipboard)
|
||||
|
||||
bpy.app.handlers.scene_update_post.append(scene_update_post_handler)
|
||||
draw.callback_enable()
|
||||
|
33
setup.py
33
setup.py
@@ -18,11 +18,13 @@
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import subprocess
|
||||
import re
|
||||
import pathlib
|
||||
import zipfile
|
||||
|
||||
from distutils import log
|
||||
from distutils.core import Command
|
||||
@@ -168,6 +170,34 @@ class BlenderAddonBdist(bdist):
|
||||
super().run()
|
||||
|
||||
|
||||
# noinspection PyAttributeOutsideInit
|
||||
class BlenderAddonFdist(BlenderAddonBdist):
|
||||
"""Ensures that 'python setup.py fdist' creates a plain folder structure."""
|
||||
|
||||
user_options = [
|
||||
('dest-path=', None, 'addon installation path'),
|
||||
]
|
||||
|
||||
def initialize_options(self):
|
||||
super().initialize_options()
|
||||
self.dest_path = None # path that will contain the addon
|
||||
|
||||
def run(self):
|
||||
super().run()
|
||||
|
||||
# dist_files is a list of tuples ('bdist', 'any', 'filepath')
|
||||
filepath = self.distribution.dist_files[0][2]
|
||||
|
||||
# if dest_path is not specified use the filename as the dest_path (minus the .zip)
|
||||
assert filepath.endswith('.zip')
|
||||
target_folder = self.dest_path or filepath[:-4]
|
||||
|
||||
print('Unzipping the package on {}.'.format(target_folder))
|
||||
|
||||
with zipfile.ZipFile(filepath, 'r') as zip_ref:
|
||||
zip_ref.extractall(target_folder)
|
||||
|
||||
|
||||
# noinspection PyAttributeOutsideInit
|
||||
class BlenderAddonInstall(install):
|
||||
"""Ensures the module is placed at the root of the zip file."""
|
||||
@@ -191,12 +221,13 @@ class AvoidEggInfo(install_egg_info):
|
||||
|
||||
setup(
|
||||
cmdclass={'bdist': BlenderAddonBdist,
|
||||
'fdist': BlenderAddonFdist,
|
||||
'install': BlenderAddonInstall,
|
||||
'install_egg_info': AvoidEggInfo,
|
||||
'wheels': BuildWheels},
|
||||
name='blender_cloud',
|
||||
description='The Blender Cloud addon allows browsing the Blender Cloud from Blender.',
|
||||
version='1.5.0',
|
||||
version='1.5.2',
|
||||
author='Sybren A. Stüvel',
|
||||
author_email='sybren@stuvel.eu',
|
||||
packages=find_packages('.'),
|
||||
|
Reference in New Issue
Block a user