Compare commits
29 Commits
version-1.
...
version-1.
Author | SHA1 | Date | |
---|---|---|---|
68b046c714 | |||
cb20d6ee03 | |||
![]() |
645bdd950f | ||
![]() |
74a5830dae | ||
da4d4df5fb | |||
9c3098cc0d | |||
98beaf7fb7 | |||
3364371ac6 | |||
2723b07fa2 | |||
c2a037ca89 | |||
d3451d4de3 | |||
5094977614 | |||
![]() |
a11a55be22 | ||
68d2fc8e42 | |||
2de4a8e87c | |||
39b2bacdcc | |||
a1416f99dd | |||
0fa7d60028 | |||
c1b6480f9a | |||
56353d4177 | |||
469a9318af | |||
e265081131 | |||
115eea82c6 | |||
900068a6f5 | |||
c8229500d1 | |||
65ff9da428 | |||
fcba8a2e0f | |||
7ef5e522f8 | |||
ae570e5907 |
@@ -21,15 +21,14 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
'name': 'Blender Cloud',
|
'name': 'Blender Cloud',
|
||||||
"author": "Sybren A. Stüvel, Francesco Siddi, Inês Almeida, Antony Riakiotakis",
|
"author": "Sybren A. Stüvel, Francesco Siddi, Inês Almeida, Antony Riakiotakis",
|
||||||
'version': (1, 4, 999),
|
'version': (1, 5, 2),
|
||||||
'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',
|
||||||
'description': 'Texture library browser and Blender Sync. Requires the Blender ID addon '
|
'description': 'Texture library browser and Blender Sync. Requires the Blender ID addon '
|
||||||
'and Blender 2.77a or newer.',
|
'and Blender 2.77a or newer.',
|
||||||
'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.6/Py/'
|
'wiki_url': 'https://wiki.blender.org/index.php/Extensions:2.6/Py/'
|
||||||
'Scripts/System/BlenderCloud',
|
'Scripts/System/BlenderCloud',
|
||||||
'category': 'System',
|
'category': 'System',
|
||||||
'warning': 'This is a beta version; the first to support Attract.'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
@@ -70,8 +70,12 @@ def active_strip(context):
|
|||||||
|
|
||||||
def selected_shots(context):
|
def selected_shots(context):
|
||||||
"""Generator, yields selected strips if they are Attract shots."""
|
"""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')
|
atc_object_id = getattr(strip, 'atc_object_id')
|
||||||
if not atc_object_id:
|
if not atc_object_id:
|
||||||
continue
|
continue
|
||||||
@@ -81,6 +85,11 @@ def selected_shots(context):
|
|||||||
|
|
||||||
def all_shots(context):
|
def all_shots(context):
|
||||||
"""Generator, yields all strips if they are Attract shots."""
|
"""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:
|
for strip in context.scene.sequence_editor.sequences_all:
|
||||||
atc_object_id = getattr(strip, 'atc_object_id')
|
atc_object_id = getattr(strip, 'atc_object_id')
|
||||||
@@ -127,18 +136,31 @@ def shot_id_use(strips):
|
|||||||
return ids_in_use
|
return ids_in_use
|
||||||
|
|
||||||
|
|
||||||
def compute_strip_conflicts(context):
|
def compute_strip_conflicts(scene):
|
||||||
"""Sets the strip property atc_object_id_conflict for each strip."""
|
"""Sets the strip property atc_object_id_conflict for each strip."""
|
||||||
|
|
||||||
ids_in_use = shot_id_use(context.scene.sequence_editor.sequences_all)
|
if not scene or not scene.sequence_editor or not scene.sequence_editor.sequences_all:
|
||||||
|
return
|
||||||
|
|
||||||
|
tag_redraw = False
|
||||||
|
ids_in_use = shot_id_use(scene.sequence_editor.sequences_all)
|
||||||
for strips in ids_in_use.values():
|
for strips in ids_in_use.values():
|
||||||
is_conflict = len(strips) > 1
|
is_conflict = len(strips) > 1
|
||||||
for strip in strips:
|
for strip in strips:
|
||||||
|
if strip.atc_object_id_conflict != is_conflict:
|
||||||
|
tag_redraw = True
|
||||||
strip.atc_object_id_conflict = is_conflict
|
strip.atc_object_id_conflict = is_conflict
|
||||||
|
|
||||||
|
if tag_redraw:
|
||||||
|
draw.tag_redraw_all_sequencer_editors()
|
||||||
return ids_in_use
|
return ids_in_use
|
||||||
|
|
||||||
|
|
||||||
|
@bpy.app.handlers.persistent
|
||||||
|
def scene_update_post_handler(scene):
|
||||||
|
compute_strip_conflicts(scene)
|
||||||
|
|
||||||
|
|
||||||
class ToolsPanel(Panel):
|
class ToolsPanel(Panel):
|
||||||
bl_label = 'Attract'
|
bl_label = 'Attract'
|
||||||
bl_space_type = 'SEQUENCE_EDITOR'
|
bl_space_type = 'SEQUENCE_EDITOR'
|
||||||
@@ -152,12 +174,12 @@ class ToolsPanel(Panel):
|
|||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
strip = active_strip(context)
|
strip = active_strip(context)
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
strip_types = {'MOVIE', 'IMAGE'}
|
strip_types = {'MOVIE', 'IMAGE', 'META'}
|
||||||
|
|
||||||
selshots = list(selected_shots(context))
|
selshots = list(selected_shots(context))
|
||||||
if strip and strip.type in strip_types and strip.atc_object_id:
|
if strip and strip.type in strip_types and strip.atc_object_id:
|
||||||
if len(selshots) > 1:
|
if len(selshots) > 1:
|
||||||
noun = 'Selected Shots'
|
noun = '%i Shots' % len(selshots)
|
||||||
else:
|
else:
|
||||||
noun = 'This Shot'
|
noun = 'This Shot'
|
||||||
|
|
||||||
@@ -180,26 +202,35 @@ class ToolsPanel(Panel):
|
|||||||
sub = layout.column(align=True)
|
sub = layout.column(align=True)
|
||||||
row = sub.row(align=True)
|
row = sub.row(align=True)
|
||||||
if bpy.ops.attract.submit_selected.poll():
|
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:
|
else:
|
||||||
row.operator(ATTRACT_OT_submit_all.bl_idname)
|
row.operator(ATTRACT_OT_submit_all.bl_idname)
|
||||||
row.operator(AttractShotFetchUpdate.bl_idname,
|
row.operator(AttractShotFetchUpdate.bl_idname,
|
||||||
text='', icon='FILE_REFRESH')
|
text='', icon='FILE_REFRESH')
|
||||||
row.operator(ATTRACT_OT_shot_open_in_browser.bl_idname,
|
row.operator(ATTRACT_OT_shot_open_in_browser.bl_idname,
|
||||||
text='', icon='WORLD')
|
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,
|
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.
|
# Group more dangerous operations.
|
||||||
dangerous_sub = layout.column(align=True)
|
dangerous_sub = layout.split(0.6, align=True)
|
||||||
dangerous_sub.operator(AttractShotDelete.bl_idname)
|
dangerous_sub.operator('attract.strip_unlink',
|
||||||
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:
|
elif context.selected_sequences:
|
||||||
if len(context.selected_sequences) > 1:
|
if len(context.selected_sequences) > 1:
|
||||||
noun = 'selected strips'
|
noun = 'Selected Strips'
|
||||||
else:
|
else:
|
||||||
noun = 'this strip'
|
noun = 'This Strip'
|
||||||
layout.operator(AttractShotSubmitSelected.bl_idname,
|
layout.operator(AttractShotSubmitSelected.bl_idname,
|
||||||
text='Submit %s as New Shot' % noun)
|
text='Submit %s as New Shot' % noun)
|
||||||
layout.operator('attract.shot_relink')
|
layout.operator('attract.shot_relink')
|
||||||
@@ -259,6 +290,7 @@ class AttractOperatorMixin:
|
|||||||
'notes': '',
|
'notes': '',
|
||||||
'used_in_edit': True,
|
'used_in_edit': True,
|
||||||
'trim_start_in_frames': strip.frame_offset_start,
|
'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,
|
'duration_in_edit_in_frames': strip.frame_final_duration,
|
||||||
'cut_in_timeline_in_frames': strip.frame_final_start},
|
'cut_in_timeline_in_frames': strip.frame_final_start},
|
||||||
'order': 0,
|
'order': 0,
|
||||||
@@ -282,7 +314,6 @@ class AttractOperatorMixin:
|
|||||||
strip.atc_notes = node['properties']['notes']
|
strip.atc_notes = node['properties']['notes']
|
||||||
strip.atc_status = node['properties']['status']
|
strip.atc_status = node['properties']['status']
|
||||||
|
|
||||||
compute_strip_conflicts(bpy.context)
|
|
||||||
draw.tag_redraw_all_sequencer_editors()
|
draw.tag_redraw_all_sequencer_editors()
|
||||||
|
|
||||||
def submit_update(self, strip):
|
def submit_update(self, strip):
|
||||||
@@ -294,6 +325,7 @@ class AttractOperatorMixin:
|
|||||||
'$set': {
|
'$set': {
|
||||||
'name': strip.atc_name,
|
'name': strip.atc_name,
|
||||||
'properties.trim_start_in_frames': strip.frame_offset_start,
|
'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.duration_in_edit_in_frames': strip.frame_final_duration,
|
||||||
'properties.cut_in_timeline_in_frames': strip.frame_final_start,
|
'properties.cut_in_timeline_in_frames': strip.frame_final_start,
|
||||||
'properties.status': strip.atc_status,
|
'properties.status': strip.atc_status,
|
||||||
@@ -308,6 +340,11 @@ class AttractOperatorMixin:
|
|||||||
def relink(self, strip, atc_object_id, *, refresh=False):
|
def relink(self, strip, atc_object_id, *, refresh=False):
|
||||||
from .. import pillar
|
from .. import pillar
|
||||||
|
|
||||||
|
# The node may have been deleted, so we need to send a 'relink' before we try
|
||||||
|
# to fetch the node itself.
|
||||||
|
node = Node({'_id': atc_object_id})
|
||||||
|
pillar.sync_call(node.patch, {'op': 'relink'})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
node = pillar.sync_call(Node.find, atc_object_id, caching=False)
|
node = pillar.sync_call(Node.find, atc_object_id, caching=False)
|
||||||
except (sdk_exceptions.ResourceNotFound, sdk_exceptions.MethodNotAllowed):
|
except (sdk_exceptions.ResourceNotFound, sdk_exceptions.MethodNotAllowed):
|
||||||
@@ -317,7 +354,6 @@ class AttractOperatorMixin:
|
|||||||
strip.atc_is_synced = False
|
strip.atc_is_synced = False
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
pillar.sync_call(node.patch, {'op': 'relink'})
|
|
||||||
strip.atc_is_synced = True
|
strip.atc_is_synced = True
|
||||||
if not refresh:
|
if not refresh:
|
||||||
strip.atc_name = node.name
|
strip.atc_name = node.name
|
||||||
@@ -327,8 +363,6 @@ class AttractOperatorMixin:
|
|||||||
strip.atc_status = node.properties.status
|
strip.atc_status = node.properties.status
|
||||||
strip.atc_notes = node.properties.notes or ''
|
strip.atc_notes = node.properties.notes or ''
|
||||||
strip.atc_description = node.description or ''
|
strip.atc_description = node.description or ''
|
||||||
|
|
||||||
compute_strip_conflicts(bpy.context)
|
|
||||||
draw.tag_redraw_all_sequencer_editors()
|
draw.tag_redraw_all_sequencer_editors()
|
||||||
|
|
||||||
|
|
||||||
@@ -397,6 +431,10 @@ class ATTRACT_OT_shot_open_in_browser(AttractOperatorMixin, Operator):
|
|||||||
bl_label = 'Open in Browser'
|
bl_label = 'Open in Browser'
|
||||||
bl_description = 'Opens a webbrowser to show the shot on Attract'
|
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):
|
def execute(self, context):
|
||||||
from ..blender import PILLAR_WEB_SERVER_URL
|
from ..blender import PILLAR_WEB_SERVER_URL
|
||||||
import webbrowser
|
import webbrowser
|
||||||
@@ -419,6 +457,10 @@ class AttractShotDelete(AttractOperatorMixin, Operator):
|
|||||||
|
|
||||||
confirm = bpy.props.BoolProperty(name='confirm')
|
confirm = bpy.props.BoolProperty(name='confirm')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return bool(context.selected_sequences)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
from .. import pillar
|
from .. import pillar
|
||||||
|
|
||||||
@@ -426,23 +468,40 @@ class AttractShotDelete(AttractOperatorMixin, Operator):
|
|||||||
self.report({'WARNING'}, 'Delete aborted.')
|
self.report({'WARNING'}, 'Delete aborted.')
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
strip = active_strip(context)
|
removed = kept = 0
|
||||||
node = pillar.sync_call(Node.find, strip.atc_object_id)
|
for strip in selected_shots(context):
|
||||||
if not pillar.sync_call(node.delete):
|
node = pillar.sync_call(Node.find, strip.atc_object_id)
|
||||||
print('Unable to delete the strip node on Attract.')
|
if not pillar.sync_call(node.delete):
|
||||||
return {'CANCELLED'}
|
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()
|
draw.tag_redraw_all_sequencer_editors()
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
|
self.confirm = False
|
||||||
return context.window_manager.invoke_props_dialog(self)
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
col = layout.column()
|
col = layout.column()
|
||||||
col.prop(self, 'confirm', text="I hereby confirm I want to 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):
|
class AttractStripUnlink(AttractOperatorMixin, Operator):
|
||||||
@@ -450,6 +509,10 @@ class AttractStripUnlink(AttractOperatorMixin, Operator):
|
|||||||
bl_label = 'Unlink Shot From This Strip'
|
bl_label = 'Unlink Shot From This Strip'
|
||||||
bl_description = 'Remove Attract props from the selected strip(s)'
|
bl_description = 'Remove Attract props from the selected strip(s)'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return bool(context.selected_sequences)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
unlinked_ids = set()
|
unlinked_ids = set()
|
||||||
|
|
||||||
@@ -464,7 +527,7 @@ class AttractStripUnlink(AttractOperatorMixin, Operator):
|
|||||||
# For all Object IDs that are no longer in use in the edit, let Attract know.
|
# For all Object IDs that are no longer in use in the edit, let Attract know.
|
||||||
# This should be done with care, as the shot could have been attached to multiple
|
# This should be done with care, as the shot could have been attached to multiple
|
||||||
# strips.
|
# strips.
|
||||||
id_to_shots = compute_strip_conflicts(context)
|
id_to_shots = compute_strip_conflicts(context.scene)
|
||||||
for oid in unlinked_ids:
|
for oid in unlinked_ids:
|
||||||
if len(id_to_shots[oid]):
|
if len(id_to_shots[oid]):
|
||||||
# Still in use
|
# Still in use
|
||||||
@@ -472,7 +535,13 @@ class AttractStripUnlink(AttractOperatorMixin, Operator):
|
|||||||
|
|
||||||
node = Node({'_id': oid})
|
node = Node({'_id': oid})
|
||||||
pillar.sync_call(node.patch, {'op': 'unlink'})
|
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()
|
draw.tag_redraw_all_sequencer_editors()
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
@@ -639,9 +708,13 @@ class ATTRACT_OT_make_shot_thumbnail(AttractOperatorMixin,
|
|||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def temporary_current_frame(self, context):
|
def temporary_current_frame(self, context):
|
||||||
|
"""Allows the context to set the scene current frame, restores it on exit.
|
||||||
|
|
||||||
|
Yields the initial current frame, so it can be used for reference in the context.
|
||||||
|
"""
|
||||||
current_frame = context.scene.frame_current
|
current_frame = context.scene.frame_current
|
||||||
try:
|
try:
|
||||||
yield
|
yield current_frame
|
||||||
finally:
|
finally:
|
||||||
context.scene.frame_current = current_frame
|
context.scene.frame_current = current_frame
|
||||||
|
|
||||||
@@ -649,19 +722,25 @@ class ATTRACT_OT_make_shot_thumbnail(AttractOperatorMixin,
|
|||||||
nr_of_strips = len(context.selected_sequences)
|
nr_of_strips = len(context.selected_sequences)
|
||||||
do_multishot = nr_of_strips > 1
|
do_multishot = nr_of_strips > 1
|
||||||
|
|
||||||
with self.temporary_current_frame(context):
|
with self.temporary_current_frame(context) as original_curframe:
|
||||||
|
# The multishot and singleshot branches do pretty much the same thing,
|
||||||
|
# but report differently to the user.
|
||||||
if do_multishot:
|
if do_multishot:
|
||||||
context.window_manager.progress_begin(0, nr_of_strips)
|
context.window_manager.progress_begin(0, nr_of_strips)
|
||||||
try:
|
try:
|
||||||
self.report({'INFO'}, 'Rendering thumbnails for %i selected shots.' %
|
self.report({'INFO'}, 'Rendering thumbnails for %i selected shots.' %
|
||||||
nr_of_strips)
|
nr_of_strips)
|
||||||
|
|
||||||
for idx, strip in enumerate(context.selected_sequences):
|
strips = sorted(context.selected_sequences, key=self.by_frame)
|
||||||
|
for idx, strip in enumerate(strips):
|
||||||
context.window_manager.progress_update(idx)
|
context.window_manager.progress_update(idx)
|
||||||
# For multi-shot we can't just use the current frame (each thumb would be
|
|
||||||
# identical), so instead we use the middle frame. The first/last frames
|
# Pick the middle frame, except for the strip the original current frame
|
||||||
# cannot be reliably used due to transitions with other shots.
|
# marker was over.
|
||||||
self.set_middle_frame(context, strip)
|
if not self.strip_contains(strip, original_curframe):
|
||||||
|
self.set_middle_frame(context, strip)
|
||||||
|
else:
|
||||||
|
context.scene.frame_set(original_curframe)
|
||||||
await self.thumbnail_strip(context, strip)
|
await self.thumbnail_strip(context, strip)
|
||||||
|
|
||||||
if self._state == 'QUIT':
|
if self._state == 'QUIT':
|
||||||
@@ -672,7 +751,7 @@ class ATTRACT_OT_make_shot_thumbnail(AttractOperatorMixin,
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
strip = active_strip(context)
|
strip = active_strip(context)
|
||||||
if not strip.frame_final_start <= context.scene.frame_current <= strip.frame_final_end:
|
if not self.strip_contains(strip, original_curframe):
|
||||||
self.report({'WARNING'}, 'Rendering middle frame as thumbnail for active shot.')
|
self.report({'WARNING'}, 'Rendering middle frame as thumbnail for active shot.')
|
||||||
self.set_middle_frame(context, strip)
|
self.set_middle_frame(context, strip)
|
||||||
else:
|
else:
|
||||||
@@ -692,12 +771,27 @@ class ATTRACT_OT_make_shot_thumbnail(AttractOperatorMixin,
|
|||||||
self.report({'INFO'}, 'Thumbnail uploaded to Attract')
|
self.report({'INFO'}, 'Thumbnail uploaded to Attract')
|
||||||
self.quit()
|
self.quit()
|
||||||
|
|
||||||
def set_middle_frame(self, context, strip):
|
@staticmethod
|
||||||
|
def strip_contains(strip, framenr: int) -> bool:
|
||||||
|
"""Returns True iff the strip covers the given frame number"""
|
||||||
|
return strip.frame_final_start <= framenr <= strip.frame_final_end
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def set_middle_frame(context, strip):
|
||||||
"""Sets the current frame to the middle frame of the strip."""
|
"""Sets the current frame to the middle frame of the strip."""
|
||||||
|
|
||||||
middle = round((strip.frame_final_start + strip.frame_final_end) / 2)
|
middle = round((strip.frame_final_start + strip.frame_final_end) / 2)
|
||||||
context.scene.frame_set(middle)
|
context.scene.frame_set(middle)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def by_frame(sequence_strip) -> int:
|
||||||
|
"""Returns the start frame number of the sequence strip.
|
||||||
|
|
||||||
|
This can be used for sorting strips by time.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return sequence_strip.frame_final_start
|
||||||
|
|
||||||
async def thumbnail_strip(self, context, strip):
|
async def thumbnail_strip(self, context, strip):
|
||||||
atc_object_id = getattr(strip, 'atc_object_id', None)
|
atc_object_id = getattr(strip, 'atc_object_id', None)
|
||||||
if not atc_object_id:
|
if not atc_object_id:
|
||||||
@@ -773,6 +867,23 @@ class ATTRACT_OT_make_shot_thumbnail(AttractOperatorMixin,
|
|||||||
return file_id
|
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):
|
def draw_strip_movie_meta(self, context):
|
||||||
strip = active_strip(context)
|
strip = active_strip(context)
|
||||||
if not strip:
|
if not strip:
|
||||||
@@ -829,11 +940,15 @@ def register():
|
|||||||
bpy.utils.register_class(ATTRACT_OT_open_meta_blendfile)
|
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_shot_open_in_browser)
|
||||||
bpy.utils.register_class(ATTRACT_OT_make_shot_thumbnail)
|
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()
|
draw.callback_enable()
|
||||||
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
draw.callback_disable()
|
draw.callback_disable()
|
||||||
|
bpy.app.handlers.scene_update_post.remove(scene_update_post_handler)
|
||||||
bpy.utils.unregister_module(__name__)
|
bpy.utils.unregister_module(__name__)
|
||||||
del bpy.types.Sequence.atc_is_synced
|
del bpy.types.Sequence.atc_is_synced
|
||||||
del bpy.types.Sequence.atc_object_id
|
del bpy.types.Sequence.atc_object_id
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
#
|
#
|
||||||
# ##### END GPL LICENSE BLOCK #####
|
# ##### END GPL LICENSE BLOCK #####
|
||||||
|
|
||||||
# <pep8-80 compliant>
|
# <pep8 compliant>
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import logging
|
import logging
|
||||||
@@ -87,8 +87,7 @@ def draw_strip_conflict(strip_coords, pixel_size_x):
|
|||||||
|
|
||||||
# Always draw the full rectangle, the conflict should be resolved and thus stand out.
|
# Always draw the full rectangle, the conflict should be resolved and thus stand out.
|
||||||
bgl.glColor3f(*CONFLICT_COLOUR)
|
bgl.glColor3f(*CONFLICT_COLOUR)
|
||||||
bgl.glLineWidth(3)
|
bgl.glLineWidth(2)
|
||||||
bgl.glPointSize(10)
|
|
||||||
|
|
||||||
bgl.glBegin(bgl.GL_LINE_LOOP)
|
bgl.glBegin(bgl.GL_LINE_LOOP)
|
||||||
bgl.glVertex2f(s_x1, s_y1)
|
bgl.glVertex2f(s_x1, s_y1)
|
||||||
@@ -97,13 +96,6 @@ def draw_strip_conflict(strip_coords, pixel_size_x):
|
|||||||
bgl.glVertex2f(s_x1, s_y2)
|
bgl.glVertex2f(s_x1, s_y2)
|
||||||
bgl.glEnd()
|
bgl.glEnd()
|
||||||
|
|
||||||
bgl.glBegin(bgl.GL_POINTS)
|
|
||||||
bgl.glVertex2f(s_x1, s_y1)
|
|
||||||
bgl.glVertex2f(s_x2, s_y1)
|
|
||||||
bgl.glVertex2f(s_x2, s_y2)
|
|
||||||
bgl.glVertex2f(s_x1, s_y2)
|
|
||||||
bgl.glEnd()
|
|
||||||
|
|
||||||
bgl.glPopAttrib()
|
bgl.glPopAttrib()
|
||||||
|
|
||||||
|
|
||||||
|
@@ -285,12 +285,10 @@ class BlenderCloudPreferences(AddonPreferences):
|
|||||||
# Image Share stuff
|
# Image Share stuff
|
||||||
share_box = layout.box()
|
share_box = layout.box()
|
||||||
share_box.label('Image Sharing on Blender Cloud', icon_value=icon('CLOUD'))
|
share_box.label('Image Sharing on Blender Cloud', icon_value=icon('CLOUD'))
|
||||||
share_box.enabled = msg_icon != 'ERROR'
|
|
||||||
share_box.prop(self, 'open_browser_after_share')
|
share_box.prop(self, 'open_browser_after_share')
|
||||||
|
|
||||||
# Attract stuff
|
# Attract stuff
|
||||||
attract_box = layout.box()
|
attract_box = layout.box()
|
||||||
attract_box.enabled = msg_icon != 'ERROR'
|
|
||||||
self.draw_attract_buttons(attract_box, self.attract_project)
|
self.draw_attract_buttons(attract_box, self.attract_project)
|
||||||
|
|
||||||
def draw_subscribe_button(self, layout):
|
def draw_subscribe_button(self, layout):
|
||||||
|
@@ -553,6 +553,7 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
|
|||||||
"""Draws the GUI with OpenGL."""
|
"""Draws the GUI with OpenGL."""
|
||||||
|
|
||||||
drawers = {
|
drawers = {
|
||||||
|
'INITIALIZING': self._draw_initializing,
|
||||||
'CHECKING_CREDENTIALS': self._draw_checking_credentials,
|
'CHECKING_CREDENTIALS': self._draw_checking_credentials,
|
||||||
'BROWSING': self._draw_browser,
|
'BROWSING': self._draw_browser,
|
||||||
'DOWNLOADING_TEXTURE': self._draw_downloading,
|
'DOWNLOADING_TEXTURE': self._draw_downloading,
|
||||||
@@ -653,6 +654,13 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
|
|||||||
'Checking login credentials',
|
'Checking login credentials',
|
||||||
(0.0, 0.0, 0.2, 0.6))
|
(0.0, 0.0, 0.2, 0.6))
|
||||||
|
|
||||||
|
def _draw_initializing(self, context):
|
||||||
|
"""OpenGL drawing code for the INITIALIZING state."""
|
||||||
|
|
||||||
|
self._draw_text_on_colour(context,
|
||||||
|
'Initializing',
|
||||||
|
(0.0, 0.0, 0.2, 0.6))
|
||||||
|
|
||||||
def _draw_text_on_colour(self, context, text, bgcolour):
|
def _draw_text_on_colour(self, context, text, bgcolour):
|
||||||
content_height, content_width = self._window_size(context)
|
content_height, content_width = self._window_size(context)
|
||||||
bgl.glEnable(bgl.GL_BLEND)
|
bgl.glEnable(bgl.GL_BLEND)
|
||||||
|
33
setup.py
33
setup.py
@@ -18,11 +18,13 @@
|
|||||||
# ##### END GPL LICENSE BLOCK #####
|
# ##### END GPL LICENSE BLOCK #####
|
||||||
|
|
||||||
import glob
|
import glob
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import re
|
import re
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import zipfile
|
||||||
|
|
||||||
from distutils import log
|
from distutils import log
|
||||||
from distutils.core import Command
|
from distutils.core import Command
|
||||||
@@ -168,6 +170,34 @@ class BlenderAddonBdist(bdist):
|
|||||||
super().run()
|
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
|
# noinspection PyAttributeOutsideInit
|
||||||
class BlenderAddonInstall(install):
|
class BlenderAddonInstall(install):
|
||||||
"""Ensures the module is placed at the root of the zip file."""
|
"""Ensures the module is placed at the root of the zip file."""
|
||||||
@@ -191,12 +221,13 @@ class AvoidEggInfo(install_egg_info):
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
cmdclass={'bdist': BlenderAddonBdist,
|
cmdclass={'bdist': BlenderAddonBdist,
|
||||||
|
'fdist': BlenderAddonFdist,
|
||||||
'install': BlenderAddonInstall,
|
'install': BlenderAddonInstall,
|
||||||
'install_egg_info': AvoidEggInfo,
|
'install_egg_info': AvoidEggInfo,
|
||||||
'wheels': BuildWheels},
|
'wheels': BuildWheels},
|
||||||
name='blender_cloud',
|
name='blender_cloud',
|
||||||
description='The Blender Cloud addon allows browsing the Blender Cloud from Blender.',
|
description='The Blender Cloud addon allows browsing the Blender Cloud from Blender.',
|
||||||
version='1.4.999',
|
version='1.5.2',
|
||||||
author='Sybren A. Stüvel',
|
author='Sybren A. Stüvel',
|
||||||
author_email='sybren@stuvel.eu',
|
author_email='sybren@stuvel.eu',
|
||||||
packages=find_packages('.'),
|
packages=find_packages('.'),
|
||||||
|
15
update_version.sh
Executable file
15
update_version.sh
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Usage: $0 new-version" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
BL_INFO_VER=$(echo "$1" | sed 's/\./, /g')
|
||||||
|
|
||||||
|
sed "s/version='[^']*'/version='$1'/" -i setup.py
|
||||||
|
sed "s/'version': ([^)]*)/'version': ($BL_INFO_VER)/" -i blender_cloud/__init__.py
|
||||||
|
|
||||||
|
git diff
|
||||||
|
echo
|
||||||
|
echo "Don't forget to commit!"
|
Reference in New Issue
Block a user