Reformat with Black

No functional changes.
This commit is contained in:
2021-02-16 11:21:06 +01:00
parent 883f125722
commit 8b49c5505e
27 changed files with 3094 additions and 2288 deletions

View File

@@ -27,61 +27,74 @@ import tempfile
import bpy
from bpy.types import AddonPreferences, Operator, WindowManager, Scene, PropertyGroup
from bpy.props import StringProperty, EnumProperty, PointerProperty, BoolProperty, IntProperty
from bpy.props import (
StringProperty,
EnumProperty,
PointerProperty,
BoolProperty,
IntProperty,
)
import rna_prop_ui
from . import compatibility, pillar, async_loop, flamenco, project_specific
from .utils import pyside_cache, redraw
PILLAR_WEB_SERVER_URL = os.environ.get('BCLOUD_SERVER', 'https://cloud.blender.org/')
PILLAR_SERVER_URL = '%sapi/' % PILLAR_WEB_SERVER_URL
PILLAR_WEB_SERVER_URL = os.environ.get("BCLOUD_SERVER", "https://cloud.blender.org/")
PILLAR_SERVER_URL = "%sapi/" % PILLAR_WEB_SERVER_URL
ADDON_NAME = 'blender_cloud'
ADDON_NAME = "blender_cloud"
log = logging.getLogger(__name__)
icons = None
@pyside_cache('version')
@pyside_cache("version")
def blender_syncable_versions(self, context):
"""Returns the list of items used by SyncStatusProperties.version EnumProperty."""
bss = context.window_manager.blender_sync_status
versions = bss.available_blender_versions
if not versions:
return [('', 'No settings stored in your Blender Cloud', '')]
return [(v, v, '') for v in versions]
return [("", "No settings stored in your Blender Cloud", "")]
return [(v, v, "") for v in versions]
@compatibility.convert_properties
class SyncStatusProperties(PropertyGroup):
status = EnumProperty(
items=[
('NONE', 'NONE', 'We have done nothing at all yet.'),
('IDLE', 'IDLE', 'User requested something, which is done, and we are now idle.'),
('SYNCING', 'SYNCING', 'Synchronising with Blender Cloud.'),
("NONE", "NONE", "We have done nothing at all yet."),
(
"IDLE",
"IDLE",
"User requested something, which is done, and we are now idle.",
),
("SYNCING", "SYNCING", "Synchronising with Blender Cloud."),
],
name='status',
description='Current status of Blender Sync',
update=redraw)
name="status",
description="Current status of Blender Sync",
update=redraw,
)
version = EnumProperty(
items=blender_syncable_versions,
name='Version of Blender from which to pull',
description='Version of Blender from which to pull')
name="Version of Blender from which to pull",
description="Version of Blender from which to pull",
)
message = StringProperty(name='message', update=redraw)
message = StringProperty(name="message", update=redraw)
level = EnumProperty(
items=[
('INFO', 'INFO', ''),
('WARNING', 'WARNING', ''),
('ERROR', 'ERROR', ''),
('SUBSCRIBE', 'SUBSCRIBE', ''),
("INFO", "INFO", ""),
("WARNING", "WARNING", ""),
("ERROR", "ERROR", ""),
("SUBSCRIBE", "SUBSCRIBE", ""),
],
name='level',
update=redraw)
name="level",
update=redraw,
)
def report(self, level: set, message: str):
assert len(level) == 1, 'level should be a set of one string, not %r' % level
assert len(level) == 1, "level should be a set of one string, not %r" % level
self.level = level.pop()
self.message = message
@@ -98,21 +111,21 @@ class SyncStatusProperties(PropertyGroup):
# because I don't know how to store a variable list of strings in a proper RNA property.
@property
def available_blender_versions(self) -> list:
return self.get('available_blender_versions', [])
return self.get("available_blender_versions", [])
@available_blender_versions.setter
def available_blender_versions(self, new_versions):
self['available_blender_versions'] = new_versions
self["available_blender_versions"] = new_versions
@pyside_cache('project')
@pyside_cache("project")
def bcloud_available_projects(self, context):
"""Returns the list of items used by BlenderCloudProjectGroup.project EnumProperty."""
projs = preferences().project.available_projects
if not projs:
return [('', 'No projects available in your Blender Cloud', '')]
return [(p['_id'], p['name'], '') for p in projs]
return [("", "No projects available in your Blender Cloud", "")]
return [(p["_id"], p["name"], "") for p in projs]
@functools.lru_cache(1)
@@ -122,51 +135,55 @@ def project_extensions(project_id) -> set:
At the moment of writing these are 'attract' and 'flamenco'.
"""
log.debug('Finding extensions for project %s', project_id)
log.debug("Finding extensions for project %s", project_id)
# We can't use our @property, since the preferences may be loaded from a
# preferences blend file, in which case it is not constructed from Python code.
available_projects = preferences().project.get('available_projects', [])
available_projects = preferences().project.get("available_projects", [])
if not available_projects:
log.debug('No projects available.')
log.debug("No projects available.")
return set()
proj = next((p for p in available_projects
if p['_id'] == project_id), None)
proj = next((p for p in available_projects if p["_id"] == project_id), None)
if proj is None:
log.debug('Project %s not found in available projects.', project_id)
log.debug("Project %s not found in available projects.", project_id)
return set()
return set(proj.get('enabled_for', ()))
return set(proj.get("enabled_for", ()))
@compatibility.convert_properties
class BlenderCloudProjectGroup(PropertyGroup):
status = EnumProperty(
items=[
('NONE', 'NONE', 'We have done nothing at all yet'),
('IDLE', 'IDLE', 'User requested something, which is done, and we are now idle'),
('FETCHING', 'FETCHING', 'Fetching available projects from Blender Cloud'),
("NONE", "NONE", "We have done nothing at all yet"),
(
"IDLE",
"IDLE",
"User requested something, which is done, and we are now idle",
),
("FETCHING", "FETCHING", "Fetching available projects from Blender Cloud"),
],
name='status',
update=redraw)
name="status",
update=redraw,
)
project = EnumProperty(
items=bcloud_available_projects,
name='Cloud project',
description='Which Blender Cloud project to work with',
update=project_specific.handle_project_update
name="Cloud project",
description="Which Blender Cloud project to work with",
update=project_specific.handle_project_update,
)
# List of projects is stored in 'available_projects' ID property,
# because I don't know how to store a variable list of strings in a proper RNA property.
@property
def available_projects(self) -> list:
return self.get('available_projects', [])
return self.get("available_projects", [])
@available_projects.setter
def available_projects(self, new_projects):
self['available_projects'] = new_projects
self["available_projects"] = new_projects
project_specific.handle_project_update()
@@ -177,21 +194,22 @@ class BlenderCloudPreferences(AddonPreferences):
# The following property is read-only to limit the scope of the
# addon and allow for proper testing within this scope.
pillar_server = StringProperty(
name='Blender Cloud Server',
description='URL of the Blender Cloud backend server',
name="Blender Cloud Server",
description="URL of the Blender Cloud backend server",
default=PILLAR_SERVER_URL,
get=lambda self: PILLAR_SERVER_URL
get=lambda self: PILLAR_SERVER_URL,
)
local_texture_dir = StringProperty(
name='Default Blender Cloud Texture Storage Directory',
subtype='DIR_PATH',
default='//textures')
name="Default Blender Cloud Texture Storage Directory",
subtype="DIR_PATH",
default="//textures",
)
open_browser_after_share = BoolProperty(
name='Open Browser after Sharing File',
description='When enabled, Blender will open a webbrowser',
default=True
name="Open Browser after Sharing File",
description="When enabled, Blender will open a webbrowser",
default=True,
)
# TODO: store project-dependent properties with the project, so that people
@@ -199,65 +217,65 @@ class BlenderCloudPreferences(AddonPreferences):
project = PointerProperty(type=BlenderCloudProjectGroup)
cloud_project_local_path = StringProperty(
name='Local Project Path',
description='Local path of your Attract project, used to search for blend files; '
'usually best to set to an absolute path',
subtype='DIR_PATH',
default='//../',
name="Local Project Path",
description="Local path of your Attract project, used to search for blend files; "
"usually best to set to an absolute path",
subtype="DIR_PATH",
default="//../",
update=project_specific.store,
)
flamenco_manager = PointerProperty(type=flamenco.FlamencoManagerGroup)
flamenco_exclude_filter = StringProperty(
name='File Exclude Filter',
name="File Exclude Filter",
description='Space-separated list of filename filters, like "*.abc *.mkv", to prevent '
'matching files from being packed into the output directory',
default='',
"matching files from being packed into the output directory",
default="",
update=project_specific.store,
)
flamenco_job_file_path = StringProperty(
name='Job Storage Path',
description='Path where to store job files, should be accesible for Workers too',
subtype='DIR_PATH',
name="Job Storage Path",
description="Path where to store job files, should be accesible for Workers too",
subtype="DIR_PATH",
default=tempfile.gettempdir(),
update=project_specific.store,
)
flamenco_job_output_path = StringProperty(
name='Job Output Path',
description='Path where to store output files, should be accessible for Workers',
subtype='DIR_PATH',
name="Job Output Path",
description="Path where to store output files, should be accessible for Workers",
subtype="DIR_PATH",
default=tempfile.gettempdir(),
update=project_specific.store,
)
flamenco_job_output_strip_components = IntProperty(
name='Job Output Path Strip Components',
description='The final output path comprises of the job output path, and the blend file '
'path relative to the project with this many path components stripped off '
'the front',
name="Job Output Path Strip Components",
description="The final output path comprises of the job output path, and the blend file "
"path relative to the project with this many path components stripped off "
"the front",
min=0,
default=0,
soft_max=4,
update=project_specific.store,
)
flamenco_relative_only = BoolProperty(
name='Relative Paths Only',
description='When enabled, only assets that are referred to with a relative path are '
'packed, and assets referred to by an absolute path are excluded from the '
'BAT pack. When disabled, all assets are packed',
name="Relative Paths Only",
description="When enabled, only assets that are referred to with a relative path are "
"packed, and assets referred to by an absolute path are excluded from the "
"BAT pack. When disabled, all assets are packed",
default=False,
update=project_specific.store,
)
flamenco_open_browser_after_submit = BoolProperty(
name='Open Browser after Submitting Job',
description='When enabled, Blender will open a webbrowser',
name="Open Browser after Submitting Job",
description="When enabled, Blender will open a webbrowser",
default=True,
)
flamenco_show_quit_after_submit_button = BoolProperty(
name='Show "Submit & Quit" button',
description='When enabled, next to the "Render on Flamenco" button there will be a button '
'"Submit & Quit" that silently quits Blender after submitting the render job '
'to Flamenco',
'"Submit & Quit" that silently quits Blender after submitting the render job '
"to Flamenco",
default=False,
)
@@ -276,24 +294,30 @@ class BlenderCloudPreferences(AddonPreferences):
blender_id_profile = blender_id.get_active_profile()
if blender_id is None:
msg_icon = 'ERROR'
text = 'This add-on requires Blender ID'
help_text = 'Make sure that the Blender ID add-on is installed and activated'
msg_icon = "ERROR"
text = "This add-on requires Blender ID"
help_text = (
"Make sure that the Blender ID add-on is installed and activated"
)
elif not blender_id_profile:
msg_icon = 'ERROR'
text = 'You are logged out.'
help_text = 'To login, go to the Blender ID add-on preferences.'
msg_icon = "ERROR"
text = "You are logged out."
help_text = "To login, go to the Blender ID add-on preferences."
elif bpy.app.debug and pillar.SUBCLIENT_ID not in blender_id_profile.subclients:
msg_icon = 'QUESTION'
text = 'No Blender Cloud credentials.'
help_text = ('You are logged in on Blender ID, but your credentials have not '
'been synchronized with Blender Cloud yet. Press the Update '
'Credentials button.')
msg_icon = "QUESTION"
text = "No Blender Cloud credentials."
help_text = (
"You are logged in on Blender ID, but your credentials have not "
"been synchronized with Blender Cloud yet. Press the Update "
"Credentials button."
)
else:
msg_icon = 'WORLD_DATA'
text = 'You are logged in as %s.' % blender_id_profile.username
help_text = ('To logout or change profile, '
'go to the Blender ID add-on preferences.')
msg_icon = "WORLD_DATA"
text = "You are logged in as %s." % blender_id_profile.username
help_text = (
"To logout or change profile, "
"go to the Blender ID add-on preferences."
)
# Authentication stuff
auth_box = layout.box()
@@ -307,165 +331,175 @@ class BlenderCloudPreferences(AddonPreferences):
# Texture browser stuff
texture_box = layout.box()
texture_box.enabled = msg_icon != 'ERROR'
texture_box.enabled = msg_icon != "ERROR"
sub = texture_box.column()
sub.label(text='Local directory for downloaded textures', icon_value=icon('CLOUD'))
sub.prop(self, "local_texture_dir", text='Default')
sub.prop(context.scene, "local_texture_dir", text='Current scene')
sub.label(
text="Local directory for downloaded textures", icon_value=icon("CLOUD")
)
sub.prop(self, "local_texture_dir", text="Default")
sub.prop(context.scene, "local_texture_dir", text="Current scene")
# Blender Sync stuff
bss = context.window_manager.blender_sync_status
bsync_box = layout.box()
bsync_box.enabled = msg_icon != 'ERROR'
bsync_box.enabled = msg_icon != "ERROR"
row = bsync_box.row().split(**compatibility.factor(0.33))
row.label(text='Blender Sync with Blender Cloud', icon_value=icon('CLOUD'))
row.label(text="Blender Sync with Blender Cloud", icon_value=icon("CLOUD"))
icon_for_level = {
'INFO': 'NONE',
'WARNING': 'INFO',
'ERROR': 'ERROR',
'SUBSCRIBE': 'ERROR',
"INFO": "NONE",
"WARNING": "INFO",
"ERROR": "ERROR",
"SUBSCRIBE": "ERROR",
}
msg_icon = icon_for_level[bss.level] if bss.message else 'NONE'
msg_icon = icon_for_level[bss.level] if bss.message else "NONE"
message_container = row.row()
message_container.label(text=bss.message, icon=msg_icon)
sub = bsync_box.column()
if bss.level == 'SUBSCRIBE':
if bss.level == "SUBSCRIBE":
self.draw_subscribe_button(sub)
self.draw_sync_buttons(sub, bss)
# Image Share stuff
share_box = layout.box()
share_box.label(text='Image Sharing on Blender Cloud', icon_value=icon('CLOUD'))
share_box.prop(self, 'open_browser_after_share')
share_box.label(text="Image Sharing on Blender Cloud", icon_value=icon("CLOUD"))
share_box.prop(self, "open_browser_after_share")
# Project selector
project_box = layout.box()
project_box.enabled = self.project.status in {'NONE', 'IDLE'}
project_box.enabled = self.project.status in {"NONE", "IDLE"}
self.draw_project_selector(project_box, self.project)
extensions = project_extensions(self.project.project)
# Flamenco stuff
if 'flamenco' in extensions:
if "flamenco" in extensions:
flamenco_box = project_box.column()
self.draw_flamenco_buttons(flamenco_box, self.flamenco_manager, context)
def draw_subscribe_button(self, layout):
layout.operator('pillar.subscribe', icon='WORLD')
layout.operator("pillar.subscribe", icon="WORLD")
def draw_sync_buttons(self, layout, bss):
layout.enabled = bss.status in {'NONE', 'IDLE'}
layout.enabled = bss.status in {"NONE", "IDLE"}
buttons = layout.column()
row_buttons = buttons.row().split(**compatibility.factor(0.5))
row_push = row_buttons.row()
row_pull = row_buttons.row(align=True)
row_push.operator('pillar.sync',
text='Save %i.%i settings' % bpy.app.version[:2],
icon='TRIA_UP').action = 'PUSH'
row_push.operator(
"pillar.sync",
text="Save %i.%i settings" % bpy.app.version[:2],
icon="TRIA_UP",
).action = "PUSH"
versions = bss.available_blender_versions
if bss.status in {'NONE', 'IDLE'}:
if bss.status in {"NONE", "IDLE"}:
if not versions:
row_pull.operator('pillar.sync',
text='Find version to load',
icon='TRIA_DOWN').action = 'REFRESH'
row_pull.operator(
"pillar.sync", text="Find version to load", icon="TRIA_DOWN"
).action = "REFRESH"
else:
props = row_pull.operator('pillar.sync',
text='Load %s settings' % bss.version,
icon='TRIA_DOWN')
props.action = 'PULL'
props = row_pull.operator(
"pillar.sync",
text="Load %s settings" % bss.version,
icon="TRIA_DOWN",
)
props.action = "PULL"
props.blender_version = bss.version
row_pull.operator('pillar.sync',
text='',
icon=compatibility.SYNC_SELECT_VERSION_ICON).action = 'SELECT'
row_pull.operator(
"pillar.sync", text="", icon=compatibility.SYNC_SELECT_VERSION_ICON
).action = "SELECT"
else:
row_pull.label(text='Cloud Sync is running.')
row_pull.label(text="Cloud Sync is running.")
def draw_project_selector(self, project_box, bcp: BlenderCloudProjectGroup):
project_row = project_box.row(align=True)
project_row.label(text='Project settings', icon_value=icon('CLOUD'))
project_row.label(text="Project settings", icon_value=icon("CLOUD"))
row_buttons = project_row.row(align=True)
projects = bcp.available_projects
project = bcp.project
if bcp.status in {'NONE', 'IDLE'}:
if bcp.status in {"NONE", "IDLE"}:
if not projects:
row_buttons.operator('pillar.projects',
text='Find project to load',
icon='FILE_REFRESH')
row_buttons.operator(
"pillar.projects", text="Find project to load", icon="FILE_REFRESH"
)
else:
row_buttons.prop(bcp, 'project')
row_buttons.operator('pillar.projects',
text='',
icon='FILE_REFRESH')
props = row_buttons.operator('pillar.project_open_in_browser',
text='',
icon='WORLD')
row_buttons.prop(bcp, "project")
row_buttons.operator("pillar.projects", text="", icon="FILE_REFRESH")
props = row_buttons.operator(
"pillar.project_open_in_browser", text="", icon="WORLD"
)
props.project_id = project
else:
row_buttons.label(text='Fetching available projects.')
row_buttons.label(text="Fetching available projects.")
enabled_for = project_extensions(project)
if not project:
return
if not enabled_for:
project_box.label(text='This project is not set up for Attract or Flamenco')
project_box.label(text="This project is not set up for Attract or Flamenco")
return
project_box.label(text='This project is set up for: %s' %
', '.join(sorted(enabled_for)))
project_box.label(
text="This project is set up for: %s" % ", ".join(sorted(enabled_for))
)
# This is only needed when the project is set up for either Attract or Flamenco.
project_box.prop(self, 'cloud_project_local_path',
text='Local Project Path')
project_box.prop(self, "cloud_project_local_path", text="Local Project Path")
def draw_flamenco_buttons(self, flamenco_box, bcp: flamenco.FlamencoManagerGroup, context):
def draw_flamenco_buttons(
self, flamenco_box, bcp: flamenco.FlamencoManagerGroup, context
):
header_row = flamenco_box.row(align=True)
header_row.label(text='Flamenco:', icon_value=icon('CLOUD'))
header_row.label(text="Flamenco:", icon_value=icon("CLOUD"))
manager_split = flamenco_box.split(**compatibility.factor(0.32), align=True)
manager_split.label(text='Manager:')
manager_split.label(text="Manager:")
manager_box = manager_split.row(align=True)
if bcp.status in {'NONE', 'IDLE'}:
if bcp.status in {"NONE", "IDLE"}:
if not bcp.available_managers:
manager_box.operator('flamenco.managers',
text='Find Flamenco Managers',
icon='FILE_REFRESH')
manager_box.operator(
"flamenco.managers",
text="Find Flamenco Managers",
icon="FILE_REFRESH",
)
else:
manager_box.prop(bcp, 'manager', text='')
manager_box.operator('flamenco.managers',
text='',
icon='FILE_REFRESH')
manager_box.prop(bcp, "manager", text="")
manager_box.operator("flamenco.managers", text="", icon="FILE_REFRESH")
else:
manager_box.label(text='Fetching available managers.')
manager_box.label(text="Fetching available managers.")
path_split = flamenco_box.split(**compatibility.factor(0.32), align=True)
path_split.label(text='Job File Path:')
path_split.label(text="Job File Path:")
path_box = path_split.row(align=True)
path_box.prop(self, 'flamenco_job_file_path', text='')
props = path_box.operator('flamenco.explore_file_path', text='', icon='DISK_DRIVE')
path_box.prop(self, "flamenco_job_file_path", text="")
props = path_box.operator(
"flamenco.explore_file_path", text="", icon="DISK_DRIVE"
)
props.path = self.flamenco_job_file_path
job_output_box = flamenco_box.column(align=True)
path_split = job_output_box.split(**compatibility.factor(0.32), align=True)
path_split.label(text='Job Output Path:')
path_split.label(text="Job Output Path:")
path_box = path_split.row(align=True)
path_box.prop(self, 'flamenco_job_output_path', text='')
props = path_box.operator('flamenco.explore_file_path', text='', icon='DISK_DRIVE')
path_box.prop(self, "flamenco_job_output_path", text="")
props = path_box.operator(
"flamenco.explore_file_path", text="", icon="DISK_DRIVE"
)
props.path = self.flamenco_job_output_path
job_output_box.prop(self, 'flamenco_exclude_filter')
job_output_box.prop(self, "flamenco_exclude_filter")
prop_split = job_output_box.split(**compatibility.factor(0.32), align=True)
prop_split.label(text='Strip Components:')
prop_split.prop(self, 'flamenco_job_output_strip_components', text='')
prop_split.label(text="Strip Components:")
prop_split.prop(self, "flamenco_job_output_strip_components", text="")
from .flamenco import render_output_path
@@ -473,25 +507,29 @@ class BlenderCloudPreferences(AddonPreferences):
output_path = render_output_path(context)
if output_path:
path_box.label(text=str(output_path))
props = path_box.operator('flamenco.explore_file_path', text='', icon='DISK_DRIVE')
props = path_box.operator(
"flamenco.explore_file_path", text="", icon="DISK_DRIVE"
)
props.path = str(output_path.parent)
else:
path_box.label(text='Blend file is not in your project path, '
'unable to give output path example.')
path_box.label(
text="Blend file is not in your project path, "
"unable to give output path example."
)
flamenco_box.prop(self, 'flamenco_relative_only')
flamenco_box.prop(self, 'flamenco_open_browser_after_submit')
flamenco_box.prop(self, 'flamenco_show_quit_after_submit_button')
flamenco_box.prop(self, "flamenco_relative_only")
flamenco_box.prop(self, "flamenco_open_browser_after_submit")
flamenco_box.prop(self, "flamenco_show_quit_after_submit_button")
class PillarCredentialsUpdate(pillar.PillarOperatorMixin,
Operator):
class PillarCredentialsUpdate(pillar.PillarOperatorMixin, Operator):
"""Updates the Pillar URL and tests the new URL."""
bl_idname = 'pillar.credentials_update'
bl_label = 'Update credentials'
bl_description = 'Resynchronises your Blender ID login with Blender Cloud'
log = logging.getLogger('bpy.ops.%s' % bl_idname)
bl_idname = "pillar.credentials_update"
bl_label = "Update credentials"
bl_description = "Resynchronises your Blender ID login with Blender Cloud"
log = logging.getLogger("bpy.ops.%s" % bl_idname)
@classmethod
def poll(cls, context):
@@ -513,51 +551,52 @@ class PillarCredentialsUpdate(pillar.PillarOperatorMixin,
# Only allow activation when the user is actually logged in.
if not self.is_logged_in(context):
self.report({'ERROR'}, 'No active profile found')
return {'CANCELLED'}
self.report({"ERROR"}, "No active profile found")
return {"CANCELLED"}
try:
loop = asyncio.get_event_loop()
loop.run_until_complete(self.check_credentials(context, set()))
except blender_id.BlenderIdCommError as ex:
log.exception('Error sending subclient-specific token to Blender ID')
self.report({'ERROR'}, 'Failed to sync Blender ID to Blender Cloud')
return {'CANCELLED'}
log.exception("Error sending subclient-specific token to Blender ID")
self.report({"ERROR"}, "Failed to sync Blender ID to Blender Cloud")
return {"CANCELLED"}
except Exception as ex:
log.exception('Error in test call to Pillar')
self.report({'ERROR'}, 'Failed test connection to Blender Cloud')
return {'CANCELLED'}
log.exception("Error in test call to Pillar")
self.report({"ERROR"}, "Failed test connection to Blender Cloud")
return {"CANCELLED"}
self.report({'INFO'}, 'Blender Cloud credentials & endpoint URL updated.')
return {'FINISHED'}
self.report({"INFO"}, "Blender Cloud credentials & endpoint URL updated.")
return {"FINISHED"}
class PILLAR_OT_subscribe(Operator):
"""Opens a browser to subscribe the user to the Cloud."""
bl_idname = 'pillar.subscribe'
bl_label = 'Subscribe to the Cloud'
bl_idname = "pillar.subscribe"
bl_label = "Subscribe to the Cloud"
bl_description = "Opens a page in a web browser to subscribe to the Blender Cloud"
def execute(self, context):
import webbrowser
webbrowser.open_new_tab('https://cloud.blender.org/join')
self.report({'INFO'}, 'We just started a browser for you.')
webbrowser.open_new_tab("https://cloud.blender.org/join")
self.report({"INFO"}, "We just started a browser for you.")
return {'FINISHED'}
return {"FINISHED"}
@compatibility.convert_properties
class PILLAR_OT_project_open_in_browser(Operator):
bl_idname = 'pillar.project_open_in_browser'
bl_label = 'Open in Browser'
bl_description = 'Opens a webbrowser to show the project'
bl_idname = "pillar.project_open_in_browser"
bl_label = "Open in Browser"
bl_description = "Opens a webbrowser to show the project"
project_id = StringProperty(name='Project ID')
project_id = StringProperty(name="Project ID")
def execute(self, context):
if not self.project_id:
return {'CANCELLED'}
return {"CANCELLED"}
import webbrowser
import urllib.parse
@@ -565,28 +604,34 @@ class PILLAR_OT_project_open_in_browser(Operator):
import pillarsdk
from .pillar import sync_call
project = sync_call(pillarsdk.Project.find, self.project_id, {'projection': {'url': True}})
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, 'p/' + project.url)
log.debug("found project: %s", pprint.pformat(project.to_dict()))
url = urllib.parse.urljoin(PILLAR_WEB_SERVER_URL, "p/" + project.url)
webbrowser.open_new_tab(url)
self.report({'INFO'}, 'Opened a browser at %s' % url)
self.report({"INFO"}, "Opened a browser at %s" % url)
return {'FINISHED'}
return {"FINISHED"}
class PILLAR_OT_projects(async_loop.AsyncModalOperatorMixin,
pillar.AuthenticatedPillarOperatorMixin,
Operator):
class PILLAR_OT_projects(
async_loop.AsyncModalOperatorMixin,
pillar.AuthenticatedPillarOperatorMixin,
Operator,
):
"""Fetches the projects available to the user"""
bl_idname = 'pillar.projects'
bl_label = 'Fetch available projects'
bl_idname = "pillar.projects"
bl_label = "Fetch available projects"
stop_upon_exception = True
_log = logging.getLogger('bpy.ops.%s' % bl_idname)
_log = logging.getLogger("bpy.ops.%s" % bl_idname)
async def async_execute(self, context):
if not await self.authenticate(context):
@@ -595,69 +640,71 @@ class PILLAR_OT_projects(async_loop.AsyncModalOperatorMixin,
import pillarsdk
from .pillar import pillar_call
self.log.info('Going to fetch projects for user %s', self.user_id)
self.log.info("Going to fetch projects for user %s", self.user_id)
preferences().project.status = 'FETCHING'
preferences().project.status = "FETCHING"
# Get all projects, except the home project.
projects_user = await pillar_call(
pillarsdk.Project.all,
{'where': {'user': self.user_id,
'category': {'$ne': 'home'}},
'sort': '-name',
'projection': {'_id': True,
'name': True,
'extension_props': True},
})
{
"where": {"user": self.user_id, "category": {"$ne": "home"}},
"sort": "-name",
"projection": {"_id": True, "name": True, "extension_props": True},
},
)
projects_shared = await pillar_call(
pillarsdk.Project.all,
{'where': {'user': {'$ne': self.user_id},
'permissions.groups.group': {'$in': self.db_user.groups}},
'sort': '-name',
'projection': {'_id': True,
'name': True,
'extension_props': True},
})
{
"where": {
"user": {"$ne": self.user_id},
"permissions.groups.group": {"$in": self.db_user.groups},
},
"sort": "-name",
"projection": {"_id": True, "name": True, "extension_props": True},
},
)
# We need to convert to regular dicts before storing in ID properties.
# Also don't store more properties than we need.
def reduce_properties(project_list):
for p in project_list:
p = p.to_dict()
extension_props = p.get('extension_props', {})
extension_props = p.get("extension_props", {})
enabled_for = list(extension_props.keys())
self._log.debug('Project %r is enabled for %s', p['name'], enabled_for)
self._log.debug("Project %r is enabled for %s", p["name"], enabled_for)
yield {
'_id': p['_id'],
'name': p['name'],
'enabled_for': enabled_for,
"_id": p["_id"],
"name": p["name"],
"enabled_for": enabled_for,
}
projects = list(reduce_properties(projects_user['_items'])) + \
list(reduce_properties(projects_shared['_items']))
projects = list(reduce_properties(projects_user["_items"])) + list(
reduce_properties(projects_shared["_items"])
)
def proj_sort_key(project):
return project.get('name')
return project.get("name")
preferences().project.available_projects = sorted(projects, key=proj_sort_key)
self.quit()
def quit(self):
preferences().project.status = 'IDLE'
preferences().project.status = "IDLE"
super().quit()
class PILLAR_PT_image_custom_properties(rna_prop_ui.PropertyPanel, bpy.types.Panel):
"""Shows custom properties in the image editor."""
bl_space_type = 'IMAGE_EDITOR'
bl_region_type = 'UI'
bl_label = 'Custom Properties'
bl_space_type = "IMAGE_EDITOR"
bl_region_type = "UI"
bl_label = "Custom Properties"
_context_path = 'edit_image'
_context_path = "edit_image"
_property_type = bpy.types.Image
@@ -681,9 +728,10 @@ def load_custom_icons():
return
import bpy.utils.previews
icons = bpy.utils.previews.new()
my_icons_dir = os.path.join(os.path.dirname(__file__), 'icons')
icons.load('CLOUD', os.path.join(my_icons_dir, 'icon-cloud.png'), 'IMAGE')
my_icons_dir = os.path.join(os.path.dirname(__file__), "icons")
icons.load("CLOUD", os.path.join(my_icons_dir, "icon-cloud.png"), "IMAGE")
def unload_custom_icons():
@@ -719,8 +767,8 @@ def register():
addon_prefs = preferences()
WindowManager.last_blender_cloud_location = StringProperty(
name="Last Blender Cloud browser location",
default="/")
name="Last Blender Cloud browser location", default="/"
)
def default_if_empty(scene, context):
"""The scene's local_texture_dir, if empty, reverts to the addon prefs."""
@@ -729,10 +777,11 @@ def register():
scene.local_texture_dir = addon_prefs.local_texture_dir
Scene.local_texture_dir = StringProperty(
name='Blender Cloud texture storage directory for current scene',
subtype='DIR_PATH',
name="Blender Cloud texture storage directory for current scene",
subtype="DIR_PATH",
default=addon_prefs.local_texture_dir,
update=default_if_empty)
update=default_if_empty,
)
WindowManager.blender_sync_status = PointerProperty(type=SyncStatusProperties)