Merge branch 'develop' into feature/module_attributes_in_tray_menu

This commit is contained in:
Milan Kolar 2020-07-08 08:58:27 +02:00 committed by GitHub
commit f49591000d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 379 additions and 49 deletions

View file

@ -12,6 +12,8 @@ from pypeapp.lib.mongo import (
get_default_components
)
from . import resources
from .plugin import (
Extractor,
@ -54,6 +56,8 @@ __all__ = [
"compose_url",
"get_default_components",
# Resources
"resources",
# plugin classes
"Extractor",
# ordering

View file

@ -51,9 +51,10 @@ def get_ftrack_event_mongo_info():
if not _used_ftrack_url or components["database"] is None:
components["database"] = database_name
components["collection"] = collection_name
uri = compose_url(components)
components.pop("collection", None)
uri = compose_url(**components)
return uri, components["port"], database_name, collection_name

View file

@ -1,16 +1,16 @@
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib import parse
import os
import webbrowser
import functools
import pype
import inspect
from Qt import QtCore
from pype.api import resources
class LoginServerHandler(BaseHTTPRequestHandler):
'''Login server handler.'''
message_filepath = resources.get_resource("ftrack", "sign_in_message.html")
def __init__(self, login_callback, *args, **kw):
'''Initialise handler.'''
self.login_callback = login_callback
@ -28,23 +28,21 @@ class LoginServerHandler(BaseHTTPRequestHandler):
login_credentials = parse.parse_qs(query)
api_user = login_credentials['api_user'][0]
api_key = login_credentials['api_key'][0]
# get path to resources
path_items = os.path.dirname(
inspect.getfile(pype)
).split(os.path.sep)
del path_items[-1]
path_items.extend(['res', 'ftrack', 'sign_in_message.html'])
message_filepath = os.path.sep.join(path_items)
message_file = open(message_filepath, 'r')
sign_in_message = message_file.read()
message_file.close()
with open(self.message_filepath, "r") as message_file:
sign_in_message = message_file.read()
# formatting html code for python
replacement = [('{', '{{'), ('}', '}}'), ('{{}}', '{}')]
for r in (replacement):
sign_in_message = sign_in_message.replace(*r)
replacements = (
("{", "{{"),
("}", "}}"),
("{{}}", "{}")
)
for replacement in (replacements):
sign_in_message = sign_in_message.replace(*replacement)
message = sign_in_message.format(api_user)
else:
message = '<h1>Failed to sign in</h1>'
message = "<h1>Failed to sign in</h1>"
self.send_response(200)
self.end_headers()
@ -74,7 +72,6 @@ class LoginServerThread(QtCore.QThread):
def run(self):
'''Listen for events.'''
# self._server = BaseHTTPServer.HTTPServer(
self._server = HTTPServer(
('localhost', 0),
functools.partial(

View file

@ -96,7 +96,11 @@ class RestApiServer:
port = self.find_port()
self.rest_api_thread = RestApiThread(self, port)
statics_dir = os.path.sep.join([os.environ["PYPE_MODULE_ROOT"], "res"])
statics_dir = os.path.join(
os.environ["PYPE_MODULE_ROOT"],
"pype",
"resources"
)
self.register_statics("/res", statics_dir)
os.environ["PYPE_STATICS_SERVER"] = "{}/res".format(
os.environ["PYPE_REST_API_URL"]

View file

@ -0,0 +1,32 @@
"""Create a camera asset."""
import bpy
from avalon import api
from avalon.blender import Creator, lib
import pype.hosts.blender.plugin
class CreateCamera(Creator):
"""Polygonal static geometry"""
name = "cameraMain"
label = "Camera"
family = "camera"
icon = "video-camera"
def process(self):
asset = self.data["asset"]
subset = self.data["subset"]
name = pype.hosts.blender.plugin.asset_name(asset, subset)
collection = bpy.data.collections.new(name=name)
bpy.context.scene.collection.children.link(collection)
self.data['task'] = api.Session.get('AVALON_TASK')
lib.imprint(collection, self.data)
if (self.options or {}).get("useSelection"):
for obj in lib.get_selection():
collection.objects.link(obj)
return collection

View file

@ -0,0 +1,239 @@
"""Load a camera asset in Blender."""
import logging
from pathlib import Path
from pprint import pformat
from typing import Dict, List, Optional
from avalon import api, blender
import bpy
import pype.hosts.blender.plugin
logger = logging.getLogger("pype").getChild("blender").getChild("load_camera")
class BlendCameraLoader(pype.hosts.blender.plugin.AssetLoader):
"""Load a camera from a .blend file.
Warning:
Loading the same asset more then once is not properly supported at the
moment.
"""
families = ["camera"]
representations = ["blend"]
label = "Link Camera"
icon = "code-fork"
color = "orange"
def _remove(self, objects, lib_container):
for obj in objects:
bpy.data.cameras.remove(obj.data)
bpy.data.collections.remove(bpy.data.collections[lib_container])
def _process(self, libpath, lib_container, container_name, actions):
relative = bpy.context.preferences.filepaths.use_relative_paths
with bpy.data.libraries.load(
libpath, link=True, relative=relative
) as (_, data_to):
data_to.collections = [lib_container]
scene = bpy.context.scene
scene.collection.children.link(bpy.data.collections[lib_container])
camera_container = scene.collection.children[lib_container].make_local()
objects_list = []
for obj in camera_container.objects:
obj = obj.make_local()
obj.data.make_local()
if not obj.get(blender.pipeline.AVALON_PROPERTY):
obj[blender.pipeline.AVALON_PROPERTY] = dict()
avalon_info = obj[blender.pipeline.AVALON_PROPERTY]
avalon_info.update({"container_name": container_name})
if actions[0] is not None:
if obj.animation_data is None:
obj.animation_data_create()
obj.animation_data.action = actions[0]
if actions[1] is not None:
if obj.data.animation_data is None:
obj.data.animation_data_create()
obj.data.animation_data.action = actions[1]
objects_list.append(obj)
camera_container.pop(blender.pipeline.AVALON_PROPERTY)
bpy.ops.object.select_all(action='DESELECT')
return objects_list
def process_asset(
self, context: dict, name: str, namespace: Optional[str] = None,
options: Optional[Dict] = None
) -> Optional[List]:
"""
Arguments:
name: Use pre-defined name
namespace: Use pre-defined namespace
context: Full parenthood of representation to load
options: Additional settings dictionary
"""
libpath = self.fname
asset = context["asset"]["name"]
subset = context["subset"]["name"]
lib_container = pype.hosts.blender.plugin.asset_name(asset, subset)
container_name = pype.hosts.blender.plugin.asset_name(
asset, subset, namespace
)
container = bpy.data.collections.new(lib_container)
container.name = container_name
blender.pipeline.containerise_existing(
container,
name,
namespace,
context,
self.__class__.__name__,
)
container_metadata = container.get(
blender.pipeline.AVALON_PROPERTY)
container_metadata["libpath"] = libpath
container_metadata["lib_container"] = lib_container
objects_list = self._process(
libpath, lib_container, container_name, (None, None))
# Save the list of objects in the metadata container
container_metadata["objects"] = objects_list
nodes = list(container.objects)
nodes.append(container)
self[:] = nodes
return nodes
def update(self, container: Dict, representation: Dict):
"""Update the loaded asset.
This will remove all objects of the current collection, load the new
ones and add them to the collection.
If the objects of the collection are used in another collection they
will not be removed, only unlinked. Normally this should not be the
case though.
Warning:
No nested collections are supported at the moment!
"""
collection = bpy.data.collections.get(
container["objectName"]
)
libpath = Path(api.get_representation_path(representation))
extension = libpath.suffix.lower()
logger.info(
"Container: %s\nRepresentation: %s",
pformat(container, indent=2),
pformat(representation, indent=2),
)
assert collection, (
f"The asset is not loaded: {container['objectName']}"
)
assert not (collection.children), (
"Nested collections are not supported."
)
assert libpath, (
"No existing library file found for {container['objectName']}"
)
assert libpath.is_file(), (
f"The file doesn't exist: {libpath}"
)
assert extension in pype.hosts.blender.plugin.VALID_EXTENSIONS, (
f"Unsupported file: {libpath}"
)
collection_metadata = collection.get(
blender.pipeline.AVALON_PROPERTY)
collection_libpath = collection_metadata["libpath"]
objects = collection_metadata["objects"]
lib_container = collection_metadata["lib_container"]
normalized_collection_libpath = (
str(Path(bpy.path.abspath(collection_libpath)).resolve())
)
normalized_libpath = (
str(Path(bpy.path.abspath(str(libpath))).resolve())
)
logger.debug(
"normalized_collection_libpath:\n %s\nnormalized_libpath:\n %s",
normalized_collection_libpath,
normalized_libpath,
)
if normalized_collection_libpath == normalized_libpath:
logger.info("Library already loaded, not updating...")
return
camera = objects[0]
actions = (camera.animation_data.action, camera.data.animation_data.action)
self._remove(objects, lib_container)
objects_list = self._process(
str(libpath), lib_container, collection.name, actions)
# Save the list of objects in the metadata container
collection_metadata["objects"] = objects_list
collection_metadata["libpath"] = str(libpath)
collection_metadata["representation"] = str(representation["_id"])
bpy.ops.object.select_all(action='DESELECT')
def remove(self, container: Dict) -> bool:
"""Remove an existing container from a Blender scene.
Arguments:
container (avalon-core:container-1.0): Container to remove,
from `host.ls()`.
Returns:
bool: Whether the container was deleted.
Warning:
No nested collections are supported at the moment!
"""
collection = bpy.data.collections.get(
container["objectName"]
)
if not collection:
return False
assert not (collection.children), (
"Nested collections are not supported."
)
collection_metadata = collection.get(
blender.pipeline.AVALON_PROPERTY)
objects = collection_metadata["objects"]
lib_container = collection_metadata["lib_container"]
self._remove(objects, lib_container)
bpy.data.collections.remove(collection)
return True

View file

@ -9,7 +9,7 @@ class ExtractBlend(pype.api.Extractor):
label = "Extract Blend"
hosts = ["blender"]
families = ["animation", "model", "rig", "action", "layout"]
families = ["model", "camera", "rig", "action", "layout", "animation"]
optional = True
def process(self, instance):

View file

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 257 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 247 KiB

After

Width:  |  Height:  |  Size: 247 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Before After
Before After

View file

@ -0,0 +1,58 @@
[
{
"title": "User settings",
"type": "module",
"import_path": "pype.modules.user",
"fromlist": ["pype", "modules"]
}, {
"title": "Ftrack",
"type": "module",
"import_path": "pype.modules.ftrack.tray",
"fromlist": ["pype", "modules", "ftrack"]
}, {
"title": "Muster",
"type": "module",
"import_path": "pype.modules.muster",
"fromlist": ["pype", "modules"]
}, {
"title": "Avalon",
"type": "module",
"import_path": "pype.modules.avalon_apps",
"fromlist": ["pype", "modules"]
}, {
"title": "Clockify",
"type": "module",
"import_path": "pype.modules.clockify",
"fromlist": ["pype", "modules"]
}, {
"title": "Standalone Publish",
"type": "module",
"import_path": "pype.modules.standalonepublish",
"fromlist": ["pype", "modules"]
}, {
"title": "Logging",
"type": "module",
"import_path": "pype.modules.logging.tray",
"fromlist": ["pype", "modules", "logging"]
}, {
"title": "Idle Manager",
"type": "module",
"import_path": "pype.modules.idle_manager",
"fromlist": ["pype","modules"]
}, {
"title": "Timers Manager",
"type": "module",
"import_path": "pype.modules.timers_manager",
"fromlist": ["pype","modules"]
}, {
"title": "Rest Api",
"type": "module",
"import_path": "pype.modules.rest_api",
"fromlist": ["pype","modules"]
}, {
"title": "Adobe Communicator",
"type": "module",
"import_path": "pype.modules.adobe_communicator",
"fromlist": ["pype", "modules"]
}
]

View file

@ -12,28 +12,34 @@ class TrayManager:
Load submenus, actions, separators and modules into tray's context.
"""
modules = {}
services = {}
services_submenu = None
errors = []
items = (
config.get_presets(first_run=True)
.get('tray', {})
.get('menu_items', [])
)
available_sourcetypes = ['python', 'file']
available_sourcetypes = ["python", "file"]
def __init__(self, tray_widget, main_window):
self.tray_widget = tray_widget
self.main_window = main_window
self.log = Logger().get_logger(self.__class__.__name__)
self.icon_run = QtGui.QIcon(get_resource('circle_green.png'))
self.icon_stay = QtGui.QIcon(get_resource('circle_orange.png'))
self.icon_failed = QtGui.QIcon(get_resource('circle_red.png'))
self.modules = {}
self.services = {}
self.services_submenu = None
self.services_thread = None
self.errors = []
CURRENT_DIR = os.path.dirname(__file__)
self.modules_imports = config.load_json(
os.path.join(CURRENT_DIR, "modules_imports.json")
)
presets = config.get_presets(first_run=True)
try:
self.modules_usage = presets["tray"]["menu_items"]["item_usage"]
except Exception:
self.modules_usage = {}
self.log.critical("Couldn't find modules usage data.")
self.icon_run = QtGui.QIcon(get_resource("circle_green.png"))
self.icon_stay = QtGui.QIcon(get_resource("circle_orange.png"))
self.icon_failed = QtGui.QIcon(get_resource("circle_red.png"))
def process_presets(self):
"""Add modules to tray by presets.
@ -46,21 +52,10 @@ class TrayManager:
"item_usage": {
"Statics Server": false
}
}, {
"item_import": [{
"title": "Ftrack",
"type": "module",
"import_path": "pype.ftrack.tray",
"fromlist": ["pype", "ftrack"]
}, {
"title": "Statics Server",
"type": "module",
"import_path": "pype.services.statics_server",
"fromlist": ["pype","services"]
}]
}
In this case `Statics Server` won't be used.
"""
# Backwards compatible presets loading
if isinstance(self.items, list):
items = self.items