Compare commits
7 Commits
version-1.
...
version-1.
Author | SHA1 | Date | |
---|---|---|---|
6fefe4ffd8 | |||
62c1c966f6 | |||
57aadc1817 | |||
7204d4a24c | |||
641b51496a | |||
0562d57513 | |||
ac19e48895 |
11
CHANGELOG.md
11
CHANGELOG.md
@@ -1,5 +1,16 @@
|
||||
# Blender Cloud changelog
|
||||
|
||||
## Version 1.9.3 (2018-10-30)
|
||||
|
||||
- Fix drawing of Attract strips in the VSE on Blender 2.8.
|
||||
|
||||
|
||||
## Version 1.9.2 (2018-09-17)
|
||||
|
||||
- No changes, just a different filename to force a refresh on our
|
||||
hosting platform.
|
||||
|
||||
|
||||
## Version 1.9.1 (2018-09-17)
|
||||
|
||||
- Fix issue with Python 3.7, which is used by current daily builds of Blender.
|
||||
|
@@ -21,7 +21,7 @@
|
||||
bl_info = {
|
||||
'name': 'Blender Cloud',
|
||||
"author": "Sybren A. Stüvel, Francesco Siddi, Inês Almeida, Antony Riakiotakis",
|
||||
'version': (1, 9, 2),
|
||||
'version': (1, 9, 3),
|
||||
'blender': (2, 80, 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 '
|
||||
|
@@ -174,10 +174,11 @@ class AttractPollMixin:
|
||||
return attract_is_active
|
||||
|
||||
|
||||
class AttractToolsPanel(AttractPollMixin, Panel):
|
||||
class ATTRACT_PT_tools(AttractPollMixin, Panel):
|
||||
bl_label = 'Attract'
|
||||
bl_space_type = 'SEQUENCE_EDITOR'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = 'Strip'
|
||||
|
||||
def draw_header(self, context):
|
||||
strip = active_strip(context)
|
||||
@@ -189,66 +190,69 @@ class AttractToolsPanel(AttractPollMixin, Panel):
|
||||
layout = self.layout
|
||||
strip_types = {'MOVIE', 'IMAGE', 'META', 'COLOR'}
|
||||
|
||||
selshots = list(selected_shots(context))
|
||||
if strip and strip.type in strip_types and strip.atc_object_id:
|
||||
if len(selshots) > 1:
|
||||
noun = '%i Shots' % len(selshots)
|
||||
else:
|
||||
noun = 'This Shot'
|
||||
|
||||
if strip.atc_object_id_conflict:
|
||||
warnbox = layout.box()
|
||||
warnbox.alert = True
|
||||
warnbox.label(text='Warning: This shot is linked to multiple sequencer strips.',
|
||||
icon='ERROR')
|
||||
|
||||
layout.prop(strip, 'atc_name', text='Name')
|
||||
layout.prop(strip, 'atc_status', text='Status')
|
||||
|
||||
# Create a special sub-layout for read-only properties.
|
||||
ro_sub = layout.column(align=True)
|
||||
ro_sub.enabled = False
|
||||
ro_sub.prop(strip, 'atc_description', text='Description')
|
||||
ro_sub.prop(strip, 'atc_notes', text='Notes')
|
||||
|
||||
if strip.atc_is_synced:
|
||||
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,
|
||||
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,
|
||||
icon='RENDER_STILL')
|
||||
|
||||
# Group more dangerous operations.
|
||||
dangerous_sub = layout.split(**blender.factor(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')
|
||||
|
||||
self._draw_attractstrip_buttons(context, strip)
|
||||
elif context.selected_sequences:
|
||||
if len(context.selected_sequences) > 1:
|
||||
noun = 'Selected Strips'
|
||||
else:
|
||||
noun = 'This Strip'
|
||||
layout.operator(AttractShotSubmitSelected.bl_idname,
|
||||
layout.operator(ATTRACT_OT_submit_selected.bl_idname,
|
||||
text='Submit %s as New Shot' % noun)
|
||||
layout.operator('attract.shot_relink')
|
||||
else:
|
||||
layout.operator(ATTRACT_OT_submit_all.bl_idname)
|
||||
layout.operator(ATTRACT_OT_project_open_in_browser.bl_idname, icon='WORLD')
|
||||
|
||||
def _draw_attractstrip_buttons(self, context, strip):
|
||||
"""Draw buttons when selected strips are Attract shots."""
|
||||
|
||||
layout = self.layout
|
||||
selshots = list(selected_shots(context))
|
||||
|
||||
if len(selshots) > 1:
|
||||
noun = '%i Shots' % len(selshots)
|
||||
else:
|
||||
noun = 'This Shot'
|
||||
if strip.atc_object_id_conflict:
|
||||
warnbox = layout.box()
|
||||
warnbox.alert = True
|
||||
warnbox.label(text='Warning: This shot is linked to multiple sequencer strips.',
|
||||
icon='ERROR')
|
||||
layout.prop(strip, 'atc_name', text='Name')
|
||||
layout.prop(strip, 'atc_status', text='Status')
|
||||
# Create a special sub-layout for read-only properties.
|
||||
ro_sub = layout.column(align=True)
|
||||
ro_sub.enabled = False
|
||||
ro_sub.prop(strip, 'atc_description', text='Description')
|
||||
ro_sub.prop(strip, 'atc_notes', text='Notes')
|
||||
if strip.atc_is_synced:
|
||||
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,
|
||||
icon='TRIA_UP')
|
||||
else:
|
||||
row.operator(ATTRACT_OT_submit_all.bl_idname)
|
||||
row.operator(ATTRACT_OT_shot_fetch_update.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,
|
||||
icon='RENDER_STILL')
|
||||
|
||||
# Group more dangerous operations.
|
||||
dangerous_sub = layout.split(**blender.factor(0.6), align=True)
|
||||
dangerous_sub.operator('attract.strip_unlink',
|
||||
text='Unlink %s' % noun,
|
||||
icon='PANEL_CLOSE')
|
||||
dangerous_sub.operator(ATTRACT_OT_shot_delete.bl_idname,
|
||||
text='Delete %s' % noun,
|
||||
icon='CANCEL')
|
||||
|
||||
|
||||
class AttractOperatorMixin(AttractPollMixin):
|
||||
@@ -379,7 +383,7 @@ class AttractOperatorMixin(AttractPollMixin):
|
||||
draw.tag_redraw_all_sequencer_editors()
|
||||
|
||||
|
||||
class AttractShotFetchUpdate(AttractOperatorMixin, Operator):
|
||||
class ATTRACT_OT_shot_fetch_update(AttractOperatorMixin, Operator):
|
||||
bl_idname = "attract.shot_fetch_update"
|
||||
bl_label = "Fetch Update From Attract"
|
||||
bl_description = 'Update status, description & notes from Attract'
|
||||
@@ -398,7 +402,7 @@ class AttractShotFetchUpdate(AttractOperatorMixin, Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class AttractShotRelink(AttractShotFetchUpdate):
|
||||
class ATTRACT_OT_shot_relink(AttractOperatorMixin, Operator):
|
||||
bl_idname = "attract.shot_relink"
|
||||
bl_label = "Relink With Attract"
|
||||
|
||||
@@ -467,7 +471,7 @@ class ATTRACT_OT_shot_open_in_browser(AttractOperatorMixin, Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class AttractShotDelete(AttractOperatorMixin, Operator):
|
||||
class ATTRACT_OT_shot_delete(AttractOperatorMixin, Operator):
|
||||
bl_idname = 'attract.shot_delete'
|
||||
bl_label = 'Delete Shot'
|
||||
bl_description = 'Remove this shot from Attract'
|
||||
@@ -522,7 +526,7 @@ class AttractShotDelete(AttractOperatorMixin, Operator):
|
||||
col.prop(self, 'confirm', text="I hereby confirm: delete %s from The Edit." % noun)
|
||||
|
||||
|
||||
class AttractStripUnlink(AttractOperatorMixin, Operator):
|
||||
class ATTRACT_OT_strip_unlink(AttractOperatorMixin, Operator):
|
||||
bl_idname = 'attract.strip_unlink'
|
||||
bl_label = 'Unlink Shot From This Strip'
|
||||
bl_description = 'Remove Attract props from the selected strip(s)'
|
||||
@@ -566,7 +570,7 @@ class AttractStripUnlink(AttractOperatorMixin, Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class AttractShotSubmitSelected(AttractOperatorMixin, Operator):
|
||||
class ATTRACT_OT_submit_selected(AttractOperatorMixin, Operator):
|
||||
bl_idname = 'attract.submit_selected'
|
||||
bl_label = 'Submit All Selected'
|
||||
bl_description = 'Submits all selected strips to Attract'
|
||||
@@ -906,6 +910,37 @@ class ATTRACT_OT_copy_id_to_clipboard(AttractOperatorMixin, Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class ATTRACT_OT_project_open_in_browser(Operator):
|
||||
bl_idname = 'attract.project_open_in_browser'
|
||||
bl_label = 'Open Project in Browser'
|
||||
bl_description = 'Opens a webbrowser to show the project in Attract'
|
||||
|
||||
project_id = bpy.props.StringProperty(name='Project ID', default='')
|
||||
|
||||
def execute(self, context):
|
||||
import webbrowser
|
||||
import urllib.parse
|
||||
|
||||
import pillarsdk
|
||||
from ..pillar import sync_call
|
||||
from ..blender import PILLAR_WEB_SERVER_URL, preferences
|
||||
|
||||
if not self.project_id:
|
||||
self.project_id = preferences().project.project
|
||||
|
||||
project = sync_call(pillarsdk.Project.find, self.project_id, {'projection': {'url': True}})
|
||||
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
import pprint
|
||||
log.debug('found project: %s', pprint.pformat(project.to_dict()))
|
||||
|
||||
url = urllib.parse.urljoin(PILLAR_WEB_SERVER_URL, f'attract/{project.url}')
|
||||
webbrowser.open_new_tab(url)
|
||||
self.report({'INFO'}, 'Opened a browser at %s' % url)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def draw_strip_movie_meta(self, context):
|
||||
strip = active_strip(context)
|
||||
if not strip:
|
||||
@@ -955,7 +990,9 @@ def deactivate():
|
||||
pass
|
||||
|
||||
|
||||
_rna_classes = [cls for cls in locals() if isinstance(cls, type) and hasattr(cls, 'bl_rna')]
|
||||
_rna_classes = [cls for cls in locals().values()
|
||||
if isinstance(cls, type) and cls.__name__.startswith('ATTRACT')]
|
||||
log.info('RNA classes:\n%s', '\n'.join([repr(cls) for cls in _rna_classes]))
|
||||
|
||||
|
||||
def register():
|
||||
|
@@ -18,9 +18,12 @@
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
import bpy
|
||||
import logging
|
||||
import collections
|
||||
import typing
|
||||
|
||||
import bpy
|
||||
import bgl
|
||||
import gpu
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -34,10 +37,74 @@ strip_status_colour = {
|
||||
'todo': (1.0, 0.5019607843137255, 0.5019607843137255)
|
||||
}
|
||||
|
||||
CONFLICT_COLOUR = (0.576, 0.118, 0.035) # RGB tuple
|
||||
CONFLICT_COLOUR = (0.576, 0.118, 0.035, 1.0) # RGBA tuple
|
||||
|
||||
gpu_vertex_shader = '''
|
||||
uniform mat4 ModelViewProjectionMatrix;
|
||||
|
||||
layout (location = 0) in vec2 pos;
|
||||
layout (location = 1) in vec4 color;
|
||||
|
||||
out vec4 lineColor; // output to the fragment shader
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = ModelViewProjectionMatrix * vec4(pos.x, pos.y, 0.0, 1.0);
|
||||
lineColor = color;
|
||||
}
|
||||
'''
|
||||
|
||||
gpu_fragment_shader = '''
|
||||
out vec4 fragColor;
|
||||
in vec4 lineColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
fragColor = lineColor;
|
||||
}
|
||||
'''
|
||||
|
||||
Float2 = typing.Tuple[float, float]
|
||||
Float3 = typing.Tuple[float, float, float]
|
||||
Float4 = typing.Tuple[float, float, float, float]
|
||||
|
||||
|
||||
def get_strip_rectf(strip):
|
||||
class AttractLineDrawer:
|
||||
|
||||
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.types.GPUShader(gpu_vertex_shader, gpu_fragment_shader)
|
||||
|
||||
def draw(self,
|
||||
coords: typing.List[Float2],
|
||||
colors: typing.List[Float4]):
|
||||
if not coords:
|
||||
return
|
||||
|
||||
bgl.glEnable(bgl.GL_BLEND)
|
||||
bgl.glLineWidth(2.0)
|
||||
|
||||
vbo = gpu.types.GPUVertBuf(len=len(coords), format=self._format)
|
||||
vbo.attr_fill(id=self._pos_id, data=coords)
|
||||
vbo.attr_fill(id=self._color_id, data=colors)
|
||||
|
||||
batch = gpu.types.GPUBatch(type="LINES", buf=vbo)
|
||||
batch.program_set(self.shader)
|
||||
batch.draw()
|
||||
|
||||
|
||||
def get_strip_rectf(strip) -> Float4:
|
||||
# Get x and y in terms of the grid's frames and channels
|
||||
x1 = strip.frame_final_start
|
||||
x2 = strip.frame_final_end
|
||||
@@ -47,59 +114,56 @@ def get_strip_rectf(strip):
|
||||
return x1, y1, x2, y2
|
||||
|
||||
|
||||
def draw_underline_in_strip(strip_coords, pixel_size_x, color):
|
||||
from bgl import glColor4f, glRectf, glEnable, glDisable, GL_BLEND
|
||||
import bgl
|
||||
|
||||
context = bpy.context
|
||||
|
||||
def underline_in_strip(strip_coords: Float4,
|
||||
pixel_size_x: float,
|
||||
color: Float4,
|
||||
out_coords: typing.List[Float2],
|
||||
out_colors: typing.List[Float4]):
|
||||
# Strip coords
|
||||
s_x1, s_y1, s_x2, s_y2 = strip_coords
|
||||
|
||||
# be careful not to draw over the current frame line
|
||||
cf_x = context.scene.frame_current_final
|
||||
cf_x = bpy.context.scene.frame_current_final
|
||||
|
||||
bgl.glPushAttrib(bgl.GL_COLOR_BUFFER_BIT | bgl.GL_LINE_BIT)
|
||||
# TODO(Sybren): figure out how to pass one colour per line,
|
||||
# instead of one colour per vertex.
|
||||
out_coords.append((s_x1, s_y1))
|
||||
out_colors.append(color)
|
||||
|
||||
glColor4f(*color)
|
||||
glEnable(GL_BLEND)
|
||||
bgl.glLineWidth(2)
|
||||
bgl.glBegin(bgl.GL_LINES)
|
||||
|
||||
bgl.glVertex2f(s_x1, s_y1)
|
||||
if s_x1 < cf_x < s_x2:
|
||||
# Bad luck, the line passes our strip
|
||||
bgl.glVertex2f(cf_x - pixel_size_x, s_y1)
|
||||
bgl.glVertex2f(cf_x + pixel_size_x, s_y1)
|
||||
bgl.glVertex2f(s_x2, s_y1)
|
||||
# Bad luck, the line passes our strip, so draw two lines.
|
||||
out_coords.append((cf_x - pixel_size_x, s_y1))
|
||||
out_colors.append(color)
|
||||
|
||||
bgl.glEnd()
|
||||
bgl.glPopAttrib()
|
||||
out_coords.append((cf_x + pixel_size_x, s_y1))
|
||||
out_colors.append(color)
|
||||
|
||||
out_coords.append((s_x2, s_y1))
|
||||
out_colors.append(color)
|
||||
|
||||
|
||||
def draw_strip_conflict(strip_coords, pixel_size_x):
|
||||
def strip_conflict(strip_coords: Float4,
|
||||
out_coords: typing.List[Float2],
|
||||
out_colors: typing.List[Float4]):
|
||||
"""Draws conflicting states between strips."""
|
||||
|
||||
import bgl
|
||||
|
||||
s_x1, s_y1, s_x2, s_y2 = strip_coords
|
||||
bgl.glPushAttrib(bgl.GL_COLOR_BUFFER_BIT | bgl.GL_LINE_BIT)
|
||||
|
||||
# Always draw the full rectangle, the conflict should be resolved and thus stand out.
|
||||
bgl.glColor3f(*CONFLICT_COLOUR)
|
||||
bgl.glLineWidth(2)
|
||||
# TODO(Sybren): draw a rectangle instead of a line.
|
||||
out_coords.append((s_x1, s_y2))
|
||||
out_colors.append(CONFLICT_COLOUR)
|
||||
|
||||
bgl.glBegin(bgl.GL_LINE_LOOP)
|
||||
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()
|
||||
out_coords.append((s_x2, s_y1))
|
||||
out_colors.append(CONFLICT_COLOUR)
|
||||
|
||||
bgl.glPopAttrib()
|
||||
out_coords.append((s_x2, s_y2))
|
||||
out_colors.append(CONFLICT_COLOUR)
|
||||
|
||||
out_coords.append((s_x1, s_y1))
|
||||
out_colors.append(CONFLICT_COLOUR)
|
||||
|
||||
|
||||
def draw_callback_px():
|
||||
def draw_callback_px(line_drawer: AttractLineDrawer):
|
||||
context = bpy.context
|
||||
|
||||
if not context.scene.sequence_editor:
|
||||
@@ -115,6 +179,10 @@ def draw_callback_px():
|
||||
|
||||
strips = shown_strips(context)
|
||||
|
||||
coords: typing.List[Float2] = []
|
||||
colors: typing.List[Float4] = []
|
||||
|
||||
# Collect all the lines (vertex coords + vertex colours) to draw.
|
||||
for strip in strips:
|
||||
if not strip.atc_object_id:
|
||||
continue
|
||||
@@ -124,7 +192,7 @@ def draw_callback_px():
|
||||
|
||||
# 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:
|
||||
strip_coords[3] < ywin1:
|
||||
continue
|
||||
|
||||
# Draw
|
||||
@@ -136,9 +204,11 @@ def draw_callback_px():
|
||||
|
||||
alpha = 1.0 if strip.atc_is_synced else 0.5
|
||||
|
||||
draw_underline_in_strip(strip_coords, pixel_size_x, color + (alpha,))
|
||||
underline_in_strip(strip_coords, pixel_size_x, color + (alpha,), coords, colors)
|
||||
if strip.atc_is_synced and strip.atc_object_id_conflict:
|
||||
draw_strip_conflict(strip_coords, pixel_size_x)
|
||||
strip_conflict(strip_coords, coords, colors)
|
||||
|
||||
line_drawer.draw(coords, colors)
|
||||
|
||||
|
||||
def tag_redraw_all_sequencer_editors():
|
||||
@@ -162,8 +232,9 @@ def callback_enable():
|
||||
if cb_handle:
|
||||
return
|
||||
|
||||
line_drawer = AttractLineDrawer()
|
||||
cb_handle[:] = bpy.types.SpaceSequenceEditor.draw_handler_add(
|
||||
draw_callback_px, (), 'WINDOW', 'POST_VIEW'),
|
||||
draw_callback_px, (line_drawer,), 'WINDOW', 'POST_VIEW'),
|
||||
|
||||
tag_redraw_all_sequencer_editors()
|
||||
|
||||
|
2
setup.py
2
setup.py
@@ -228,7 +228,7 @@ setup(
|
||||
'wheels': BuildWheels},
|
||||
name='blender_cloud',
|
||||
description='The Blender Cloud addon allows browsing the Blender Cloud from Blender.',
|
||||
version='1.9.2',
|
||||
version='1.9.3',
|
||||
author='Sybren A. Stüvel',
|
||||
author_email='sybren@stuvel.eu',
|
||||
packages=find_packages('.'),
|
||||
|
Reference in New Issue
Block a user