Merge branch '2.x/develop' into feature/239-resolve_tagging_for_publish
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import traceback
|
|||
from avalon import api as avalon
|
||||
from pyblish import api as pyblish
|
||||
|
||||
import bpy
|
||||
|
||||
from pype import PLUGINS_DIR
|
||||
|
||||
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "blender", "publish")
|
||||
|
|
@ -25,6 +27,9 @@ def install():
|
|||
avalon.register_plugin_path(avalon.Loader, str(LOAD_PATH))
|
||||
avalon.register_plugin_path(avalon.Creator, str(CREATE_PATH))
|
||||
|
||||
avalon.on("new", on_new)
|
||||
avalon.on("open", on_open)
|
||||
|
||||
|
||||
def uninstall():
|
||||
"""Uninstall Blender configuration for Avalon."""
|
||||
|
|
@ -32,3 +37,24 @@ def uninstall():
|
|||
pyblish.deregister_plugin_path(str(PUBLISH_PATH))
|
||||
avalon.deregister_plugin_path(avalon.Loader, str(LOAD_PATH))
|
||||
avalon.deregister_plugin_path(avalon.Creator, str(CREATE_PATH))
|
||||
|
||||
|
||||
def set_start_end_frames():
|
||||
from avalon import io
|
||||
|
||||
asset_name = io.Session["AVALON_ASSET"]
|
||||
asset_doc = io.find_one({
|
||||
"type": "asset",
|
||||
"name": asset_name
|
||||
})
|
||||
|
||||
bpy.context.scene.frame_start = asset_doc["data"]["frameStart"]
|
||||
bpy.context.scene.frame_end = asset_doc["data"]["frameEnd"]
|
||||
|
||||
|
||||
def on_new(arg1, arg2):
|
||||
set_start_end_frames()
|
||||
|
||||
|
||||
def on_open(arg1, arg2):
|
||||
set_start_end_frames()
|
||||
|
|
|
|||
|
|
@ -14,12 +14,41 @@ def asset_name(
|
|||
asset: str, subset: str, namespace: Optional[str] = None
|
||||
) -> str:
|
||||
"""Return a consistent name for an asset."""
|
||||
name = f"{asset}_{subset}"
|
||||
name = f"{asset}"
|
||||
if namespace:
|
||||
name = f"{namespace}:{name}"
|
||||
name = f"{name}_{namespace}"
|
||||
name = f"{name}_{subset}"
|
||||
return name
|
||||
|
||||
|
||||
def get_unique_number(
|
||||
asset: str, subset: str
|
||||
) -> str:
|
||||
"""Return a unique number based on the asset name."""
|
||||
avalon_containers = [
|
||||
c for c in bpy.data.collections
|
||||
if c.name == 'AVALON_CONTAINERS'
|
||||
]
|
||||
loaded_assets = []
|
||||
for c in avalon_containers:
|
||||
loaded_assets.extend(c.children)
|
||||
collections_names = [
|
||||
c.name for c in loaded_assets
|
||||
]
|
||||
count = 1
|
||||
name = f"{asset}_{count:0>2}_{subset}_CON"
|
||||
while name in collections_names:
|
||||
count += 1
|
||||
name = f"{asset}_{count:0>2}_{subset}_CON"
|
||||
return f"{count:0>2}"
|
||||
|
||||
|
||||
def prepare_data(data, container_name):
|
||||
name = data.name
|
||||
data = data.make_local()
|
||||
data.name = f"{name}:{container_name}"
|
||||
|
||||
|
||||
def create_blender_context(active: Optional[bpy.types.Object] = None,
|
||||
selected: Optional[bpy.types.Object] = None,):
|
||||
"""Create a new Blender context. If an object is passed as
|
||||
|
|
@ -47,6 +76,25 @@ def create_blender_context(active: Optional[bpy.types.Object] = None,
|
|||
raise Exception("Could not create a custom Blender context.")
|
||||
|
||||
|
||||
def get_parent_collection(collection):
|
||||
"""Get the parent of the input collection"""
|
||||
check_list = [bpy.context.scene.collection]
|
||||
|
||||
for c in check_list:
|
||||
if collection.name in c.children.keys():
|
||||
return c
|
||||
check_list.extend(c.children)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_local_collection_with_name(name):
|
||||
for collection in bpy.data.collections:
|
||||
if collection.name == name and collection.library is None:
|
||||
return collection
|
||||
return None
|
||||
|
||||
|
||||
class AssetLoader(api.Loader):
|
||||
"""A basic AssetLoader for Blender
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
from avalon import api, harmony
|
||||
from avalon import api, io, harmony
|
||||
from avalon.vendor import Qt
|
||||
import avalon.tools.sceneinventory
|
||||
import pyblish.api
|
||||
from pype import lib
|
||||
|
||||
|
|
@ -92,6 +93,61 @@ def ensure_scene_settings():
|
|||
set_scene_settings(valid_settings)
|
||||
|
||||
|
||||
def check_inventory():
|
||||
if not lib.any_outdated():
|
||||
return
|
||||
|
||||
host = avalon.api.registered_host()
|
||||
outdated_containers = []
|
||||
for container in host.ls():
|
||||
representation = container['representation']
|
||||
representation_doc = io.find_one(
|
||||
{
|
||||
"_id": io.ObjectId(representation),
|
||||
"type": "representation"
|
||||
},
|
||||
projection={"parent": True}
|
||||
)
|
||||
if representation_doc and not lib.is_latest(representation_doc):
|
||||
outdated_containers.append(container)
|
||||
|
||||
# Colour nodes.
|
||||
func = """function func(args){
|
||||
for( var i =0; i <= args[0].length - 1; ++i)
|
||||
{
|
||||
var red_color = new ColorRGBA(255, 0, 0, 255);
|
||||
node.setColor(args[0][i], red_color);
|
||||
}
|
||||
}
|
||||
func
|
||||
"""
|
||||
outdated_nodes = []
|
||||
for container in outdated_containers:
|
||||
if container["loader"] == "ImageSequenceLoader":
|
||||
outdated_nodes.append(
|
||||
harmony.find_node_by_name(container["name"], "READ")
|
||||
)
|
||||
harmony.send({"function": func, "args": [outdated_nodes]})
|
||||
|
||||
# Warn about outdated containers.
|
||||
print("Starting new QApplication..")
|
||||
app = Qt.QtWidgets.QApplication(sys.argv)
|
||||
|
||||
message_box = Qt.QtWidgets.QMessageBox()
|
||||
message_box.setIcon(Qt.QtWidgets.QMessageBox.Warning)
|
||||
msg = "There are outdated containers in the scene."
|
||||
message_box.setText(msg)
|
||||
message_box.exec_()
|
||||
|
||||
# Garbage collect QApplication.
|
||||
del app
|
||||
|
||||
|
||||
def application_launch():
|
||||
ensure_scene_settings()
|
||||
check_inventory()
|
||||
|
||||
|
||||
def export_template(backdrops, nodes, filepath):
|
||||
func = """function func(args)
|
||||
{
|
||||
|
|
@ -161,7 +217,7 @@ def install():
|
|||
"instanceToggled", on_pyblish_instance_toggled
|
||||
)
|
||||
|
||||
api.on("application.launched", ensure_scene_settings)
|
||||
api.on("application.launched", application_launch)
|
||||
|
||||
|
||||
def on_pyblish_instance_toggled(instance, old_value, new_value):
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ from pype.api import Logger
|
|||
|
||||
log = Logger().get_logger(__name__, "nukestudio")
|
||||
|
||||
|
||||
def file_extensions():
|
||||
return [".hrox"]
|
||||
return api.HOST_WORKFILE_EXTENSIONS["nukestudio"]
|
||||
|
||||
|
||||
def has_unsaved_changes():
|
||||
|
|
|
|||
|
|
@ -1,9 +1,48 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
from avalon import api
|
||||
from avalon import api, io
|
||||
from avalon.vendor import Qt
|
||||
from pype import lib
|
||||
import pyblish.api
|
||||
|
||||
|
||||
def check_inventory():
|
||||
if not lib.any_outdated():
|
||||
return
|
||||
|
||||
host = api.registered_host()
|
||||
outdated_containers = []
|
||||
for container in host.ls():
|
||||
representation = container['representation']
|
||||
representation_doc = io.find_one(
|
||||
{
|
||||
"_id": io.ObjectId(representation),
|
||||
"type": "representation"
|
||||
},
|
||||
projection={"parent": True}
|
||||
)
|
||||
if representation_doc and not lib.is_latest(representation_doc):
|
||||
outdated_containers.append(container)
|
||||
|
||||
# Warn about outdated containers.
|
||||
print("Starting new QApplication..")
|
||||
app = Qt.QtWidgets.QApplication(sys.argv)
|
||||
|
||||
message_box = Qt.QtWidgets.QMessageBox()
|
||||
message_box.setIcon(Qt.QtWidgets.QMessageBox.Warning)
|
||||
msg = "There are outdated containers in the scene."
|
||||
message_box.setText(msg)
|
||||
message_box.exec_()
|
||||
|
||||
# Garbage collect QApplication.
|
||||
del app
|
||||
|
||||
|
||||
def application_launch():
|
||||
check_inventory()
|
||||
|
||||
|
||||
def install():
|
||||
print("Installing Pype config...")
|
||||
|
||||
|
|
@ -27,6 +66,8 @@ def install():
|
|||
"instanceToggled", on_pyblish_instance_toggled
|
||||
)
|
||||
|
||||
api.on("application.launched", application_launch)
|
||||
|
||||
|
||||
def on_pyblish_instance_toggled(instance, old_value, new_value):
|
||||
"""Toggle layer visibility on instance toggles."""
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from Qt import QtCore, QtGui, QtWidgets
|
||||
from avalon import style
|
||||
from pype.api import resources
|
||||
|
||||
|
||||
class MessageWidget(QtWidgets.QWidget):
|
||||
|
|
@ -19,8 +20,8 @@ class MessageWidget(QtWidgets.QWidget):
|
|||
if parent and hasattr(parent, 'icon'):
|
||||
self.setWindowIcon(parent.icon)
|
||||
else:
|
||||
from pypeapp.resources import get_resource
|
||||
self.setWindowIcon(QtGui.QIcon(get_resource('icon.png')))
|
||||
icon = QtGui.QIcon(resources.pype_icon_filepath())
|
||||
self.setWindowIcon(icon)
|
||||
|
||||
self.setWindowFlags(
|
||||
QtCore.Qt.WindowCloseButtonHint |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import os
|
||||
from Qt import QtCore, QtGui, QtWidgets
|
||||
from avalon import style
|
||||
from pype.api import resources
|
||||
|
||||
|
||||
class ClockifySettings(QtWidgets.QWidget):
|
||||
|
|
@ -26,10 +27,7 @@ class ClockifySettings(QtWidgets.QWidget):
|
|||
elif hasattr(parent, 'parent') and hasattr(parent.parent, 'icon'):
|
||||
self.setWindowIcon(self.parent.parent.icon)
|
||||
else:
|
||||
pype_setup = os.getenv('PYPE_SETUP_PATH')
|
||||
items = [pype_setup, "app", "resources", "icon.png"]
|
||||
fname = os.path.sep.join(items)
|
||||
icon = QtGui.QIcon(fname)
|
||||
icon = QtGui.QIcon(resources.pype_icon_filepath())
|
||||
self.setWindowIcon(icon)
|
||||
|
||||
self.setWindowFlags(
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ from pype.api import (
|
|||
compose_url
|
||||
)
|
||||
|
||||
from pype.modules.ftrack.lib.custom_db_connector import DbConnector
|
||||
from pype.modules.ftrack.lib.custom_db_connector import CustomDbConnector
|
||||
|
||||
|
||||
TOPIC_STATUS_SERVER = "pype.event.server.status"
|
||||
|
|
@ -44,15 +44,8 @@ def get_ftrack_event_mongo_info():
|
|||
mongo_url = os.environ.get("FTRACK_EVENTS_MONGO_URL")
|
||||
if mongo_url is not None:
|
||||
components = decompose_url(mongo_url)
|
||||
_used_ftrack_url = True
|
||||
else:
|
||||
components = get_default_components()
|
||||
_used_ftrack_url = False
|
||||
|
||||
if not _used_ftrack_url or components["database"] is None:
|
||||
components["database"] = database_name
|
||||
|
||||
components.pop("collection", None)
|
||||
|
||||
uri = compose_url(**components)
|
||||
|
||||
|
|
@ -166,10 +159,10 @@ class ProcessEventHub(SocketBaseEventHub):
|
|||
pypelog = Logger().get_logger("Session Processor")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.dbcon = DbConnector(
|
||||
self.dbcon = CustomDbConnector(
|
||||
self.uri,
|
||||
self.port,
|
||||
self.database,
|
||||
self.port,
|
||||
self.table_name
|
||||
)
|
||||
super(ProcessEventHub, self).__init__(*args, **kwargs)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from pype.modules.ftrack.ftrack_server.lib import (
|
|||
get_ftrack_event_mongo_info,
|
||||
TOPIC_STATUS_SERVER, TOPIC_STATUS_SERVER_RESULT
|
||||
)
|
||||
from pype.modules.ftrack.lib.custom_db_connector import DbConnector
|
||||
from pype.modules.ftrack.lib.custom_db_connector import CustomDbConnector
|
||||
from pype.api import Logger
|
||||
|
||||
log = Logger().get_logger("Event storer")
|
||||
|
|
@ -24,7 +24,7 @@ class SessionFactory:
|
|||
|
||||
|
||||
uri, port, database, table_name = get_ftrack_event_mongo_info()
|
||||
dbcon = DbConnector(uri, port, database, table_name)
|
||||
dbcon = CustomDbConnector(uri, database, port, table_name)
|
||||
|
||||
# ignore_topics = ["ftrack.meta.connected"]
|
||||
ignore_topics = []
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import time
|
|||
import logging
|
||||
import functools
|
||||
import atexit
|
||||
import os
|
||||
|
||||
# Third-party dependencies
|
||||
import pymongo
|
||||
|
|
@ -40,7 +41,7 @@ def auto_reconnect(func):
|
|||
|
||||
|
||||
def check_active_table(func):
|
||||
"""Check if DbConnector has active table before db method is called"""
|
||||
"""Check if CustomDbConnector has active collection."""
|
||||
@functools.wraps(func)
|
||||
def decorated(obj, *args, **kwargs):
|
||||
if not obj.active_table:
|
||||
|
|
@ -49,23 +50,12 @@ def check_active_table(func):
|
|||
return decorated
|
||||
|
||||
|
||||
def check_active_table(func):
|
||||
"""Handling auto reconnect in 3 retry times"""
|
||||
@functools.wraps(func)
|
||||
def decorated(obj, *args, **kwargs):
|
||||
if not obj.active_table:
|
||||
raise NotActiveTable("Active table is not set. (This is bug)")
|
||||
return func(obj, *args, **kwargs)
|
||||
|
||||
return decorated
|
||||
|
||||
|
||||
class DbConnector:
|
||||
class CustomDbConnector:
|
||||
log = logging.getLogger(__name__)
|
||||
timeout = 1000
|
||||
timeout = int(os.environ["AVALON_TIMEOUT"])
|
||||
|
||||
def __init__(
|
||||
self, uri, port=None, database_name=None, table_name=None
|
||||
self, uri, database_name, port=None, table_name=None
|
||||
):
|
||||
self._mongo_client = None
|
||||
self._sentry_client = None
|
||||
|
|
@ -78,9 +68,6 @@ class DbConnector:
|
|||
if port is None:
|
||||
port = components.get("port")
|
||||
|
||||
if database_name is None:
|
||||
database_name = components.get("database")
|
||||
|
||||
if database_name is None:
|
||||
raise ValueError(
|
||||
"Database is not defined for connection. {}".format(uri)
|
||||
|
|
@ -99,7 +86,7 @@ class DbConnector:
|
|||
# not all methods of PyMongo database are implemented with this it is
|
||||
# possible to use them too
|
||||
try:
|
||||
return super(DbConnector, self).__getattribute__(attr)
|
||||
return super(CustomDbConnector, self).__getattribute__(attr)
|
||||
except AttributeError:
|
||||
if self.active_table is None:
|
||||
raise NotActiveTable()
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ import copy
|
|||
import platform
|
||||
import avalon.lib
|
||||
import acre
|
||||
import getpass
|
||||
from pype import lib as pypelib
|
||||
from pype.api import config, Anatomy
|
||||
from .ftrack_action_handler import BaseAction
|
||||
from avalon.api import last_workfile, HOST_WORKFILE_EXTENSIONS
|
||||
|
||||
|
||||
class AppAction(BaseAction):
|
||||
|
|
@ -152,10 +154,11 @@ class AppAction(BaseAction):
|
|||
|
||||
hierarchy = ""
|
||||
asset_doc_parents = asset_document["data"].get("parents")
|
||||
if len(asset_doc_parents) > 0:
|
||||
if asset_doc_parents:
|
||||
hierarchy = os.path.join(*asset_doc_parents)
|
||||
|
||||
application = avalon.lib.get_application(self.identifier)
|
||||
host_name = application["application_dir"]
|
||||
data = {
|
||||
"project": {
|
||||
"name": entity["project"]["full_name"],
|
||||
|
|
@ -163,7 +166,7 @@ class AppAction(BaseAction):
|
|||
},
|
||||
"task": entity["name"],
|
||||
"asset": asset_name,
|
||||
"app": application["application_dir"],
|
||||
"app": host_name,
|
||||
"hierarchy": hierarchy
|
||||
}
|
||||
|
||||
|
|
@ -187,6 +190,21 @@ class AppAction(BaseAction):
|
|||
except FileExistsError:
|
||||
pass
|
||||
|
||||
last_workfile_path = None
|
||||
extensions = HOST_WORKFILE_EXTENSIONS.get(host_name)
|
||||
if extensions:
|
||||
# Find last workfile
|
||||
file_template = anatomy.templates["work"]["file"]
|
||||
data.update({
|
||||
"version": 1,
|
||||
"user": getpass.getuser(),
|
||||
"ext": extensions[0]
|
||||
})
|
||||
|
||||
last_workfile_path = last_workfile(
|
||||
workdir, file_template, data, extensions, True
|
||||
)
|
||||
|
||||
# set environments for Avalon
|
||||
prep_env = copy.deepcopy(os.environ)
|
||||
prep_env.update({
|
||||
|
|
@ -198,6 +216,8 @@ class AppAction(BaseAction):
|
|||
"AVALON_HIERARCHY": hierarchy,
|
||||
"AVALON_WORKDIR": workdir
|
||||
})
|
||||
if last_workfile_path:
|
||||
prep_env["AVALON_LAST_WORKFILE"] = last_workfile_path
|
||||
prep_env.update(anatomy.roots_obj.root_environments())
|
||||
|
||||
# collect all parents from the task
|
||||
|
|
@ -213,7 +233,6 @@ class AppAction(BaseAction):
|
|||
tools_env = acre.get_tools(tools_attr)
|
||||
env = acre.compute(tools_env)
|
||||
env = acre.merge(env, current_env=dict(prep_env))
|
||||
env = acre.append(dict(prep_env), env)
|
||||
|
||||
# Get path to execute
|
||||
st_temp_path = os.environ["PYPE_CONFIG"]
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import requests
|
|||
from avalon import style
|
||||
from pype.modules.ftrack import credentials
|
||||
from . import login_tools
|
||||
from pype.api import resources
|
||||
from Qt import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
|
|
@ -29,10 +30,7 @@ class Login_Dialog_ui(QtWidgets.QWidget):
|
|||
elif hasattr(parent, 'parent') and hasattr(parent.parent, 'icon'):
|
||||
self.setWindowIcon(self.parent.parent.icon)
|
||||
else:
|
||||
pype_setup = os.getenv('PYPE_SETUP_PATH')
|
||||
items = [pype_setup, "app", "resources", "icon.png"]
|
||||
fname = os.path.sep.join(items)
|
||||
icon = QtGui.QIcon(fname)
|
||||
icon = QtGui.QIcon(resources.pype_icon_filepath())
|
||||
self.setWindowIcon(icon)
|
||||
|
||||
self.setWindowFlags(
|
||||
|
|
|
|||
|
|
@ -1,26 +1,25 @@
|
|||
import time
|
||||
import collections
|
||||
from Qt import QtCore
|
||||
import threading
|
||||
from pynput import mouse, keyboard
|
||||
from pype.api import Logger
|
||||
|
||||
|
||||
class IdleManager(QtCore.QThread):
|
||||
class IdleManager(threading.Thread):
|
||||
""" Measure user's idle time in seconds.
|
||||
Idle time resets on keyboard/mouse input.
|
||||
Is able to emit signals at specific time idle.
|
||||
"""
|
||||
time_signals = collections.defaultdict(list)
|
||||
time_callbacks = collections.defaultdict(list)
|
||||
idle_time = 0
|
||||
signal_reset_timer = QtCore.Signal()
|
||||
|
||||
def __init__(self):
|
||||
super(IdleManager, self).__init__()
|
||||
self.log = Logger().get_logger(self.__class__.__name__)
|
||||
self.signal_reset_timer.connect(self._reset_time)
|
||||
self.qaction = None
|
||||
self.failed_icon = None
|
||||
self._is_running = False
|
||||
self.threads = []
|
||||
|
||||
def set_qaction(self, qaction, failed_icon):
|
||||
self.qaction = qaction
|
||||
|
|
@ -32,18 +31,18 @@ class IdleManager(QtCore.QThread):
|
|||
def tray_exit(self):
|
||||
self.stop()
|
||||
try:
|
||||
self.time_signals = {}
|
||||
self.time_callbacks = {}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def add_time_signal(self, emit_time, signal):
|
||||
""" If any module want to use IdleManager, need to use add_time_signal
|
||||
:param emit_time: time when signal will be emitted
|
||||
:type emit_time: int
|
||||
:param signal: signal that will be emitted (without objects)
|
||||
:type signal: QtCore.Signal
|
||||
def add_time_callback(self, emit_time, callback):
|
||||
"""If any module want to use IdleManager, need to use this method.
|
||||
|
||||
Args:
|
||||
emit_time(int): Time when callback will be triggered.
|
||||
callback(func): Callback that will be triggered.
|
||||
"""
|
||||
self.time_signals[emit_time].append(signal)
|
||||
self.time_callbacks[emit_time].append(callback)
|
||||
|
||||
@property
|
||||
def is_running(self):
|
||||
|
|
@ -58,17 +57,26 @@ class IdleManager(QtCore.QThread):
|
|||
def run(self):
|
||||
self.log.info('IdleManager has started')
|
||||
self._is_running = True
|
||||
thread_mouse = MouseThread(self.signal_reset_timer)
|
||||
thread_mouse = MouseThread(self._reset_time)
|
||||
thread_mouse.start()
|
||||
thread_keyboard = KeyboardThread(self.signal_reset_timer)
|
||||
thread_keyboard = KeyboardThread(self._reset_time)
|
||||
thread_keyboard.start()
|
||||
try:
|
||||
while self.is_running:
|
||||
if self.idle_time in self.time_callbacks:
|
||||
for callback in self.time_callbacks[self.idle_time]:
|
||||
thread = threading.Thread(target=callback)
|
||||
thread.start()
|
||||
self.threads.append(thread)
|
||||
|
||||
for thread in tuple(self.threads):
|
||||
if not thread.isAlive():
|
||||
thread.join()
|
||||
self.threads.remove(thread)
|
||||
|
||||
self.idle_time += 1
|
||||
if self.idle_time in self.time_signals:
|
||||
for signal in self.time_signals[self.idle_time]:
|
||||
signal.emit()
|
||||
time.sleep(1)
|
||||
|
||||
except Exception:
|
||||
self.log.warning(
|
||||
'Idle Manager service has failed', exc_info=True
|
||||
|
|
@ -79,16 +87,14 @@ class IdleManager(QtCore.QThread):
|
|||
|
||||
# Threads don't have their attrs when Qt application already finished
|
||||
try:
|
||||
thread_mouse.signal_stop.emit()
|
||||
thread_mouse.terminate()
|
||||
thread_mouse.wait()
|
||||
thread_mouse.stop()
|
||||
thread_mouse.join()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
thread_keyboard.signal_stop.emit()
|
||||
thread_keyboard.terminate()
|
||||
thread_keyboard.wait()
|
||||
thread_keyboard.stop()
|
||||
thread_keyboard.join()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
|
@ -96,49 +102,24 @@ class IdleManager(QtCore.QThread):
|
|||
self.log.info('IdleManager has stopped')
|
||||
|
||||
|
||||
class MouseThread(QtCore.QThread):
|
||||
"""Listens user's mouse movement
|
||||
"""
|
||||
signal_stop = QtCore.Signal()
|
||||
class MouseThread(mouse.Listener):
|
||||
"""Listens user's mouse movement."""
|
||||
|
||||
def __init__(self, signal):
|
||||
super(MouseThread, self).__init__()
|
||||
self.signal_stop.connect(self.stop)
|
||||
self.m_listener = None
|
||||
|
||||
self.signal_reset_timer = signal
|
||||
|
||||
def stop(self):
|
||||
if self.m_listener is not None:
|
||||
self.m_listener.stop()
|
||||
def __init__(self, callback):
|
||||
super(MouseThread, self).__init__(on_move=self.on_move)
|
||||
self.callback = callback
|
||||
|
||||
def on_move(self, posx, posy):
|
||||
self.signal_reset_timer.emit()
|
||||
|
||||
def run(self):
|
||||
self.m_listener = mouse.Listener(on_move=self.on_move)
|
||||
self.m_listener.start()
|
||||
self.callback()
|
||||
|
||||
|
||||
class KeyboardThread(QtCore.QThread):
|
||||
"""Listens user's keyboard input
|
||||
"""
|
||||
signal_stop = QtCore.Signal()
|
||||
class KeyboardThread(keyboard.Listener):
|
||||
"""Listens user's keyboard input."""
|
||||
|
||||
def __init__(self, signal):
|
||||
super(KeyboardThread, self).__init__()
|
||||
self.signal_stop.connect(self.stop)
|
||||
self.k_listener = None
|
||||
def __init__(self, callback):
|
||||
super(KeyboardThread, self).__init__(on_press=self.on_press)
|
||||
|
||||
self.signal_reset_timer = signal
|
||||
|
||||
def stop(self):
|
||||
if self.k_listener is not None:
|
||||
self.k_listener.stop()
|
||||
self.callback = callback
|
||||
|
||||
def on_press(self, key):
|
||||
self.signal_reset_timer.emit()
|
||||
|
||||
def run(self):
|
||||
self.k_listener = keyboard.Listener(on_press=self.on_press)
|
||||
self.k_listener.start()
|
||||
self.callback()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import os
|
||||
from Qt import QtCore, QtGui, QtWidgets
|
||||
from avalon import style
|
||||
from pype.api import resources
|
||||
|
||||
|
||||
class MusterLogin(QtWidgets.QWidget):
|
||||
|
|
@ -23,10 +24,7 @@ class MusterLogin(QtWidgets.QWidget):
|
|||
elif hasattr(parent, 'parent') and hasattr(parent.parent, 'icon'):
|
||||
self.setWindowIcon(parent.parent.icon)
|
||||
else:
|
||||
pype_setup = os.getenv('PYPE_SETUP_PATH')
|
||||
items = [pype_setup, "app", "resources", "icon.png"]
|
||||
fname = os.path.sep.join(items)
|
||||
icon = QtGui.QIcon(fname)
|
||||
icon = QtGui.QIcon(resources.pype_icon_filepath())
|
||||
self.setWindowIcon(icon)
|
||||
|
||||
self.setWindowFlags(
|
||||
|
|
|
|||
|
|
@ -10,10 +10,37 @@ from . import DropEmpty, ComponentsList, ComponentItem
|
|||
|
||||
|
||||
class DropDataFrame(QtWidgets.QFrame):
|
||||
image_extensions = [
|
||||
".ani", ".anim", ".apng", ".art", ".bmp", ".bpg", ".bsave", ".cal",
|
||||
".cin", ".cpc", ".cpt", ".dds", ".dpx", ".ecw", ".exr", ".fits",
|
||||
".flic", ".flif", ".fpx", ".gif", ".hdri", ".hevc", ".icer",
|
||||
".icns", ".ico", ".cur", ".ics", ".ilbm", ".jbig", ".jbig2",
|
||||
".jng", ".jpeg", ".jpeg-ls", ".jpeg", ".2000", ".jpg", ".xr",
|
||||
".jpeg", ".xt", ".jpeg-hdr", ".kra", ".mng", ".miff", ".nrrd",
|
||||
".ora", ".pam", ".pbm", ".pgm", ".ppm", ".pnm", ".pcx", ".pgf",
|
||||
".pictor", ".png", ".psd", ".psb", ".psp", ".qtvr", ".ras",
|
||||
".rgbe", ".logluv", ".tiff", ".sgi", ".tga", ".tiff", ".tiff/ep",
|
||||
".tiff/it", ".ufo", ".ufp", ".wbmp", ".webp", ".xbm", ".xcf",
|
||||
".xpm", ".xwd"
|
||||
]
|
||||
video_extensions = [
|
||||
".3g2", ".3gp", ".amv", ".asf", ".avi", ".drc", ".f4a", ".f4b",
|
||||
".f4p", ".f4v", ".flv", ".gif", ".gifv", ".m2v", ".m4p", ".m4v",
|
||||
".mkv", ".mng", ".mov", ".mp2", ".mp4", ".mpe", ".mpeg", ".mpg",
|
||||
".mpv", ".mxf", ".nsv", ".ogg", ".ogv", ".qt", ".rm", ".rmvb",
|
||||
".roq", ".svi", ".vob", ".webm", ".wmv", ".yuv"
|
||||
]
|
||||
extensions = {
|
||||
"nuke": [".nk"],
|
||||
"maya": [".ma", ".mb"],
|
||||
"houdini": [".hip"],
|
||||
"image_file": image_extensions,
|
||||
"video_file": video_extensions
|
||||
}
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__()
|
||||
self.parent_widget = parent
|
||||
self.presets = config.get_presets()['standalone_publish']
|
||||
|
||||
self.setAcceptDrops(True)
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
|
|
@ -26,7 +53,9 @@ class DropDataFrame(QtWidgets.QFrame):
|
|||
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.drop_widget.sizePolicy().hasHeightForWidth())
|
||||
sizePolicy.setHeightForWidth(
|
||||
self.drop_widget.sizePolicy().hasHeightForWidth()
|
||||
)
|
||||
self.drop_widget.setSizePolicy(sizePolicy)
|
||||
|
||||
layout.addWidget(self.drop_widget)
|
||||
|
|
@ -255,8 +284,8 @@ class DropDataFrame(QtWidgets.QFrame):
|
|||
file_info = data['file_info']
|
||||
|
||||
if (
|
||||
ext in self.presets['extensions']['image_file'] or
|
||||
ext in self.presets['extensions']['video_file']
|
||||
ext in self.image_extensions
|
||||
or ext in self.video_extensions
|
||||
):
|
||||
probe_data = self.load_data_with_probe(filepath)
|
||||
if 'fps' not in data:
|
||||
|
|
@ -293,7 +322,7 @@ class DropDataFrame(QtWidgets.QFrame):
|
|||
data[key] = value
|
||||
|
||||
icon = 'default'
|
||||
for ico, exts in self.presets['extensions'].items():
|
||||
for ico, exts in self.extensions.items():
|
||||
if ext in exts:
|
||||
icon = ico
|
||||
break
|
||||
|
|
@ -304,17 +333,16 @@ class DropDataFrame(QtWidgets.QFrame):
|
|||
icon += 's'
|
||||
data['icon'] = icon
|
||||
data['thumb'] = (
|
||||
ext in self.presets['extensions']['image_file'] or
|
||||
ext in self.presets['extensions']['video_file']
|
||||
ext in self.image_extensions
|
||||
or ext in self.video_extensions
|
||||
)
|
||||
data['prev'] = (
|
||||
ext in self.presets['extensions']['video_file'] or
|
||||
(new_is_seq and ext in self.presets['extensions']['image_file'])
|
||||
ext in self.video_extensions
|
||||
or (new_is_seq and ext in self.image_extensions)
|
||||
)
|
||||
|
||||
actions = []
|
||||
|
||||
|
||||
found = False
|
||||
for item in self.components_list.widgets():
|
||||
if data['ext'] != item.in_data['ext']:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
from Qt import QtCore
|
||||
from .widget_user_idle import WidgetUserIdle
|
||||
from .widget_user_idle import WidgetUserIdle, SignalHandler
|
||||
from pype.api import Logger, config
|
||||
|
||||
|
||||
|
|
@ -31,7 +30,10 @@ class TimersManager(metaclass=Singleton):
|
|||
self.log = Logger().get_logger(self.__class__.__name__)
|
||||
self.tray_widget = tray_widget
|
||||
self.main_widget = main_widget
|
||||
self.widget_user_idle = WidgetUserIdle(self)
|
||||
|
||||
self.idle_man = None
|
||||
self.signal_handler = None
|
||||
self.widget_user_idle = WidgetUserIdle(self, tray_widget)
|
||||
|
||||
def set_signal_times(self):
|
||||
try:
|
||||
|
|
@ -114,49 +116,59 @@ class TimersManager(metaclass=Singleton):
|
|||
:param modules: All imported modules from TrayManager
|
||||
:type modules: dict
|
||||
"""
|
||||
self.s_handler = SignalHandler(self)
|
||||
|
||||
if 'IdleManager' in modules:
|
||||
self.signal_handler = SignalHandler(self)
|
||||
if self.set_signal_times() is True:
|
||||
self.register_to_idle_manager(modules['IdleManager'])
|
||||
|
||||
def time_callback(self, int_def):
|
||||
if not self.signal_handler:
|
||||
return
|
||||
|
||||
if int_def == 0:
|
||||
self.signal_handler.signal_show_message.emit()
|
||||
elif int_def == 1:
|
||||
self.signal_handler.signal_change_label.emit()
|
||||
elif int_def == 2:
|
||||
self.signal_handler.signal_stop_timers.emit()
|
||||
|
||||
def register_to_idle_manager(self, man_obj):
|
||||
self.idle_man = man_obj
|
||||
|
||||
# Time when message is shown
|
||||
self.idle_man.add_time_callback(
|
||||
self.time_show_message,
|
||||
lambda: self.time_callback(0)
|
||||
)
|
||||
|
||||
# Times when idle is between show widget and stop timers
|
||||
show_to_stop_range = range(
|
||||
self.time_show_message-1, self.time_stop_timer
|
||||
self.time_show_message - 1, self.time_stop_timer
|
||||
)
|
||||
for num in show_to_stop_range:
|
||||
self.idle_man.add_time_signal(
|
||||
num,
|
||||
self.s_handler.signal_change_label
|
||||
self.idle_man.add_time_callback(
|
||||
num, lambda: self.time_callback(1)
|
||||
)
|
||||
# Times when widget is already shown and user restart idle
|
||||
shown_and_moved_range = range(
|
||||
self.time_stop_timer - self.time_show_message
|
||||
)
|
||||
for num in shown_and_moved_range:
|
||||
self.idle_man.add_time_signal(
|
||||
num,
|
||||
self.s_handler.signal_change_label
|
||||
self.idle_man.add_time_callback(
|
||||
num, lambda: self.time_callback(1)
|
||||
)
|
||||
# Time when message is shown
|
||||
self.idle_man.add_time_signal(
|
||||
self.time_show_message,
|
||||
self.s_handler.signal_show_message
|
||||
)
|
||||
|
||||
# Time when timers are stopped
|
||||
self.idle_man.add_time_signal(
|
||||
self.idle_man.add_time_callback(
|
||||
self.time_stop_timer,
|
||||
self.s_handler.signal_stop_timers
|
||||
lambda: self.time_callback(2)
|
||||
)
|
||||
|
||||
def change_label(self):
|
||||
if self.is_running is False:
|
||||
return
|
||||
if self.widget_user_idle.bool_is_showed is False:
|
||||
return
|
||||
if not hasattr(self, 'idle_man'):
|
||||
if not self.idle_man or self.widget_user_idle.bool_is_showed is False:
|
||||
return
|
||||
|
||||
if self.idle_man.idle_time > self.time_show_message:
|
||||
|
|
@ -174,14 +186,3 @@ class TimersManager(metaclass=Singleton):
|
|||
return
|
||||
if self.widget_user_idle.bool_is_showed is False:
|
||||
self.widget_user_idle.show()
|
||||
|
||||
|
||||
class SignalHandler(QtCore.QObject):
|
||||
signal_show_message = QtCore.Signal()
|
||||
signal_change_label = QtCore.Signal()
|
||||
signal_stop_timers = QtCore.Signal()
|
||||
def __init__(self, cls):
|
||||
super().__init__()
|
||||
self.signal_show_message.connect(cls.show_message)
|
||||
self.signal_change_label.connect(cls.change_label)
|
||||
self.signal_stop_timers.connect(cls.stop_timers)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
from pype.api import Logger
|
||||
from avalon import style
|
||||
from Qt import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
|
@ -8,18 +7,18 @@ class WidgetUserIdle(QtWidgets.QWidget):
|
|||
SIZE_W = 300
|
||||
SIZE_H = 160
|
||||
|
||||
def __init__(self, parent):
|
||||
def __init__(self, module, tray_widget):
|
||||
|
||||
super(WidgetUserIdle, self).__init__()
|
||||
|
||||
self.bool_is_showed = False
|
||||
self.bool_not_stopped = True
|
||||
|
||||
self.parent_widget = parent
|
||||
self.setWindowIcon(parent.tray_widget.icon)
|
||||
self.module = module
|
||||
self.setWindowIcon(tray_widget.icon)
|
||||
self.setWindowFlags(
|
||||
QtCore.Qt.WindowCloseButtonHint |
|
||||
QtCore.Qt.WindowMinimizeButtonHint
|
||||
QtCore.Qt.WindowCloseButtonHint
|
||||
| QtCore.Qt.WindowMinimizeButtonHint
|
||||
)
|
||||
|
||||
self._translate = QtCore.QCoreApplication.translate
|
||||
|
|
@ -129,11 +128,11 @@ class WidgetUserIdle(QtWidgets.QWidget):
|
|||
self.lbl_rest_time.setText(str_time)
|
||||
|
||||
def stop_timer(self):
|
||||
self.parent_widget.stop_timers()
|
||||
self.module.stop_timers()
|
||||
self.close_widget()
|
||||
|
||||
def restart_timer(self):
|
||||
self.parent_widget.restart_timers()
|
||||
self.module.restart_timers()
|
||||
self.close_widget()
|
||||
|
||||
def continue_timer(self):
|
||||
|
|
@ -154,3 +153,15 @@ class WidgetUserIdle(QtWidgets.QWidget):
|
|||
|
||||
def showEvent(self, event):
|
||||
self.bool_is_showed = True
|
||||
|
||||
|
||||
class SignalHandler(QtCore.QObject):
|
||||
signal_show_message = QtCore.Signal()
|
||||
signal_change_label = QtCore.Signal()
|
||||
signal_stop_timers = QtCore.Signal()
|
||||
|
||||
def __init__(self, cls):
|
||||
super().__init__()
|
||||
self.signal_show_message.connect(cls.show_message)
|
||||
self.signal_change_label.connect(cls.change_label)
|
||||
self.signal_stop_timers.connect(cls.stop_timers)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from Qt import QtCore, QtGui, QtWidgets
|
||||
from pype.resources import get_resource
|
||||
from avalon import style
|
||||
from pype.api import resources
|
||||
|
||||
|
||||
class UserWidget(QtWidgets.QWidget):
|
||||
|
|
@ -14,7 +14,7 @@ class UserWidget(QtWidgets.QWidget):
|
|||
self.module = module
|
||||
|
||||
# Style
|
||||
icon = QtGui.QIcon(get_resource("icon.png"))
|
||||
icon = QtGui.QIcon(resources.pype_icon_filepath())
|
||||
self.setWindowIcon(icon)
|
||||
self.setWindowTitle("Username Settings")
|
||||
self.setMinimumWidth(self.MIN_WIDTH)
|
||||
|
|
|
|||
|
|
@ -7,20 +7,11 @@ from typing import Dict, List, Optional
|
|||
|
||||
from avalon import api, blender
|
||||
import bpy
|
||||
import pype.hosts.blender.plugin
|
||||
import pype.hosts.blender.plugin as plugin
|
||||
|
||||
|
||||
logger = logging.getLogger("pype").getChild(
|
||||
"blender").getChild("load_layout")
|
||||
|
||||
|
||||
class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader):
|
||||
"""Load animations from a .blend file.
|
||||
|
||||
Warning:
|
||||
Loading the same asset more then once is not properly supported at the
|
||||
moment.
|
||||
"""
|
||||
class BlendLayoutLoader(plugin.AssetLoader):
|
||||
"""Load layout from a .blend file."""
|
||||
|
||||
families = ["layout"]
|
||||
representations = ["blend"]
|
||||
|
|
@ -29,24 +20,25 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
icon = "code-fork"
|
||||
color = "orange"
|
||||
|
||||
def _remove(self, objects, lib_container):
|
||||
|
||||
def _remove(self, objects, obj_container):
|
||||
for obj in objects:
|
||||
|
||||
if obj.type == 'ARMATURE':
|
||||
bpy.data.armatures.remove(obj.data)
|
||||
elif obj.type == 'MESH':
|
||||
bpy.data.meshes.remove(obj.data)
|
||||
elif obj.type == 'CAMERA':
|
||||
bpy.data.cameras.remove(obj.data)
|
||||
elif obj.type == 'CURVE':
|
||||
bpy.data.curves.remove(obj.data)
|
||||
|
||||
for element_container in bpy.data.collections[lib_container].children:
|
||||
for element_container in obj_container.children:
|
||||
for child in element_container.children:
|
||||
bpy.data.collections.remove(child)
|
||||
bpy.data.collections.remove(element_container)
|
||||
|
||||
bpy.data.collections.remove(bpy.data.collections[lib_container])
|
||||
bpy.data.collections.remove(obj_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
|
||||
|
|
@ -58,26 +50,38 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
scene.collection.children.link(bpy.data.collections[lib_container])
|
||||
|
||||
layout_container = scene.collection.children[lib_container].make_local()
|
||||
layout_container.name = container_name
|
||||
|
||||
meshes = []
|
||||
objects_local_types = ['MESH', 'CAMERA', 'CURVE']
|
||||
|
||||
objects = []
|
||||
armatures = []
|
||||
|
||||
objects_list = []
|
||||
containers = list(layout_container.children)
|
||||
|
||||
for element_container in layout_container.children:
|
||||
element_container.make_local()
|
||||
meshes.extend([obj for obj in element_container.objects if obj.type == 'MESH'])
|
||||
armatures.extend([obj for obj in element_container.objects if obj.type == 'ARMATURE'])
|
||||
for child in element_container.children:
|
||||
child.make_local()
|
||||
meshes.extend(child.objects)
|
||||
for container in layout_container.children:
|
||||
if container.name == blender.pipeline.AVALON_CONTAINERS:
|
||||
containers.remove(container)
|
||||
|
||||
for container in containers:
|
||||
container.make_local()
|
||||
objects.extend([
|
||||
obj for obj in container.objects
|
||||
if obj.type in objects_local_types
|
||||
])
|
||||
armatures.extend([
|
||||
obj for obj in container.objects
|
||||
if obj.type == 'ARMATURE'
|
||||
])
|
||||
containers.extend(list(container.children))
|
||||
|
||||
# Link meshes first, then armatures.
|
||||
# The armature is unparented for all the non-local meshes,
|
||||
# when it is made local.
|
||||
for obj in meshes + armatures:
|
||||
obj = obj.make_local()
|
||||
obj.data.make_local()
|
||||
for obj in objects + armatures:
|
||||
obj.make_local()
|
||||
if obj.data:
|
||||
obj.data.make_local()
|
||||
|
||||
if not obj.get(blender.pipeline.AVALON_PROPERTY):
|
||||
obj[blender.pipeline.AVALON_PROPERTY] = dict()
|
||||
|
|
@ -85,18 +89,16 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
avalon_info = obj[blender.pipeline.AVALON_PROPERTY]
|
||||
avalon_info.update({"container_name": container_name})
|
||||
|
||||
action = actions.get( obj.name, None )
|
||||
action = actions.get(obj.name, None)
|
||||
|
||||
if obj.type == 'ARMATURE' and action is not None:
|
||||
obj.animation_data.action = action
|
||||
|
||||
objects_list.append(obj)
|
||||
|
||||
layout_container.pop(blender.pipeline.AVALON_PROPERTY)
|
||||
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
|
||||
return objects_list
|
||||
return layout_container
|
||||
|
||||
def process_asset(
|
||||
self, context: dict, name: str, namespace: Optional[str] = None,
|
||||
|
|
@ -113,9 +115,15 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
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
|
||||
lib_container = plugin.asset_name(
|
||||
asset, subset
|
||||
)
|
||||
unique_number = plugin.get_unique_number(
|
||||
asset, subset
|
||||
)
|
||||
namespace = namespace or f"{asset}_{unique_number}"
|
||||
container_name = plugin.asset_name(
|
||||
asset, subset, unique_number
|
||||
)
|
||||
|
||||
container = bpy.data.collections.new(lib_container)
|
||||
|
|
@ -134,11 +142,13 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
container_metadata["libpath"] = libpath
|
||||
container_metadata["lib_container"] = lib_container
|
||||
|
||||
objects_list = self._process(
|
||||
obj_container = self._process(
|
||||
libpath, lib_container, container_name, {})
|
||||
|
||||
container_metadata["obj_container"] = obj_container
|
||||
|
||||
# Save the list of objects in the metadata container
|
||||
container_metadata["objects"] = objects_list
|
||||
container_metadata["objects"] = obj_container.all_objects
|
||||
|
||||
nodes = list(container.objects)
|
||||
nodes.append(container)
|
||||
|
|
@ -157,7 +167,6 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
Warning:
|
||||
No nested collections are supported at the moment!
|
||||
"""
|
||||
|
||||
collection = bpy.data.collections.get(
|
||||
container["objectName"]
|
||||
)
|
||||
|
|
@ -165,7 +174,7 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
libpath = Path(api.get_representation_path(representation))
|
||||
extension = libpath.suffix.lower()
|
||||
|
||||
logger.info(
|
||||
self.log.info(
|
||||
"Container: %s\nRepresentation: %s",
|
||||
pformat(container, indent=2),
|
||||
pformat(representation, indent=2),
|
||||
|
|
@ -189,41 +198,40 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
|
||||
collection_metadata = collection.get(
|
||||
blender.pipeline.AVALON_PROPERTY)
|
||||
|
||||
collection_libpath = collection_metadata["libpath"]
|
||||
objects = collection_metadata["objects"]
|
||||
lib_container = collection_metadata["lib_container"]
|
||||
obj_container = collection_metadata["obj_container"]
|
||||
|
||||
normalized_collection_libpath = (
|
||||
str(Path(bpy.path.abspath(collection_libpath)).resolve())
|
||||
)
|
||||
normalized_libpath = (
|
||||
str(Path(bpy.path.abspath(str(libpath))).resolve())
|
||||
)
|
||||
logger.debug(
|
||||
self.log.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...")
|
||||
self.log.info("Library already loaded, not updating...")
|
||||
return
|
||||
|
||||
objects = collection_metadata["objects"]
|
||||
lib_container = collection_metadata["lib_container"]
|
||||
|
||||
actions = {}
|
||||
|
||||
for obj in objects:
|
||||
|
||||
if obj.type == 'ARMATURE':
|
||||
|
||||
actions[obj.name] = obj.animation_data.action
|
||||
|
||||
self._remove(objects, lib_container)
|
||||
self._remove(objects, obj_container)
|
||||
|
||||
objects_list = self._process(
|
||||
obj_container = 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["obj_container"] = obj_container
|
||||
collection_metadata["objects"] = obj_container.all_objects
|
||||
collection_metadata["libpath"] = str(libpath)
|
||||
collection_metadata["representation"] = str(representation["_id"])
|
||||
|
||||
|
|
@ -255,9 +263,9 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
collection_metadata = collection.get(
|
||||
blender.pipeline.AVALON_PROPERTY)
|
||||
objects = collection_metadata["objects"]
|
||||
lib_container = collection_metadata["lib_container"]
|
||||
obj_container = collection_metadata["obj_container"]
|
||||
|
||||
self._remove(objects, lib_container)
|
||||
self._remove(objects, obj_container)
|
||||
|
||||
bpy.data.collections.remove(collection)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,20 +7,14 @@ 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_model")
|
||||
import pype.hosts.blender.plugin as plugin
|
||||
|
||||
|
||||
class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader):
|
||||
class BlendModelLoader(plugin.AssetLoader):
|
||||
"""Load models from a .blend file.
|
||||
|
||||
Because they come from a .blend file we can simply link the collection that
|
||||
contains the model. There is no further need to 'containerise' it.
|
||||
|
||||
Warning:
|
||||
Loading the same asset more then once is not properly supported at the
|
||||
moment.
|
||||
"""
|
||||
|
||||
families = ["model"]
|
||||
|
|
@ -30,54 +24,52 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
icon = "code-fork"
|
||||
color = "orange"
|
||||
|
||||
def _remove(self, objects, lib_container):
|
||||
|
||||
def _remove(self, objects, container):
|
||||
for obj in objects:
|
||||
|
||||
for material_slot in obj.material_slots:
|
||||
bpy.data.materials.remove(material_slot.material)
|
||||
bpy.data.meshes.remove(obj.data)
|
||||
|
||||
bpy.data.collections.remove(bpy.data.collections[lib_container])
|
||||
|
||||
def _process(self, libpath, lib_container, container_name):
|
||||
bpy.data.collections.remove(container)
|
||||
|
||||
def _process(
|
||||
self, libpath, lib_container, container_name,
|
||||
parent_collection
|
||||
):
|
||||
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
|
||||
parent = parent_collection
|
||||
|
||||
scene.collection.children.link(bpy.data.collections[lib_container])
|
||||
if parent is None:
|
||||
parent = bpy.context.scene.collection
|
||||
|
||||
model_container = scene.collection.children[lib_container].make_local()
|
||||
parent.children.link(bpy.data.collections[lib_container])
|
||||
|
||||
objects_list = []
|
||||
model_container = parent.children[lib_container].make_local()
|
||||
model_container.name = container_name
|
||||
|
||||
for obj in model_container.objects:
|
||||
|
||||
obj = obj.make_local()
|
||||
|
||||
obj.data.make_local()
|
||||
plugin.prepare_data(obj, container_name)
|
||||
plugin.prepare_data(obj.data, container_name)
|
||||
|
||||
for material_slot in obj.material_slots:
|
||||
|
||||
material_slot.material.make_local()
|
||||
plugin.prepare_data(material_slot.material, container_name)
|
||||
|
||||
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})
|
||||
|
||||
objects_list.append(obj)
|
||||
|
||||
model_container.pop(blender.pipeline.AVALON_PROPERTY)
|
||||
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
|
||||
return objects_list
|
||||
return model_container
|
||||
|
||||
def process_asset(
|
||||
self, context: dict, name: str, namespace: Optional[str] = None,
|
||||
|
|
@ -94,35 +86,44 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
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
|
||||
|
||||
lib_container = plugin.asset_name(
|
||||
asset, subset
|
||||
)
|
||||
unique_number = plugin.get_unique_number(
|
||||
asset, subset
|
||||
)
|
||||
namespace = namespace or f"{asset}_{unique_number}"
|
||||
container_name = plugin.asset_name(
|
||||
asset, subset, unique_number
|
||||
)
|
||||
|
||||
collection = bpy.data.collections.new(lib_container)
|
||||
collection.name = container_name
|
||||
container = bpy.data.collections.new(lib_container)
|
||||
container.name = container_name
|
||||
blender.pipeline.containerise_existing(
|
||||
collection,
|
||||
container,
|
||||
name,
|
||||
namespace,
|
||||
context,
|
||||
self.__class__.__name__,
|
||||
)
|
||||
|
||||
container_metadata = collection.get(
|
||||
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)
|
||||
obj_container = self._process(
|
||||
libpath, lib_container, container_name, None)
|
||||
|
||||
container_metadata["obj_container"] = obj_container
|
||||
|
||||
# Save the list of objects in the metadata container
|
||||
container_metadata["objects"] = objects_list
|
||||
container_metadata["objects"] = obj_container.all_objects
|
||||
|
||||
nodes = list(collection.objects)
|
||||
nodes.append(collection)
|
||||
nodes = list(container.objects)
|
||||
nodes.append(container)
|
||||
self[:] = nodes
|
||||
return nodes
|
||||
|
||||
|
|
@ -144,7 +145,7 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
libpath = Path(api.get_representation_path(representation))
|
||||
extension = libpath.suffix.lower()
|
||||
|
||||
logger.debug(
|
||||
self.log.info(
|
||||
"Container: %s\nRepresentation: %s",
|
||||
pformat(container, indent=2),
|
||||
pformat(representation, indent=2),
|
||||
|
|
@ -162,38 +163,47 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
assert libpath.is_file(), (
|
||||
f"The file doesn't exist: {libpath}"
|
||||
)
|
||||
assert extension in pype.hosts.blender.plugin.VALID_EXTENSIONS, (
|
||||
assert extension in 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"]
|
||||
|
||||
obj_container = plugin.get_local_collection_with_name(
|
||||
collection_metadata["obj_container"].name
|
||||
)
|
||||
objects = obj_container.all_objects
|
||||
|
||||
container_name = obj_container.name
|
||||
|
||||
normalized_collection_libpath = (
|
||||
str(Path(bpy.path.abspath(collection_libpath)).resolve())
|
||||
)
|
||||
normalized_libpath = (
|
||||
str(Path(bpy.path.abspath(str(libpath))).resolve())
|
||||
)
|
||||
logger.debug(
|
||||
self.log.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...")
|
||||
self.log.info("Library already loaded, not updating...")
|
||||
return
|
||||
|
||||
self._remove(objects, lib_container)
|
||||
parent = plugin.get_parent_collection(obj_container)
|
||||
|
||||
objects_list = self._process(
|
||||
str(libpath), lib_container, collection.name)
|
||||
self._remove(objects, obj_container)
|
||||
|
||||
obj_container = self._process(
|
||||
str(libpath), lib_container, container_name, parent)
|
||||
|
||||
# Save the list of objects in the metadata container
|
||||
collection_metadata["objects"] = objects_list
|
||||
collection_metadata["obj_container"] = obj_container
|
||||
collection_metadata["objects"] = obj_container.all_objects
|
||||
collection_metadata["libpath"] = str(libpath)
|
||||
collection_metadata["representation"] = str(representation["_id"])
|
||||
|
||||
|
|
@ -221,17 +231,20 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
|
||||
collection_metadata = collection.get(
|
||||
blender.pipeline.AVALON_PROPERTY)
|
||||
objects = collection_metadata["objects"]
|
||||
lib_container = collection_metadata["lib_container"]
|
||||
|
||||
self._remove(objects, lib_container)
|
||||
obj_container = plugin.get_local_collection_with_name(
|
||||
collection_metadata["obj_container"].name
|
||||
)
|
||||
objects = obj_container.all_objects
|
||||
|
||||
self._remove(objects, obj_container)
|
||||
|
||||
bpy.data.collections.remove(collection)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class CacheModelLoader(pype.hosts.blender.plugin.AssetLoader):
|
||||
class CacheModelLoader(plugin.AssetLoader):
|
||||
"""Load cache models.
|
||||
|
||||
Stores the imported asset in a collection named after the asset.
|
||||
|
|
@ -267,7 +280,7 @@ class CacheModelLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
subset = context["subset"]["name"]
|
||||
# TODO (jasper): evaluate use of namespace which is 'alien' to Blender.
|
||||
lib_container = container_name = (
|
||||
pype.hosts.blender.plugin.asset_name(asset, subset, namespace)
|
||||
plugin.asset_name(asset, subset, namespace)
|
||||
)
|
||||
relative = bpy.context.preferences.filepaths.use_relative_paths
|
||||
|
||||
|
|
|
|||
|
|
@ -7,20 +7,14 @@ 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_model")
|
||||
import pype.hosts.blender.plugin as plugin
|
||||
|
||||
|
||||
class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader):
|
||||
class BlendRigLoader(plugin.AssetLoader):
|
||||
"""Load rigs from a .blend file.
|
||||
|
||||
Because they come from a .blend file we can simply link the collection that
|
||||
contains the model. There is no further need to 'containerise' it.
|
||||
|
||||
Warning:
|
||||
Loading the same asset more then once is not properly supported at the
|
||||
moment.
|
||||
"""
|
||||
|
||||
families = ["rig"]
|
||||
|
|
@ -30,50 +24,54 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
icon = "code-fork"
|
||||
color = "orange"
|
||||
|
||||
def _remove(self, objects, lib_container):
|
||||
|
||||
def _remove(self, objects, obj_container):
|
||||
for obj in objects:
|
||||
|
||||
if obj.type == 'ARMATURE':
|
||||
bpy.data.armatures.remove(obj.data)
|
||||
elif obj.type == 'MESH':
|
||||
bpy.data.meshes.remove(obj.data)
|
||||
|
||||
for child in bpy.data.collections[lib_container].children:
|
||||
for child in obj_container.children:
|
||||
bpy.data.collections.remove(child)
|
||||
|
||||
bpy.data.collections.remove(bpy.data.collections[lib_container])
|
||||
|
||||
def _process(self, libpath, lib_container, container_name, action):
|
||||
bpy.data.collections.remove(obj_container)
|
||||
|
||||
def _process(
|
||||
self, libpath, lib_container, container_name,
|
||||
action, parent_collection
|
||||
):
|
||||
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
|
||||
parent = parent_collection
|
||||
|
||||
scene.collection.children.link(bpy.data.collections[lib_container])
|
||||
if parent is None:
|
||||
parent = bpy.context.scene.collection
|
||||
|
||||
rig_container = scene.collection.children[lib_container].make_local()
|
||||
parent.children.link(bpy.data.collections[lib_container])
|
||||
|
||||
rig_container = parent.children[lib_container].make_local()
|
||||
rig_container.name = container_name
|
||||
|
||||
meshes = []
|
||||
armatures = [
|
||||
obj for obj in rig_container.objects if obj.type == 'ARMATURE']
|
||||
|
||||
objects_list = []
|
||||
obj for obj in rig_container.objects
|
||||
if obj.type == 'ARMATURE'
|
||||
]
|
||||
|
||||
for child in rig_container.children:
|
||||
child.make_local()
|
||||
meshes.extend( child.objects )
|
||||
plugin.prepare_data(child, container_name)
|
||||
meshes.extend(child.objects)
|
||||
|
||||
# Link meshes first, then armatures.
|
||||
# The armature is unparented for all the non-local meshes,
|
||||
# when it is made local.
|
||||
for obj in meshes + armatures:
|
||||
obj = obj.make_local()
|
||||
obj.data.make_local()
|
||||
plugin.prepare_data(obj, container_name)
|
||||
plugin.prepare_data(obj.data, container_name)
|
||||
|
||||
if not obj.get(blender.pipeline.AVALON_PROPERTY):
|
||||
obj[blender.pipeline.AVALON_PROPERTY] = dict()
|
||||
|
|
@ -84,13 +82,11 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
if obj.type == 'ARMATURE' and action is not None:
|
||||
obj.animation_data.action = action
|
||||
|
||||
objects_list.append(obj)
|
||||
|
||||
rig_container.pop(blender.pipeline.AVALON_PROPERTY)
|
||||
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
|
||||
return objects_list
|
||||
return rig_container
|
||||
|
||||
def process_asset(
|
||||
self, context: dict, name: str, namespace: Optional[str] = None,
|
||||
|
|
@ -107,9 +103,15 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
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
|
||||
lib_container = plugin.asset_name(
|
||||
asset, subset
|
||||
)
|
||||
unique_number = plugin.get_unique_number(
|
||||
asset, subset
|
||||
)
|
||||
namespace = namespace or f"{asset}_{unique_number}"
|
||||
container_name = plugin.asset_name(
|
||||
asset, subset, unique_number
|
||||
)
|
||||
|
||||
container = bpy.data.collections.new(lib_container)
|
||||
|
|
@ -128,11 +130,13 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
container_metadata["libpath"] = libpath
|
||||
container_metadata["lib_container"] = lib_container
|
||||
|
||||
objects_list = self._process(
|
||||
libpath, lib_container, container_name, None)
|
||||
obj_container = self._process(
|
||||
libpath, lib_container, container_name, None, None)
|
||||
|
||||
container_metadata["obj_container"] = obj_container
|
||||
|
||||
# Save the list of objects in the metadata container
|
||||
container_metadata["objects"] = objects_list
|
||||
container_metadata["objects"] = obj_container.all_objects
|
||||
|
||||
nodes = list(container.objects)
|
||||
nodes.append(container)
|
||||
|
|
@ -151,15 +155,13 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
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(
|
||||
self.log.info(
|
||||
"Container: %s\nRepresentation: %s",
|
||||
pformat(container, indent=2),
|
||||
pformat(representation, indent=2),
|
||||
|
|
@ -177,29 +179,35 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
assert libpath.is_file(), (
|
||||
f"The file doesn't exist: {libpath}"
|
||||
)
|
||||
assert extension in pype.hosts.blender.plugin.VALID_EXTENSIONS, (
|
||||
assert extension in 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"]
|
||||
|
||||
obj_container = plugin.get_local_collection_with_name(
|
||||
collection_metadata["obj_container"].name
|
||||
)
|
||||
objects = obj_container.all_objects
|
||||
|
||||
container_name = obj_container.name
|
||||
|
||||
normalized_collection_libpath = (
|
||||
str(Path(bpy.path.abspath(collection_libpath)).resolve())
|
||||
)
|
||||
normalized_libpath = (
|
||||
str(Path(bpy.path.abspath(str(libpath))).resolve())
|
||||
)
|
||||
logger.debug(
|
||||
self.log.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...")
|
||||
self.log.info("Library already loaded, not updating...")
|
||||
return
|
||||
|
||||
# Get the armature of the rig
|
||||
|
|
@ -208,13 +216,16 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
|
||||
action = armatures[0].animation_data.action
|
||||
|
||||
self._remove(objects, lib_container)
|
||||
parent = plugin.get_parent_collection(obj_container)
|
||||
|
||||
objects_list = self._process(
|
||||
str(libpath), lib_container, collection.name, action)
|
||||
self._remove(objects, obj_container)
|
||||
|
||||
obj_container = self._process(
|
||||
str(libpath), lib_container, container_name, action, parent)
|
||||
|
||||
# Save the list of objects in the metadata container
|
||||
collection_metadata["objects"] = objects_list
|
||||
collection_metadata["obj_container"] = obj_container
|
||||
collection_metadata["objects"] = obj_container.all_objects
|
||||
collection_metadata["libpath"] = str(libpath)
|
||||
collection_metadata["representation"] = str(representation["_id"])
|
||||
|
||||
|
|
@ -245,10 +256,13 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
|
||||
collection_metadata = collection.get(
|
||||
blender.pipeline.AVALON_PROPERTY)
|
||||
objects = collection_metadata["objects"]
|
||||
lib_container = collection_metadata["lib_container"]
|
||||
|
||||
self._remove(objects, lib_container)
|
||||
obj_container = plugin.get_local_collection_with_name(
|
||||
collection_metadata["obj_container"].name
|
||||
)
|
||||
objects = obj_container.all_objects
|
||||
|
||||
self._remove(objects, obj_container)
|
||||
|
||||
bpy.data.collections.remove(collection)
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
|
|||
"textures",
|
||||
"action",
|
||||
"harmony.template",
|
||||
"harmony.palette",
|
||||
"editorial"
|
||||
]
|
||||
exclude_families = ["clip"]
|
||||
|
|
@ -605,7 +606,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
|
|||
"type": "subset",
|
||||
"name": subset_name,
|
||||
"data": {
|
||||
"families": instance.data.get('families')
|
||||
"families": instance.data.get("families", [])
|
||||
},
|
||||
"parent": asset["_id"]
|
||||
}).inserted_id
|
||||
|
|
@ -727,7 +728,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
|
|||
task_name = io.Session.get("AVALON_TASK")
|
||||
family = self.main_family_from_instance(instance)
|
||||
|
||||
matching_profiles = None
|
||||
matching_profiles = {}
|
||||
highest_value = -1
|
||||
self.log.info(self.template_name_profiles)
|
||||
for name, filters in self.template_name_profiles.items():
|
||||
|
|
@ -745,7 +746,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
|
|||
value += 1
|
||||
|
||||
if value > highest_value:
|
||||
matching_profiles = {}
|
||||
highest_value = value
|
||||
|
||||
if value == highest_value:
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class ValidateContainers(pyblish.api.ContextPlugin):
|
|||
|
||||
label = "Validate Containers"
|
||||
order = pyblish.api.ValidatorOrder
|
||||
hosts = ["maya", "houdini", "nuke"]
|
||||
hosts = ["maya", "houdini", "nuke", "harmony", "photoshop"]
|
||||
optional = True
|
||||
actions = [ShowInventory]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import os
|
||||
import uuid
|
||||
|
||||
import clique
|
||||
|
||||
from avalon import api, harmony
|
||||
import pype.lib
|
||||
|
||||
copy_files = """function copyFile(srcFilename, dstFilename)
|
||||
{
|
||||
|
|
@ -98,33 +100,63 @@ function import_files(args)
|
|||
transparencyModeAttr.setValue(SGITransparencyMode);
|
||||
if (extension == "psd")
|
||||
transparencyModeAttr.setValue(FlatPSDTransparencyMode);
|
||||
if (extension == "jpg")
|
||||
transparencyModeAttr.setValue(LayeredPSDTransparencyMode);
|
||||
|
||||
node.linkAttr(read, "DRAWING.ELEMENT", uniqueColumnName);
|
||||
|
||||
// Create a drawing for each file.
|
||||
for( var i =0; i <= files.length - 1; ++i)
|
||||
if (files.length == 1)
|
||||
{
|
||||
timing = start_frame + i
|
||||
// Create a drawing drawing, 'true' indicate that the file exists.
|
||||
Drawing.create(elemId, timing, true);
|
||||
Drawing.create(elemId, 1, true);
|
||||
// Get the actual path, in tmp folder.
|
||||
var drawingFilePath = Drawing.filename(elemId, timing.toString());
|
||||
copyFile( files[i], drawingFilePath );
|
||||
var drawingFilePath = Drawing.filename(elemId, "1");
|
||||
copyFile(files[0], drawingFilePath);
|
||||
// Expose the image for the entire frame range.
|
||||
for( var i =0; i <= frame.numberOf() - 1; ++i)
|
||||
{
|
||||
timing = start_frame + i
|
||||
column.setEntry(uniqueColumnName, 1, timing, "1");
|
||||
}
|
||||
} else {
|
||||
// Create a drawing for each file.
|
||||
for( var i =0; i <= files.length - 1; ++i)
|
||||
{
|
||||
timing = start_frame + i
|
||||
// Create a drawing drawing, 'true' indicate that the file exists.
|
||||
Drawing.create(elemId, timing, true);
|
||||
// Get the actual path, in tmp folder.
|
||||
var drawingFilePath = Drawing.filename(elemId, timing.toString());
|
||||
copyFile( files[i], drawingFilePath );
|
||||
|
||||
column.setEntry(uniqueColumnName, 1, timing, timing.toString());
|
||||
column.setEntry(uniqueColumnName, 1, timing, timing.toString());
|
||||
}
|
||||
}
|
||||
|
||||
var green_color = new ColorRGBA(0, 255, 0, 255);
|
||||
node.setColor(read, green_color);
|
||||
|
||||
return read;
|
||||
}
|
||||
import_files
|
||||
"""
|
||||
|
||||
replace_files = """function replace_files(args)
|
||||
replace_files = """var PNGTransparencyMode = 0; //Premultiplied wih Black
|
||||
var TGATransparencyMode = 0; //Premultiplied wih Black
|
||||
var SGITransparencyMode = 0; //Premultiplied wih Black
|
||||
var LayeredPSDTransparencyMode = 1; //Straight
|
||||
var FlatPSDTransparencyMode = 2; //Premultiplied wih White
|
||||
|
||||
function replace_files(args)
|
||||
{
|
||||
var files = args[0];
|
||||
MessageLog.trace(files);
|
||||
MessageLog.trace(files.length);
|
||||
var _node = args[1];
|
||||
var start_frame = args[2];
|
||||
|
||||
var _column = node.linkedColumn(_node, "DRAWING.ELEMENT");
|
||||
var elemId = column.getElementIdOfDrawing(_column);
|
||||
|
||||
// Delete existing drawings.
|
||||
var timings = column.getDrawingTimings(_column);
|
||||
|
|
@ -133,20 +165,62 @@ replace_files = """function replace_files(args)
|
|||
column.deleteDrawingAt(_column, parseInt(timings[i]));
|
||||
}
|
||||
|
||||
// Create new drawings.
|
||||
for( var i =0; i <= files.length - 1; ++i)
|
||||
{
|
||||
timing = start_frame + i
|
||||
// Create a drawing drawing, 'true' indicate that the file exists.
|
||||
Drawing.create(node.getElementId(_node), timing, true);
|
||||
// Get the actual path, in tmp folder.
|
||||
var drawingFilePath = Drawing.filename(
|
||||
node.getElementId(_node), timing.toString()
|
||||
);
|
||||
copyFile( files[i], drawingFilePath );
|
||||
|
||||
column.setEntry(_column, 1, timing, timing.toString());
|
||||
var filename = files[0];
|
||||
var pos = filename.lastIndexOf(".");
|
||||
if( pos < 0 )
|
||||
return null;
|
||||
var extension = filename.substr(pos+1).toLowerCase();
|
||||
|
||||
if(extension == "jpeg")
|
||||
extension = "jpg";
|
||||
|
||||
var transparencyModeAttr = node.getAttr(
|
||||
_node, frame.current(), "applyMatteToColor"
|
||||
);
|
||||
if (extension == "png")
|
||||
transparencyModeAttr.setValue(PNGTransparencyMode);
|
||||
if (extension == "tga")
|
||||
transparencyModeAttr.setValue(TGATransparencyMode);
|
||||
if (extension == "sgi")
|
||||
transparencyModeAttr.setValue(SGITransparencyMode);
|
||||
if (extension == "psd")
|
||||
transparencyModeAttr.setValue(FlatPSDTransparencyMode);
|
||||
if (extension == "jpg")
|
||||
transparencyModeAttr.setValue(LayeredPSDTransparencyMode);
|
||||
|
||||
if (files.length == 1)
|
||||
{
|
||||
// Create a drawing drawing, 'true' indicate that the file exists.
|
||||
Drawing.create(elemId, 1, true);
|
||||
// Get the actual path, in tmp folder.
|
||||
var drawingFilePath = Drawing.filename(elemId, "1");
|
||||
copyFile(files[0], drawingFilePath);
|
||||
MessageLog.trace(files[0]);
|
||||
MessageLog.trace(drawingFilePath);
|
||||
// Expose the image for the entire frame range.
|
||||
for( var i =0; i <= frame.numberOf() - 1; ++i)
|
||||
{
|
||||
timing = start_frame + i
|
||||
column.setEntry(_column, 1, timing, "1");
|
||||
}
|
||||
} else {
|
||||
// Create a drawing for each file.
|
||||
for( var i =0; i <= files.length - 1; ++i)
|
||||
{
|
||||
timing = start_frame + i
|
||||
// Create a drawing drawing, 'true' indicate that the file exists.
|
||||
Drawing.create(elemId, timing, true);
|
||||
// Get the actual path, in tmp folder.
|
||||
var drawingFilePath = Drawing.filename(elemId, timing.toString());
|
||||
copyFile( files[i], drawingFilePath );
|
||||
|
||||
column.setEntry(_column, 1, timing, timing.toString());
|
||||
}
|
||||
}
|
||||
|
||||
var green_color = new ColorRGBA(0, 255, 0, 255);
|
||||
node.setColor(_node, green_color);
|
||||
}
|
||||
replace_files
|
||||
"""
|
||||
|
|
@ -156,8 +230,8 @@ class ImageSequenceLoader(api.Loader):
|
|||
"""Load images
|
||||
Stores the imported asset in a container named after the asset.
|
||||
"""
|
||||
families = ["shot", "render"]
|
||||
representations = ["jpeg", "png"]
|
||||
families = ["shot", "render", "image"]
|
||||
representations = ["jpeg", "png", "jpg"]
|
||||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
|
||||
|
|
@ -165,20 +239,29 @@ class ImageSequenceLoader(api.Loader):
|
|||
os.listdir(os.path.dirname(self.fname))
|
||||
)
|
||||
files = []
|
||||
for f in list(collections[0]):
|
||||
if collections:
|
||||
for f in list(collections[0]):
|
||||
files.append(
|
||||
os.path.join(
|
||||
os.path.dirname(self.fname), f
|
||||
).replace("\\", "/")
|
||||
)
|
||||
else:
|
||||
files.append(
|
||||
os.path.join(os.path.dirname(self.fname), f).replace("\\", "/")
|
||||
os.path.join(
|
||||
os.path.dirname(self.fname), remainder[0]
|
||||
).replace("\\", "/")
|
||||
)
|
||||
|
||||
name = context["subset"]["name"]
|
||||
name += "_{}".format(uuid.uuid4())
|
||||
read_node = harmony.send(
|
||||
{
|
||||
"function": copy_files + import_files,
|
||||
"args": ["Top", files, context["subset"]["name"], 1]
|
||||
"args": ["Top", files, name, 1]
|
||||
}
|
||||
)["result"]
|
||||
|
||||
self[:] = [read_node]
|
||||
|
||||
return harmony.containerise(
|
||||
name,
|
||||
namespace,
|
||||
|
|
@ -188,17 +271,25 @@ class ImageSequenceLoader(api.Loader):
|
|||
)
|
||||
|
||||
def update(self, container, representation):
|
||||
node = container.pop("node")
|
||||
node = harmony.find_node_by_name(container["name"], "READ")
|
||||
|
||||
path = api.get_representation_path(representation)
|
||||
collections, remainder = clique.assemble(
|
||||
os.listdir(
|
||||
os.path.dirname(api.get_representation_path(representation))
|
||||
)
|
||||
os.listdir(os.path.dirname(path))
|
||||
)
|
||||
files = []
|
||||
for f in list(collections[0]):
|
||||
if collections:
|
||||
for f in list(collections[0]):
|
||||
files.append(
|
||||
os.path.join(
|
||||
os.path.dirname(path), f
|
||||
).replace("\\", "/")
|
||||
)
|
||||
else:
|
||||
files.append(
|
||||
os.path.join(os.path.dirname(self.fname), f).replace("\\", "/")
|
||||
os.path.join(
|
||||
os.path.dirname(path), remainder[0]
|
||||
).replace("\\", "/")
|
||||
)
|
||||
|
||||
harmony.send(
|
||||
|
|
@ -208,12 +299,34 @@ class ImageSequenceLoader(api.Loader):
|
|||
}
|
||||
)
|
||||
|
||||
# Colour node.
|
||||
func = """function func(args){
|
||||
for( var i =0; i <= args[0].length - 1; ++i)
|
||||
{
|
||||
var red_color = new ColorRGBA(255, 0, 0, 255);
|
||||
var green_color = new ColorRGBA(0, 255, 0, 255);
|
||||
if (args[1] == "red"){
|
||||
node.setColor(args[0], red_color);
|
||||
}
|
||||
if (args[1] == "green"){
|
||||
node.setColor(args[0], green_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
func
|
||||
"""
|
||||
if pype.lib.is_latest(representation):
|
||||
harmony.send({"function": func, "args": [node, "green"]})
|
||||
else:
|
||||
harmony.send({"function": func, "args": [node, "red"]})
|
||||
|
||||
harmony.imprint(
|
||||
node, {"representation": str(representation["_id"])}
|
||||
)
|
||||
|
||||
def remove(self, container):
|
||||
node = container.pop("node")
|
||||
node = harmony.find_node_by_name(container["name"], "READ")
|
||||
|
||||
func = """function deleteNode(_node)
|
||||
{
|
||||
node.deleteNode(_node, true, true);
|
||||
|
|
|
|||
66
pype/plugins/harmony/load/load_palette.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import os
|
||||
import shutil
|
||||
|
||||
from avalon import api, harmony
|
||||
from avalon.vendor import Qt
|
||||
|
||||
|
||||
class ImportPaletteLoader(api.Loader):
|
||||
"""Import palettes."""
|
||||
|
||||
families = ["harmony.palette"]
|
||||
representations = ["plt"]
|
||||
label = "Import Palette"
|
||||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
name = self.load_palette(context["representation"])
|
||||
|
||||
return harmony.containerise(
|
||||
name,
|
||||
namespace,
|
||||
name,
|
||||
context,
|
||||
self.__class__.__name__
|
||||
)
|
||||
|
||||
def load_palette(self, representation):
|
||||
subset_name = representation["context"]["subset"]
|
||||
name = subset_name.replace("palette", "")
|
||||
|
||||
# Overwrite palette on disk.
|
||||
scene_path = harmony.send(
|
||||
{"function": "scene.currentProjectPath"}
|
||||
)["result"]
|
||||
src = api.get_representation_path(representation)
|
||||
dst = os.path.join(
|
||||
scene_path,
|
||||
"palette-library",
|
||||
"{}.plt".format(name)
|
||||
)
|
||||
shutil.copy(src, dst)
|
||||
|
||||
harmony.save_scene()
|
||||
|
||||
# Dont allow instances with the same name.
|
||||
message_box = Qt.QtWidgets.QMessageBox()
|
||||
message_box.setIcon(Qt.QtWidgets.QMessageBox.Warning)
|
||||
msg = "Updated {}.".format(subset_name)
|
||||
msg += " You need to reload the scene to see the changes."
|
||||
message_box.setText(msg)
|
||||
message_box.exec_()
|
||||
|
||||
return name
|
||||
|
||||
def remove(self, container):
|
||||
harmony.remove(container["name"])
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
||||
def update(self, container, representation):
|
||||
self.remove(container)
|
||||
name = self.load_palette(representation)
|
||||
|
||||
container["representation"] = str(representation["_id"])
|
||||
container["name"] = name
|
||||
harmony.imprint(name, container)
|
||||
|
|
@ -40,5 +40,5 @@ class ImportWorkfileLoader(ImportTemplateLoader):
|
|||
"""Import workfiles."""
|
||||
|
||||
families = ["workfile"]
|
||||
representations = ["*"]
|
||||
representations = ["zip"]
|
||||
label = "Import Workfile"
|
||||
|
|
|
|||
45
pype/plugins/harmony/publish/collect_palettes.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import os
|
||||
import json
|
||||
|
||||
import pyblish.api
|
||||
from avalon import harmony
|
||||
|
||||
|
||||
class CollectPalettes(pyblish.api.ContextPlugin):
|
||||
"""Gather palettes from scene when publishing templates."""
|
||||
|
||||
label = "Palettes"
|
||||
order = pyblish.api.CollectorOrder
|
||||
hosts = ["harmony"]
|
||||
|
||||
def process(self, context):
|
||||
func = """function func()
|
||||
{
|
||||
var palette_list = PaletteObjectManager.getScenePaletteList();
|
||||
|
||||
var palettes = {};
|
||||
for(var i=0; i < palette_list.numPalettes; ++i)
|
||||
{
|
||||
var palette = palette_list.getPaletteByIndex(i);
|
||||
palettes[palette.getName()] = palette.id;
|
||||
}
|
||||
|
||||
return palettes;
|
||||
}
|
||||
func
|
||||
"""
|
||||
palettes = harmony.send({"function": func})["result"]
|
||||
|
||||
for name, id in palettes.items():
|
||||
instance = context.create_instance(name)
|
||||
instance.data.update({
|
||||
"id": id,
|
||||
"family": "harmony.palette",
|
||||
"asset": os.environ["AVALON_ASSET"],
|
||||
"subset": "palette" + name
|
||||
})
|
||||
self.log.info(
|
||||
"Created instance:\n" + json.dumps(
|
||||
instance.data, sort_keys=True, indent=4
|
||||
)
|
||||
)
|
||||
34
pype/plugins/harmony/publish/extract_palette.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import os
|
||||
|
||||
from avalon import harmony
|
||||
import pype.api
|
||||
import pype.hosts.harmony
|
||||
|
||||
|
||||
class ExtractPalette(pype.api.Extractor):
|
||||
"""Extract palette."""
|
||||
|
||||
label = "Extract Palette"
|
||||
hosts = ["harmony"]
|
||||
families = ["harmony.palette"]
|
||||
|
||||
def process(self, instance):
|
||||
func = """function func(args)
|
||||
{
|
||||
var palette_list = PaletteObjectManager.getScenePaletteList();
|
||||
var palette = palette_list.getPaletteById(args[0]);
|
||||
return (palette.getPath() + "/" + palette.getName() + ".plt");
|
||||
}
|
||||
func
|
||||
"""
|
||||
palette_file = harmony.send(
|
||||
{"function": func, "args": [instance.data["id"]]}
|
||||
)["result"]
|
||||
|
||||
representation = {
|
||||
"name": "plt",
|
||||
"ext": "plt",
|
||||
"files": os.path.basename(palette_file),
|
||||
"stagingDir": os.path.dirname(palette_file)
|
||||
}
|
||||
instance.data["representations"] = [representation]
|
||||
|
|
@ -50,8 +50,11 @@ class ImagePlaneLoader(api.Loader):
|
|||
|
||||
camera = selection[0]
|
||||
|
||||
camera.displayResolution.set(1)
|
||||
camera.farClipPlane.set(image_plane_depth * 10)
|
||||
try:
|
||||
camera.displayResolution.set(1)
|
||||
camera.farClipPlane.set(image_plane_depth * 10)
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
# Create image plane
|
||||
image_plane_transform, image_plane_shape = pc.imagePlane(
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ import pyblish.api
|
|||
import pype.api
|
||||
|
||||
|
||||
class ValidateNukeWriteKnobs(pyblish.api.ContextPlugin):
|
||||
class ValidateKnobs(pyblish.api.ContextPlugin):
|
||||
"""Ensure knobs are consistent.
|
||||
|
||||
Knobs to validate and their values comes from the
|
||||
|
||||
Example for presets in config:
|
||||
"presets/plugins/nuke/publish.json" preset, which needs this structure:
|
||||
"ValidateNukeWriteKnobs": {
|
||||
"ValidateKnobs": {
|
||||
"enabled": true,
|
||||
"knobs": {
|
||||
"family": {
|
||||
|
|
@ -22,22 +22,31 @@ class ValidateNukeWriteKnobs(pyblish.api.ContextPlugin):
|
|||
"""
|
||||
|
||||
order = pyblish.api.ValidatorOrder
|
||||
label = "Validate Write Knobs"
|
||||
label = "Validate Knobs"
|
||||
hosts = ["nuke"]
|
||||
actions = [pype.api.RepairContextAction]
|
||||
optional = True
|
||||
|
||||
def process(self, context):
|
||||
# Check for preset existence.
|
||||
if not getattr(self, "knobs"):
|
||||
nuke_presets = context.data["presets"].get("nuke")
|
||||
|
||||
if not nuke_presets:
|
||||
return
|
||||
|
||||
publish_presets = nuke_presets.get("publish")
|
||||
|
||||
if not publish_presets:
|
||||
return
|
||||
|
||||
plugin_preset = publish_presets.get("ValidateKnobs")
|
||||
|
||||
if not plugin_preset:
|
||||
return
|
||||
|
||||
self.log.debug("__ self.knobs: {}".format(self.knobs))
|
||||
|
||||
invalid = self.get_invalid(context, compute=True)
|
||||
if invalid:
|
||||
raise RuntimeError(
|
||||
"Found knobs with invalid values: {}".format(invalid)
|
||||
"Found knobs with invalid values:\n{}".format(invalid)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
|
@ -51,6 +60,8 @@ class ValidateNukeWriteKnobs(pyblish.api.ContextPlugin):
|
|||
@classmethod
|
||||
def get_invalid_knobs(cls, context):
|
||||
invalid_knobs = []
|
||||
publish_presets = context.data["presets"]["nuke"]["publish"]
|
||||
knobs_preset = publish_presets["ValidateKnobs"]["knobs"]
|
||||
for instance in context:
|
||||
# Filter publisable instances.
|
||||
if not instance.data["publish"]:
|
||||
|
|
@ -59,15 +70,15 @@ class ValidateNukeWriteKnobs(pyblish.api.ContextPlugin):
|
|||
# Filter families.
|
||||
families = [instance.data["family"]]
|
||||
families += instance.data.get("families", [])
|
||||
families = list(set(families) & set(cls.knobs.keys()))
|
||||
families = list(set(families) & set(knobs_preset.keys()))
|
||||
if not families:
|
||||
continue
|
||||
|
||||
# Get all knobs to validate.
|
||||
knobs = {}
|
||||
for family in families:
|
||||
for preset in cls.knobs[family]:
|
||||
knobs.update({preset: cls.knobs[family][preset]})
|
||||
for preset in knobs_preset[family]:
|
||||
knobs.update({preset: knobs_preset[family][preset]})
|
||||
|
||||
# Get invalid knobs.
|
||||
nodes = []
|
||||
|
|
@ -82,16 +93,20 @@ class ValidateNukeWriteKnobs(pyblish.api.ContextPlugin):
|
|||
|
||||
for node in nodes:
|
||||
for knob in node.knobs():
|
||||
if knob in knobs.keys():
|
||||
expected = knobs[knob]
|
||||
if node[knob].value() != expected:
|
||||
invalid_knobs.append(
|
||||
{
|
||||
"knob": node[knob],
|
||||
"expected": expected,
|
||||
"current": node[knob].value()
|
||||
}
|
||||
)
|
||||
if knob not in knobs.keys():
|
||||
continue
|
||||
|
||||
expected = knobs[knob]
|
||||
if node[knob].value() != expected:
|
||||
invalid_knobs.append(
|
||||
{
|
||||
"knob": node[knob],
|
||||
"name": node[knob].name(),
|
||||
"label": node[knob].label(),
|
||||
"expected": expected,
|
||||
"current": node[knob].value()
|
||||
}
|
||||
)
|
||||
|
||||
context.data["invalid_knobs"] = invalid_knobs
|
||||
return invalid_knobs
|
||||
|
|
@ -74,4 +74,5 @@ class CreateImage(api.Creator):
|
|||
groups.append(group)
|
||||
|
||||
for group in groups:
|
||||
self.data.update({"subset": "image" + group.Name})
|
||||
photoshop.imprint(group, self.data)
|
||||
|
|
|
|||
36
pype/plugins/photoshop/publish/collect_review.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import os
|
||||
|
||||
import pythoncom
|
||||
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class CollectReview(pyblish.api.ContextPlugin):
|
||||
"""Gather the active document as review instance."""
|
||||
|
||||
label = "Review"
|
||||
order = pyblish.api.CollectorOrder
|
||||
hosts = ["photoshop"]
|
||||
|
||||
def process(self, context):
|
||||
# Necessary call when running in a different thread which pyblish-qml
|
||||
# can be.
|
||||
pythoncom.CoInitialize()
|
||||
|
||||
family = "review"
|
||||
task = os.getenv("AVALON_TASK", None)
|
||||
subset = family + task.capitalize()
|
||||
|
||||
file_path = context.data["currentFile"]
|
||||
base_name = os.path.basename(file_path)
|
||||
|
||||
instance = context.create_instance(subset)
|
||||
instance.data.update({
|
||||
"subset": subset,
|
||||
"label": base_name,
|
||||
"name": base_name,
|
||||
"family": family,
|
||||
"families": ["ftrack"],
|
||||
"representations": [],
|
||||
"asset": os.environ["AVALON_ASSET"]
|
||||
})
|
||||
103
pype/plugins/photoshop/publish/extract_review.py
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import os
|
||||
|
||||
import pype.api
|
||||
import pype.lib
|
||||
from avalon import photoshop
|
||||
|
||||
|
||||
class ExtractReview(pype.api.Extractor):
|
||||
"""Produce a flattened image file from all instances."""
|
||||
|
||||
label = "Extract Review"
|
||||
hosts = ["photoshop"]
|
||||
families = ["review"]
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
staging_dir = self.staging_dir(instance)
|
||||
self.log.info("Outputting image to {}".format(staging_dir))
|
||||
|
||||
layers = []
|
||||
for image_instance in instance.context:
|
||||
if image_instance.data["family"] != "image":
|
||||
continue
|
||||
layers.append(image_instance[0])
|
||||
|
||||
# Perform extraction
|
||||
output_image = "{} copy.jpg".format(
|
||||
os.path.splitext(photoshop.app().ActiveDocument.Name)[0]
|
||||
)
|
||||
with photoshop.maintained_visibility():
|
||||
# Hide all other layers.
|
||||
extract_ids = [
|
||||
x.id for x in photoshop.get_layers_in_layers(layers)
|
||||
]
|
||||
for layer in photoshop.get_layers_in_document():
|
||||
if layer.id in extract_ids:
|
||||
layer.Visible = True
|
||||
else:
|
||||
layer.Visible = False
|
||||
|
||||
photoshop.app().ActiveDocument.SaveAs(
|
||||
staging_dir, photoshop.com_objects.JPEGSaveOptions(), True
|
||||
)
|
||||
|
||||
instance.data["representations"].append({
|
||||
"name": "jpg",
|
||||
"ext": "jpg",
|
||||
"files": output_image,
|
||||
"stagingDir": staging_dir
|
||||
})
|
||||
instance.data["stagingDir"] = staging_dir
|
||||
|
||||
# Generate thumbnail.
|
||||
thumbnail_path = os.path.join(staging_dir, "thumbnail.jpg")
|
||||
args = [
|
||||
"ffmpeg", "-y",
|
||||
"-i", os.path.join(staging_dir, output_image),
|
||||
"-vf", "scale=300:-1",
|
||||
"-vframes", "1",
|
||||
thumbnail_path
|
||||
]
|
||||
output = pype.lib._subprocess(args, cwd=os.environ["FFMPEG_PATH"])
|
||||
|
||||
self.log.debug(output)
|
||||
|
||||
instance.data["representations"].append({
|
||||
"name": "thumbnail",
|
||||
"ext": "jpg",
|
||||
"files": os.path.basename(thumbnail_path),
|
||||
"stagingDir": staging_dir,
|
||||
"tags": ["thumbnail"]
|
||||
})
|
||||
|
||||
# Generate mov.
|
||||
mov_path = os.path.join(staging_dir, "review.mov")
|
||||
args = [
|
||||
"ffmpeg", "-y",
|
||||
"-i", os.path.join(staging_dir, output_image),
|
||||
"-vframes", "1",
|
||||
mov_path
|
||||
]
|
||||
output = pype.lib._subprocess(args, cwd=os.environ["FFMPEG_PATH"])
|
||||
|
||||
self.log.debug(output)
|
||||
|
||||
instance.data["representations"].append({
|
||||
"name": "mov",
|
||||
"ext": "mov",
|
||||
"files": os.path.basename(mov_path),
|
||||
"stagingDir": staging_dir,
|
||||
"frameStart": 1,
|
||||
"frameEnd": 1,
|
||||
"fps": 25,
|
||||
"preview": True,
|
||||
"tags": ["review", "ftrackreview"]
|
||||
})
|
||||
|
||||
# Required for extract_review plugin (L222 onwards).
|
||||
instance.data["frameStart"] = 1
|
||||
instance.data["frameEnd"] = 1
|
||||
instance.data["fps"] = 25
|
||||
|
||||
self.log.info(f"Extracted {instance} to {staging_dir}")
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import pyblish.api
|
||||
import pype.api
|
||||
from avalon import photoshop
|
||||
|
||||
|
||||
class ValidateNamingRepair(pyblish.api.Action):
|
||||
|
|
@ -22,7 +23,11 @@ class ValidateNamingRepair(pyblish.api.Action):
|
|||
instances = pyblish.api.instances_by_plugin(failed, plugin)
|
||||
|
||||
for instance in instances:
|
||||
instance[0].Name = instance.data["name"].replace(" ", "_")
|
||||
name = instance.data["name"].replace(" ", "_")
|
||||
instance[0].Name = name
|
||||
data = photoshop.read(instance[0])
|
||||
data["subset"] = "image" + name
|
||||
photoshop.imprint(instance[0], data)
|
||||
|
||||
return True
|
||||
|
||||
|
|
@ -42,3 +47,6 @@ class ValidateNaming(pyblish.api.InstancePlugin):
|
|||
def process(self, instance):
|
||||
msg = "Name \"{}\" is not allowed.".format(instance.data["name"])
|
||||
assert " " not in instance.data["name"], msg
|
||||
|
||||
msg = "Subset \"{}\" is not allowed.".format(instance.data["subset"])
|
||||
assert " " not in instance.data["subset"], msg
|
||||
|
|
|
|||
|
|
@ -56,12 +56,18 @@ class CollectShots(pyblish.api.InstancePlugin):
|
|||
asset_entity = instance.context.data["assetEntity"]
|
||||
asset_name = asset_entity["name"]
|
||||
|
||||
# Ask user for sequence start. Usually 10:00:00:00.
|
||||
sequence_start_frame = 900000
|
||||
|
||||
# Project specific prefix naming. This needs to be replaced with some
|
||||
# options to be more flexible.
|
||||
asset_name = asset_name.split("_")[0]
|
||||
|
||||
instances = []
|
||||
for track in tracks:
|
||||
track_start_frame = (
|
||||
abs(track.source_range.start_time.value) - sequence_start_frame
|
||||
)
|
||||
for child in track.each_child():
|
||||
|
||||
# Transitions are ignored, because Clips have the full frame
|
||||
|
|
@ -69,12 +75,17 @@ class CollectShots(pyblish.api.InstancePlugin):
|
|||
if isinstance(child, otio.schema.transition.Transition):
|
||||
continue
|
||||
|
||||
if child.name is None:
|
||||
continue
|
||||
|
||||
# Hardcoded to expect a shot name of "[name].[extension]"
|
||||
child_name = os.path.splitext(child.name)[0].lower()
|
||||
name = f"{asset_name}_{child_name}"
|
||||
|
||||
frame_start = child.range_in_parent().start_time.value
|
||||
frame_end = child.range_in_parent().end_time_inclusive().value
|
||||
frame_start = track_start_frame
|
||||
frame_start += child.range_in_parent().start_time.value
|
||||
frame_end = track_start_frame
|
||||
frame_end += child.range_in_parent().end_time_inclusive().value
|
||||
|
||||
label = f"{name} (framerange: {frame_start}-{frame_end})"
|
||||
instances.append(
|
||||
|
|
|
|||
|
|
@ -14,3 +14,25 @@ def get_resource(*args):
|
|||
*args
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def pype_icon_filepath(debug=None):
|
||||
if debug is None:
|
||||
debug = bool(os.getenv("PYPE_DEV"))
|
||||
|
||||
if debug:
|
||||
icon_file_name = "pype_icon_dev.png"
|
||||
else:
|
||||
icon_file_name = "pype_icon.png"
|
||||
return get_resource("icons", icon_file_name)
|
||||
|
||||
|
||||
def pype_splash_filepath(debug=None):
|
||||
if debug is None:
|
||||
debug = bool(os.getenv("PYPE_DEV"))
|
||||
|
||||
if debug:
|
||||
splash_file_name = "pype_splash_dev.png"
|
||||
else:
|
||||
splash_file_name = "pype_splash.png"
|
||||
return get_resource("icons", splash_file_name)
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
|
|
@ -1,5 +1,7 @@
|
|||
from Qt import QtCore
|
||||
|
||||
EXPANDER_WIDTH = 20
|
||||
|
||||
|
||||
def flags(*args, **kwargs):
|
||||
type_name = kwargs.pop("type_name", "Flags")
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from Qt import QtWidgets, QtGui, QtCore
|
|||
from . import model
|
||||
from .awesome import tags as awesome
|
||||
from .constants import (
|
||||
PluginStates, InstanceStates, PluginActionStates, Roles
|
||||
PluginStates, InstanceStates, PluginActionStates, Roles, EXPANDER_WIDTH
|
||||
)
|
||||
|
||||
colors = {
|
||||
|
|
@ -14,12 +14,16 @@ colors = {
|
|||
"ok": QtGui.QColor("#77AE24"),
|
||||
"active": QtGui.QColor("#99CEEE"),
|
||||
"idle": QtCore.Qt.white,
|
||||
"font": QtGui.QColor("#DDD"),
|
||||
"inactive": QtGui.QColor("#888"),
|
||||
"hover": QtGui.QColor(255, 255, 255, 10),
|
||||
"selected": QtGui.QColor(255, 255, 255, 20),
|
||||
"outline": QtGui.QColor("#333"),
|
||||
"group": QtGui.QColor("#333")
|
||||
"group": QtGui.QColor("#333"),
|
||||
"group-hover": QtGui.QColor("#3c3c3c"),
|
||||
"group-selected-hover": QtGui.QColor("#555555"),
|
||||
"expander-bg": QtGui.QColor("#222"),
|
||||
"expander-hover": QtGui.QColor("#2d6c9f"),
|
||||
"expander-selected-hover": QtGui.QColor("#3784c5")
|
||||
}
|
||||
|
||||
scale_factors = {"darwin": 1.5}
|
||||
|
|
@ -279,14 +283,169 @@ class InstanceItemDelegate(QtWidgets.QStyledItemDelegate):
|
|||
return QtCore.QSize(option.rect.width(), 20)
|
||||
|
||||
|
||||
class OverviewGroupSection(QtWidgets.QStyledItemDelegate):
|
||||
"""Generic delegate for section header"""
|
||||
class InstanceDelegate(QtWidgets.QStyledItemDelegate):
|
||||
"""Generic delegate for instance header"""
|
||||
|
||||
item_class = None
|
||||
radius = 8.0
|
||||
|
||||
def __init__(self, parent):
|
||||
super(OverviewGroupSection, self).__init__(parent)
|
||||
self.item_delegate = self.item_class(parent)
|
||||
super(InstanceDelegate, self).__init__(parent)
|
||||
self.item_delegate = InstanceItemDelegate(parent)
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
if index.data(Roles.TypeRole) in (
|
||||
model.InstanceType, model.PluginType
|
||||
):
|
||||
self.item_delegate.paint(painter, option, index)
|
||||
return
|
||||
|
||||
self.group_item_paint(painter, option, index)
|
||||
|
||||
def group_item_paint(self, painter, option, index):
|
||||
"""Paint text
|
||||
_
|
||||
My label
|
||||
"""
|
||||
body_rect = QtCore.QRectF(option.rect)
|
||||
bg_rect = QtCore.QRectF(
|
||||
body_rect.left(), body_rect.top() + 1,
|
||||
body_rect.width() - 5, body_rect.height() - 2
|
||||
)
|
||||
|
||||
expander_rect = QtCore.QRectF(bg_rect)
|
||||
expander_rect.setWidth(EXPANDER_WIDTH)
|
||||
|
||||
remainder_rect = QtCore.QRectF(
|
||||
expander_rect.x() + expander_rect.width(),
|
||||
expander_rect.y(),
|
||||
bg_rect.width() - expander_rect.width(),
|
||||
expander_rect.height()
|
||||
)
|
||||
|
||||
width = float(expander_rect.width())
|
||||
height = float(expander_rect.height())
|
||||
|
||||
x_pos = expander_rect.x()
|
||||
y_pos = expander_rect.y()
|
||||
|
||||
x_radius = min(self.radius, width / 2)
|
||||
y_radius = min(self.radius, height / 2)
|
||||
x_radius2 = x_radius * 2
|
||||
y_radius2 = y_radius * 2
|
||||
|
||||
expander_path = QtGui.QPainterPath()
|
||||
expander_path.moveTo(x_pos, y_pos + y_radius)
|
||||
expander_path.arcTo(
|
||||
x_pos, y_pos,
|
||||
x_radius2, y_radius2,
|
||||
180.0, -90.0
|
||||
)
|
||||
expander_path.lineTo(x_pos + width, y_pos)
|
||||
expander_path.lineTo(x_pos + width, y_pos + height)
|
||||
expander_path.lineTo(x_pos + x_radius, y_pos + height)
|
||||
expander_path.arcTo(
|
||||
x_pos, y_pos + height - y_radius2,
|
||||
x_radius2, y_radius2,
|
||||
270.0, -90.0
|
||||
)
|
||||
expander_path.closeSubpath()
|
||||
|
||||
width = float(remainder_rect.width())
|
||||
height = float(remainder_rect.height())
|
||||
x_pos = remainder_rect.x()
|
||||
y_pos = remainder_rect.y()
|
||||
|
||||
x_radius = min(self.radius, width / 2)
|
||||
y_radius = min(self.radius, height / 2)
|
||||
x_radius2 = x_radius * 2
|
||||
y_radius2 = y_radius * 2
|
||||
|
||||
remainder_path = QtGui.QPainterPath()
|
||||
remainder_path.moveTo(x_pos + width, y_pos + height - y_radius)
|
||||
remainder_path.arcTo(
|
||||
x_pos + width - x_radius2, y_pos + height - y_radius2,
|
||||
x_radius2, y_radius2,
|
||||
0.0, -90.0
|
||||
)
|
||||
remainder_path.lineTo(x_pos, y_pos + height)
|
||||
remainder_path.lineTo(x_pos, y_pos)
|
||||
remainder_path.lineTo(x_pos + width - x_radius, y_pos)
|
||||
remainder_path.arcTo(
|
||||
x_pos + width - x_radius2, y_pos,
|
||||
x_radius2, y_radius2,
|
||||
90.0, -90.0
|
||||
)
|
||||
remainder_path.closeSubpath()
|
||||
|
||||
painter.fillPath(expander_path, colors["expander-bg"])
|
||||
painter.fillPath(remainder_path, colors["group"])
|
||||
|
||||
mouse_pos = option.widget.mapFromGlobal(QtGui.QCursor.pos())
|
||||
selected = option.state & QtWidgets.QStyle.State_Selected
|
||||
hovered = option.state & QtWidgets.QStyle.State_MouseOver
|
||||
|
||||
if selected and hovered:
|
||||
if expander_rect.contains(mouse_pos):
|
||||
painter.fillPath(
|
||||
expander_path, colors["expander-selected-hover"]
|
||||
)
|
||||
else:
|
||||
painter.fillPath(
|
||||
remainder_path, colors["group-selected-hover"]
|
||||
)
|
||||
|
||||
elif hovered:
|
||||
if expander_rect.contains(mouse_pos):
|
||||
painter.fillPath(expander_path, colors["expander-hover"])
|
||||
else:
|
||||
painter.fillPath(remainder_path, colors["group-hover"])
|
||||
|
||||
text_height = font_metrics["awesome6"].height()
|
||||
adjust_value = (expander_rect.height() - text_height) / 2
|
||||
expander_rect.adjust(
|
||||
adjust_value + 1.5, adjust_value - 0.5,
|
||||
-adjust_value + 1.5, -adjust_value - 0.5
|
||||
)
|
||||
|
||||
offset = (remainder_rect.height() - font_metrics["h5"].height()) / 2
|
||||
label_rect = QtCore.QRectF(remainder_rect.adjusted(
|
||||
5, offset - 1, 0, 0
|
||||
))
|
||||
|
||||
expander_icon = icons["plus-sign"]
|
||||
|
||||
expanded = self.parent().isExpanded(index)
|
||||
if expanded:
|
||||
expander_icon = icons["minus-sign"]
|
||||
label = index.data(QtCore.Qt.DisplayRole)
|
||||
label = font_metrics["h5"].elidedText(
|
||||
label, QtCore.Qt.ElideRight, label_rect.width()
|
||||
)
|
||||
|
||||
# Maintain reference to state, so we can restore it once we're done
|
||||
painter.save()
|
||||
|
||||
painter.setFont(fonts["awesome6"])
|
||||
painter.setPen(QtGui.QPen(colors["idle"]))
|
||||
painter.drawText(expander_rect, QtCore.Qt.AlignCenter, expander_icon)
|
||||
|
||||
# Draw label
|
||||
painter.setFont(fonts["h5"])
|
||||
painter.drawText(label_rect, label)
|
||||
|
||||
# Ok, we're done, tidy up.
|
||||
painter.restore()
|
||||
|
||||
def sizeHint(self, option, index):
|
||||
return QtCore.QSize(option.rect.width(), 20)
|
||||
|
||||
|
||||
class PluginDelegate(QtWidgets.QStyledItemDelegate):
|
||||
"""Generic delegate for plugin header"""
|
||||
|
||||
def __init__(self, parent):
|
||||
super(PluginDelegate, self).__init__(parent)
|
||||
self.item_delegate = PluginItemDelegate(parent)
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
if index.data(Roles.TypeRole) in (
|
||||
|
|
@ -310,7 +469,14 @@ class OverviewGroupSection(QtWidgets.QStyledItemDelegate):
|
|||
radius = 8.0
|
||||
bg_path = QtGui.QPainterPath()
|
||||
bg_path.addRoundedRect(bg_rect, radius, radius)
|
||||
painter.fillPath(bg_path, colors["group"])
|
||||
hovered = option.state & QtWidgets.QStyle.State_MouseOver
|
||||
selected = option.state & QtWidgets.QStyle.State_Selected
|
||||
if hovered and selected:
|
||||
painter.fillPath(bg_path, colors["group-selected-hover"])
|
||||
elif hovered:
|
||||
painter.fillPath(bg_path, colors["group-hover"])
|
||||
else:
|
||||
painter.fillPath(bg_path, colors["group"])
|
||||
|
||||
expander_rect = QtCore.QRectF(bg_rect)
|
||||
expander_rect.setWidth(expander_rect.height())
|
||||
|
|
@ -343,18 +509,12 @@ class OverviewGroupSection(QtWidgets.QStyledItemDelegate):
|
|||
|
||||
painter.setFont(fonts["awesome6"])
|
||||
painter.setPen(QtGui.QPen(colors["idle"]))
|
||||
painter.drawText(expander_rect, expander_icon)
|
||||
painter.drawText(expander_rect, QtCore.Qt.AlignCenter, expander_icon)
|
||||
|
||||
# Draw label
|
||||
painter.setFont(fonts["h5"])
|
||||
painter.drawText(label_rect, label)
|
||||
|
||||
if option.state & QtWidgets.QStyle.State_MouseOver:
|
||||
painter.fillPath(bg_path, colors["hover"])
|
||||
|
||||
if option.state & QtWidgets.QStyle.State_Selected:
|
||||
painter.fillPath(bg_path, colors["selected"])
|
||||
|
||||
# Ok, we're done, tidy up.
|
||||
painter.restore()
|
||||
|
||||
|
|
@ -362,16 +522,6 @@ class OverviewGroupSection(QtWidgets.QStyledItemDelegate):
|
|||
return QtCore.QSize(option.rect.width(), 20)
|
||||
|
||||
|
||||
class PluginDelegate(OverviewGroupSection):
|
||||
"""Generic delegate for model items in proxy tree view"""
|
||||
item_class = PluginItemDelegate
|
||||
|
||||
|
||||
class InstanceDelegate(OverviewGroupSection):
|
||||
"""Generic delegate for model items in proxy tree view"""
|
||||
item_class = InstanceItemDelegate
|
||||
|
||||
|
||||
class ArtistDelegate(QtWidgets.QStyledItemDelegate):
|
||||
"""Delegate used on Artist page"""
|
||||
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@ class PluginItem(QtGui.QStandardItem):
|
|||
return False
|
||||
self.plugin.active = value
|
||||
self.emitDataChanged()
|
||||
return True
|
||||
return
|
||||
|
||||
elif role == Roles.PluginActionProgressRole:
|
||||
if isinstance(value, list):
|
||||
|
|
@ -652,14 +652,14 @@ class InstanceItem(QtGui.QStandardItem):
|
|||
def setData(self, value, role=(QtCore.Qt.UserRole + 1)):
|
||||
if role == QtCore.Qt.CheckStateRole:
|
||||
if not self.data(Roles.IsEnabledRole):
|
||||
return False
|
||||
return
|
||||
self.instance.data["publish"] = value
|
||||
self.emitDataChanged()
|
||||
return True
|
||||
return
|
||||
|
||||
if role == Roles.IsEnabledRole:
|
||||
if not self.instance.optional:
|
||||
return False
|
||||
return
|
||||
|
||||
if role == Roles.PublishFlagsRole:
|
||||
if isinstance(value, list):
|
||||
|
|
@ -692,12 +692,12 @@ class InstanceItem(QtGui.QStandardItem):
|
|||
|
||||
self.instance._publish_states = value
|
||||
self.emitDataChanged()
|
||||
return True
|
||||
return
|
||||
|
||||
if role == Roles.LogRecordsRole:
|
||||
self.instance._logs = value
|
||||
self.emitDataChanged()
|
||||
return True
|
||||
return
|
||||
|
||||
return super(InstanceItem, self).setData(value, role)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from Qt import QtCore, QtWidgets
|
||||
from . import model
|
||||
from .constants import Roles
|
||||
from .constants import Roles, EXPANDER_WIDTH
|
||||
# Imported when used
|
||||
widgets = None
|
||||
|
||||
|
|
@ -84,8 +84,6 @@ class OverviewView(QtWidgets.QTreeView):
|
|||
self.setRootIsDecorated(False)
|
||||
self.setIndentation(0)
|
||||
|
||||
self.clicked.connect(self.item_expand)
|
||||
|
||||
def event(self, event):
|
||||
if not event.type() == QtCore.QEvent.KeyPress:
|
||||
return super(OverviewView, self).event(event)
|
||||
|
|
@ -113,6 +111,24 @@ class OverviewView(QtWidgets.QTreeView):
|
|||
def focusOutEvent(self, event):
|
||||
self.selectionModel().clear()
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() in (QtCore.Qt.LeftButton, QtCore.Qt.RightButton):
|
||||
# Deselect all group labels
|
||||
indexes = self.selectionModel().selectedIndexes()
|
||||
for index in indexes:
|
||||
if index.data(Roles.TypeRole) == model.GroupType:
|
||||
self.selectionModel().select(
|
||||
index, QtCore.QItemSelectionModel.Deselect
|
||||
)
|
||||
|
||||
return super(OverviewView, self).mouseReleaseEvent(event)
|
||||
|
||||
|
||||
class PluginView(OverviewView):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PluginView, self).__init__(*args, **kwargs)
|
||||
self.clicked.connect(self.item_expand)
|
||||
|
||||
def item_expand(self, index):
|
||||
if index.data(Roles.TypeRole) == model.GroupType:
|
||||
if self.isExpanded(index):
|
||||
|
|
@ -125,23 +141,86 @@ class OverviewView(QtWidgets.QTreeView):
|
|||
indexes = self.selectionModel().selectedIndexes()
|
||||
if len(indexes) == 1:
|
||||
index = indexes[0]
|
||||
# If instance or Plugin
|
||||
if index.data(Roles.TypeRole) in (
|
||||
model.InstanceType, model.PluginType
|
||||
pos_index = self.indexAt(event.pos())
|
||||
# If instance or Plugin and is selected
|
||||
if (
|
||||
index == pos_index
|
||||
and index.data(Roles.TypeRole) == model.PluginType
|
||||
):
|
||||
if event.pos().x() < 20:
|
||||
self.toggled.emit(index, None)
|
||||
elif event.pos().x() > self.width() - 20:
|
||||
self.show_perspective.emit(index)
|
||||
|
||||
# Deselect all group labels
|
||||
for index in indexes:
|
||||
if index.data(Roles.TypeRole) == model.GroupType:
|
||||
self.selectionModel().select(
|
||||
index, QtCore.QItemSelectionModel.Deselect
|
||||
)
|
||||
return super(PluginView, self).mouseReleaseEvent(event)
|
||||
|
||||
return super(OverviewView, self).mouseReleaseEvent(event)
|
||||
|
||||
class InstanceView(OverviewView):
|
||||
def __init__(self, parent=None):
|
||||
super(InstanceView, self).__init__(parent)
|
||||
self.viewport().setMouseTracking(True)
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
index = self.indexAt(event.pos())
|
||||
if index.data(Roles.TypeRole) == model.GroupType:
|
||||
self.update(index)
|
||||
super(InstanceView, self).mouseMoveEvent(event)
|
||||
|
||||
def item_expand(self, index, expand=None):
|
||||
if expand is None:
|
||||
expand = not self.isExpanded(index)
|
||||
|
||||
if expand:
|
||||
self.expand(index)
|
||||
else:
|
||||
self.collapse(index)
|
||||
|
||||
def group_toggle(self, index):
|
||||
model = index.model()
|
||||
|
||||
chilren_indexes_checked = []
|
||||
chilren_indexes_unchecked = []
|
||||
for idx in range(model.rowCount(index)):
|
||||
child_index = model.index(idx, 0, index)
|
||||
if not child_index.data(Roles.IsEnabledRole):
|
||||
continue
|
||||
|
||||
if child_index.data(QtCore.Qt.CheckStateRole):
|
||||
chilren_indexes_checked.append(child_index)
|
||||
else:
|
||||
chilren_indexes_unchecked.append(child_index)
|
||||
|
||||
if chilren_indexes_checked:
|
||||
to_change_indexes = chilren_indexes_checked
|
||||
new_state = False
|
||||
else:
|
||||
to_change_indexes = chilren_indexes_unchecked
|
||||
new_state = True
|
||||
|
||||
for index in to_change_indexes:
|
||||
model.setData(index, new_state, QtCore.Qt.CheckStateRole)
|
||||
self.toggled.emit(index, new_state)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
indexes = self.selectionModel().selectedIndexes()
|
||||
if len(indexes) == 1:
|
||||
index = indexes[0]
|
||||
pos_index = self.indexAt(event.pos())
|
||||
if index == pos_index:
|
||||
# If instance or Plugin
|
||||
if index.data(Roles.TypeRole) == model.InstanceType:
|
||||
if event.pos().x() < 20:
|
||||
self.toggled.emit(index, None)
|
||||
elif event.pos().x() > self.width() - 20:
|
||||
self.show_perspective.emit(index)
|
||||
else:
|
||||
if event.pos().x() < EXPANDER_WIDTH:
|
||||
self.item_expand(index)
|
||||
else:
|
||||
self.group_toggle(index)
|
||||
self.item_expand(index, True)
|
||||
return super(InstanceView, self).mouseReleaseEvent(event)
|
||||
|
||||
|
||||
class TerminalView(QtWidgets.QTreeView):
|
||||
|
|
|
|||
|
|
@ -160,14 +160,14 @@ class Window(QtWidgets.QDialog):
|
|||
# TODO add parent
|
||||
overview_page = QtWidgets.QWidget()
|
||||
|
||||
overview_instance_view = view.OverviewView(parent=overview_page)
|
||||
overview_instance_view = view.InstanceView(parent=overview_page)
|
||||
overview_instance_delegate = delegate.InstanceDelegate(
|
||||
parent=overview_instance_view
|
||||
)
|
||||
overview_instance_view.setItemDelegate(overview_instance_delegate)
|
||||
overview_instance_view.setModel(instance_model)
|
||||
|
||||
overview_plugin_view = view.OverviewView(parent=overview_page)
|
||||
overview_plugin_view = view.PluginView(parent=overview_page)
|
||||
overview_plugin_delegate = delegate.PluginDelegate(
|
||||
parent=overview_plugin_view
|
||||
)
|
||||
|
|
|
|||
58
pype/tools/tray/modules_imports.json
Normal 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"]
|
||||
}
|
||||
]
|
||||
|
|
@ -3,8 +3,7 @@ import sys
|
|||
import platform
|
||||
from avalon import style
|
||||
from Qt import QtCore, QtGui, QtWidgets, QtSvg
|
||||
from pype.resources import get_resource
|
||||
from pype.api import config, Logger
|
||||
from pype.api import config, Logger, resources
|
||||
|
||||
|
||||
class TrayManager:
|
||||
|
|
@ -12,28 +11,40 @@ 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(
|
||||
resources.get_resource("icons", "circle_green.png")
|
||||
)
|
||||
self.icon_stay = QtGui.QIcon(
|
||||
resources.get_resource("icons", "circle_orange.png")
|
||||
)
|
||||
self.icon_failed = QtGui.QIcon(
|
||||
resources.get_resource("icons", "circle_red.png")
|
||||
)
|
||||
|
||||
def process_presets(self):
|
||||
"""Add modules to tray by presets.
|
||||
|
|
@ -46,42 +57,26 @@ 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
|
||||
else:
|
||||
items = []
|
||||
# Get booleans is module should be used
|
||||
usages = self.items.get("item_usage") or {}
|
||||
for item in self.items.get("item_import", []):
|
||||
import_path = item.get("import_path")
|
||||
title = item.get("title")
|
||||
|
||||
item_usage = usages.get(title)
|
||||
if item_usage is None:
|
||||
item_usage = usages.get(import_path, True)
|
||||
items = []
|
||||
# Get booleans is module should be used
|
||||
for item in self.modules_imports:
|
||||
import_path = item.get("import_path")
|
||||
title = item.get("title")
|
||||
|
||||
if item_usage:
|
||||
items.append(item)
|
||||
else:
|
||||
if not title:
|
||||
title = import_path
|
||||
self.log.debug("{} - Module ignored".format(title))
|
||||
item_usage = self.modules_usage.get(title)
|
||||
if item_usage is None:
|
||||
item_usage = self.modules_usage.get(import_path, True)
|
||||
|
||||
if item_usage:
|
||||
items.append(item)
|
||||
else:
|
||||
if not title:
|
||||
title = import_path
|
||||
self.log.info("{} - Module ignored".format(title))
|
||||
|
||||
if items:
|
||||
self.process_items(items, self.tray_widget.menu)
|
||||
|
|
@ -333,12 +328,7 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
|
|||
:type parent: QtWidgets.QMainWindow
|
||||
"""
|
||||
def __init__(self, parent):
|
||||
if os.getenv("PYPE_DEV"):
|
||||
icon_file_name = "icon_dev.png"
|
||||
else:
|
||||
icon_file_name = "icon.png"
|
||||
|
||||
self.icon = QtGui.QIcon(get_resource(icon_file_name))
|
||||
self.icon = QtGui.QIcon(resources.pype_icon_filepath())
|
||||
|
||||
QtWidgets.QSystemTrayIcon.__init__(self, self.icon, parent)
|
||||
|
||||
|
|
@ -402,7 +392,7 @@ class TrayMainWindow(QtWidgets.QMainWindow):
|
|||
self.trayIcon.show()
|
||||
|
||||
def set_working_widget(self):
|
||||
image_file = get_resource('working.svg')
|
||||
image_file = resources.get_resource("icons", "working.svg")
|
||||
img_pix = QtGui.QPixmap(image_file)
|
||||
if image_file.endswith('.svg'):
|
||||
widget = QtSvg.QSvgWidget(image_file)
|
||||
|
|
@ -492,11 +482,7 @@ class PypeTrayApplication(QtWidgets.QApplication):
|
|||
splash_widget.hide()
|
||||
|
||||
def set_splash(self):
|
||||
if os.getenv("PYPE_DEV"):
|
||||
splash_file_name = "splash_dev.png"
|
||||
else:
|
||||
splash_file_name = "splash.png"
|
||||
splash_pix = QtGui.QPixmap(get_resource(splash_file_name))
|
||||
splash_pix = QtGui.QPixmap(resources.pype_splash_filepath())
|
||||
splash = QtWidgets.QSplashScreen(splash_pix)
|
||||
splash.setMask(splash_pix.mask())
|
||||
splash.setEnabled(False)
|
||||
|
|
|
|||
|
|
@ -52,6 +52,19 @@ def message(title=None, message=None, level="info", parent=None):
|
|||
app = parent
|
||||
if not app:
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
ex = Window(app, title, message, level)
|
||||
ex.show()
|
||||
|
||||
# Move widget to center of screen
|
||||
try:
|
||||
desktop_rect = QtWidgets.QApplication.desktop().availableGeometry(ex)
|
||||
center = desktop_rect.center()
|
||||
ex.move(
|
||||
center.x() - (ex.width() * 0.5),
|
||||
center.y() - (ex.height() * 0.5)
|
||||
)
|
||||
except Exception:
|
||||
# skip all possible issues that may happen feature is not crutial
|
||||
log.warning("Couldn't center message.", exc_info=True)
|
||||
# sys.exit(app.exec_())
|
||||
|
|
|
|||