diff --git a/pype/api.py b/pype/api.py index 021080b4d5..c1bf84b4ef 100644 --- a/pype/api.py +++ b/pype/api.py @@ -1,6 +1,7 @@ from .settings import ( system_settings, - project_settings + project_settings, + environments ) from pypeapp import ( Logger, @@ -55,6 +56,7 @@ from .lib import _subprocess as subprocess __all__ = [ "system_settings", "project_settings", + "environments", "Logger", "Anatomy", diff --git a/pype/hosts/harmony/__init__.py b/pype/hosts/harmony/__init__.py index 7310e91e9b..f920e38765 100644 --- a/pype/hosts/harmony/__init__.py +++ b/pype/hosts/harmony/__init__.py @@ -1,5 +1,6 @@ import os import sys +from uuid import uuid4 from avalon import api, io, harmony from avalon.vendor import Qt @@ -8,8 +9,11 @@ import pyblish.api from pype import lib +signature = str(uuid4()) + + def set_scene_settings(settings): - func = """function func(args) + func = """function %s_func(args) { if (args[0]["fps"]) { @@ -36,8 +40,8 @@ def set_scene_settings(settings): ) } } - func - """ + %s_func + """ % (signature, signature) harmony.send({"function": func, "args": [settings]}) @@ -107,15 +111,15 @@ def check_inventory(): outdated_containers.append(container) # Colour nodes. - func = """function func(args){ + func = """function %s_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 - """ + %s_func + """ % (signature, signature) outdated_nodes = [] for container in outdated_containers: if container["loader"] == "ImageSequenceLoader": @@ -144,7 +148,7 @@ def application_launch(): def export_template(backdrops, nodes, filepath): - func = """function func(args) + func = """function %s_func(args) { var temp_node = node.add("Top", "temp_note", "NOTE", 0, 0, 0); @@ -179,8 +183,8 @@ def export_template(backdrops, nodes, filepath): Action.perform("onActionUpToParent()", "Node View"); node.deleteNode(template_group, true, true); } - func - """ + %s_func + """ % (signature, signature) harmony.send({ "function": func, "args": [ @@ -221,12 +225,15 @@ def install(): def on_pyblish_instance_toggled(instance, old_value, new_value): """Toggle node enabling on instance toggles.""" - func = """function func(args) + func = """function %s_func(args) { node.setEnable(args[0], args[1]) } - func - """ - harmony.send( - {"function": func, "args": [instance[0], new_value]} - ) + %s_func + """ % (signature, signature) + try: + harmony.send( + {"function": func, "args": [instance[0], new_value]} + ) + except IndexError: + print(f"Instance '{instance}' is missing node") diff --git a/pype/modules/__init__.py b/pype/modules/__init__.py index e69de29bb2..aacd541e18 100644 --- a/pype/modules/__init__.py +++ b/pype/modules/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +from .base import PypeModule + +__all__ = ( + "PypeModule", +) diff --git a/pype/modules/avalon_apps/avalon_app.py b/pype/modules/avalon_apps/avalon_app.py index 7ed651f82b..de10268304 100644 --- a/pype/modules/avalon_apps/avalon_app.py +++ b/pype/modules/avalon_apps/avalon_app.py @@ -1,16 +1,27 @@ -from Qt import QtWidgets -from avalon.tools import libraryloader from pype.api import Logger -from pype.tools.launcher import LauncherWindow, actions class AvalonApps: def __init__(self, main_parent=None, parent=None): self.log = Logger().get_logger(__name__) - self.main_parent = main_parent + + self.tray_init(main_parent, parent) + + def tray_init(self, main_parent, parent): + from avalon.tools.libraryloader import app + from avalon import style + from pype.tools.launcher import LauncherWindow, actions + self.parent = parent + self.main_parent = main_parent self.app_launcher = LauncherWindow() + self.libraryloader = app.Window( + icon=self.parent.icon, + show_projects=True, + show_libraries=True + ) + self.libraryloader.setStyleSheet(style.load_stylesheet()) # actions.register_default_actions() actions.register_config_actions() @@ -23,6 +34,7 @@ class AvalonApps: # Definition of Tray menu def tray_menu(self, parent_menu=None): + from Qt import QtWidgets # Actions if parent_menu is None: if self.parent is None: @@ -52,9 +64,11 @@ class AvalonApps: self.app_launcher.activateWindow() def show_library_loader(self): - libraryloader.show( - parent=self.main_parent, - icon=self.parent.icon, - show_projects=True, - show_libraries=True - ) + self.libraryloader.show() + + # Raise and activate the window + # for MacOS + self.libraryloader.raise_() + # for Windows + self.libraryloader.activateWindow() + self.libraryloader.refresh() diff --git a/pype/modules/base.py b/pype/modules/base.py new file mode 100644 index 0000000000..ee90aa4cbb --- /dev/null +++ b/pype/modules/base.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +"""Base class for Pype Modules.""" +from uuid import uuid4 +from abc import ABC, abstractmethod +from pype.api import Logger + + +class PypeModule(ABC): + """Base class of pype module. + + Attributes: + id (UUID): Module id. + enabled (bool): Is module enabled. + name (str): Module name. + """ + + enabled = False + name = None + _id = None + + def __init__(self, settings): + if self.name is None: + self.name = self.__class__.__name__ + + self.log = Logger().get_logger(self.name) + + self.settings = settings.get(self.name) + self.enabled = settings.get("enabled", False) + self._id = uuid4() + + @property + def id(self): + return self._id + + @abstractmethod + def startup_environments(self): + """Get startup environments for module.""" + return {} diff --git a/pype/modules/clockify/clockify.py b/pype/modules/clockify/clockify.py index fea15a1bea..4309bff9f2 100644 --- a/pype/modules/clockify/clockify.py +++ b/pype/modules/clockify/clockify.py @@ -1,9 +1,8 @@ import os import threading +import time + from pype.api import Logger -from avalon import style -from Qt import QtWidgets -from .widgets import ClockifySettings, MessageWidget from .clockify_api import ClockifyAPI from .constants import CLOCKIFY_FTRACK_USER_PATH @@ -17,11 +16,21 @@ class ClockifyModule: os.environ["CLOCKIFY_WORKSPACE"] = self.workspace_name + self.timer_manager = None + self.MessageWidgetClass = None + + self.clockapi = ClockifyAPI(master_parent=self) + self.log = Logger().get_logger(self.__class__.__name__, "PypeTray") + self.tray_init(main_parent, parent) + + def tray_init(self, main_parent, parent): + from .widgets import ClockifySettings, MessageWidget + + self.MessageWidgetClass = MessageWidget self.main_parent = main_parent self.parent = parent - self.clockapi = ClockifyAPI(master_parent=self) self.message_widget = None self.widget_settings = ClockifySettings(main_parent, self) self.widget_settings_required = None @@ -57,11 +66,10 @@ class ClockifyModule: ) if 'AvalonApps' in modules: - from launcher import lib - actions_path = os.path.sep.join([ + actions_path = os.path.join( os.path.dirname(__file__), 'launcher_actions' - ]) + ) current = os.environ.get('AVALON_ACTIONS', '') if current: current += os.pathsep @@ -78,12 +86,12 @@ class ClockifyModule: self.stop_timer() def timer_started(self, data): - if hasattr(self, 'timer_manager'): + if self.timer_manager: self.timer_manager.start_timers(data) def timer_stopped(self): self.bool_timer_run = False - if hasattr(self, 'timer_manager'): + if self.timer_manager: self.timer_manager.stop_timers() def start_timer_check(self): @@ -102,7 +110,7 @@ class ClockifyModule: self.thread_timer_check = None def check_running(self): - import time + while self.bool_thread_check_running is True: bool_timer_run = False if self.clockapi.get_in_progress() is not None: @@ -156,15 +164,14 @@ class ClockifyModule: self.timer_stopped() def signed_in(self): - if hasattr(self, 'timer_manager'): - if not self.timer_manager: - return + if not self.timer_manager: + return - if not self.timer_manager.last_task: - return + if not self.timer_manager.last_task: + return - if self.timer_manager.is_running: - self.start_timer_manager(self.timer_manager.last_task) + if self.timer_manager.is_running: + self.start_timer_manager(self.timer_manager.last_task) def start_timer(self, input_data): # If not api key is not entered then skip @@ -197,11 +204,14 @@ class ClockifyModule: "

Please inform your Project Manager." ).format(project_name, str(self.clockapi.workspace_name)) - self.message_widget = MessageWidget( - self.main_parent, msg, "Clockify - Info Message" - ) - self.message_widget.closed.connect(self.on_message_widget_close) - self.message_widget.show() + if self.MessageWidgetClass: + self.message_widget = self.MessageWidgetClass( + self.main_parent, msg, "Clockify - Info Message" + ) + self.message_widget.closed.connect( + self.on_message_widget_close + ) + self.message_widget.show() return @@ -227,31 +237,29 @@ class ClockifyModule: # Definition of Tray menu def tray_menu(self, parent_menu): # Menu for Tray App - self.menu = QtWidgets.QMenu('Clockify', parent_menu) - self.menu.setProperty('submenu', 'on') - self.menu.setStyleSheet(style.load_stylesheet()) + from Qt import QtWidgets + menu = QtWidgets.QMenu("Clockify", parent_menu) + menu.setProperty("submenu", "on") # Actions - self.aShowSettings = QtWidgets.QAction( - "Settings", self.menu - ) - self.aStopTimer = QtWidgets.QAction( - "Stop timer", self.menu - ) + action_show_settings = QtWidgets.QAction("Settings", menu) + action_stop_timer = QtWidgets.QAction("Stop timer", menu) - self.menu.addAction(self.aShowSettings) - self.menu.addAction(self.aStopTimer) + menu.addAction(action_show_settings) + menu.addAction(action_stop_timer) - self.aShowSettings.triggered.connect(self.show_settings) - self.aStopTimer.triggered.connect(self.stop_timer) + action_show_settings.triggered.connect(self.show_settings) + action_stop_timer.triggered.connect(self.stop_timer) + + self.action_stop_timer = action_stop_timer self.set_menu_visibility() - parent_menu.addMenu(self.menu) + parent_menu.addMenu(menu) def show_settings(self): self.widget_settings.input_api_key.setText(self.clockapi.get_api_key()) self.widget_settings.show() def set_menu_visibility(self): - self.aStopTimer.setVisible(self.bool_timer_run) + self.action_stop_timer.setVisible(self.bool_timer_run) diff --git a/pype/modules/ftrack/__init__.py b/pype/modules/ftrack/__init__.py index aa8f04bffb..fad771f084 100644 --- a/pype/modules/ftrack/__init__.py +++ b/pype/modules/ftrack/__init__.py @@ -1,2 +1,12 @@ -from .lib import * +from . import ftrack_server from .ftrack_server import FtrackServer, check_ftrack_url +from .lib import BaseHandler, BaseEvent, BaseAction + +__all__ = ( + "ftrack_server", + "FtrackServer", + "check_ftrack_url", + "BaseHandler", + "BaseEvent", + "BaseAction" +) diff --git a/pype/modules/ftrack/ftrack_server/__init__.py b/pype/modules/ftrack/ftrack_server/__init__.py index fcae4e0690..9e3920b500 100644 --- a/pype/modules/ftrack/ftrack_server/__init__.py +++ b/pype/modules/ftrack/ftrack_server/__init__.py @@ -1,2 +1,8 @@ from .ftrack_server import FtrackServer from .lib import check_ftrack_url + + +__all__ = ( + "FtrackServer", + "check_ftrack_url" +) diff --git a/pype/modules/ftrack/lib/custom_db_connector.py b/pype/modules/ftrack/ftrack_server/custom_db_connector.py similarity index 71% rename from pype/modules/ftrack/lib/custom_db_connector.py rename to pype/modules/ftrack/ftrack_server/custom_db_connector.py index d498d041dc..8a8ba4ccbb 100644 --- a/pype/modules/ftrack/lib/custom_db_connector.py +++ b/pype/modules/ftrack/ftrack_server/custom_db_connector.py @@ -16,9 +16,9 @@ import pymongo from pype.api import decompose_url -class NotActiveTable(Exception): +class NotActiveCollection(Exception): def __init__(self, *args, **kwargs): - msg = "Active table is not set. (This is bug)" + msg = "Active collection is not set. (This is bug)" if not (args or kwargs): args = [msg] super().__init__(*args, **kwargs) @@ -40,12 +40,12 @@ def auto_reconnect(func): return decorated -def check_active_table(func): +def check_active_collection(func): """Check if CustomDbConnector has active collection.""" @functools.wraps(func) def decorated(obj, *args, **kwargs): - if not obj.active_table: - raise NotActiveTable() + if not obj.active_collection: + raise NotActiveCollection() return func(obj, *args, **kwargs) return decorated @@ -55,7 +55,7 @@ class CustomDbConnector: timeout = int(os.environ["AVALON_TIMEOUT"]) def __init__( - self, uri, database_name, port=None, table_name=None + self, uri, database_name, port=None, collection_name=None ): self._mongo_client = None self._sentry_client = None @@ -76,10 +76,10 @@ class CustomDbConnector: self._port = port self._database_name = database_name - self.active_table = table_name + self.active_collection = collection_name def __getitem__(self, key): - # gives direct access to collection withou setting `active_table` + # gives direct access to collection withou setting `active_collection` return self._database[key] def __getattribute__(self, attr): @@ -88,9 +88,11 @@ class CustomDbConnector: try: return super(CustomDbConnector, self).__getattribute__(attr) except AttributeError: - if self.active_table is None: - raise NotActiveTable() - return self._database[self.active_table].__getattribute__(attr) + if self.active_collection is None: + raise NotActiveCollection() + return self._database[self.active_collection].__getattribute__( + attr + ) def install(self): """Establish a persistent connection to the database""" @@ -146,46 +148,30 @@ class CustomDbConnector: self._is_installed = False atexit.unregister(self.uninstall) - def create_table(self, name, **options): - if self.exist_table(name): + def collection_exists(self, collection_name): + return collection_name in self.collections() + + def create_collection(self, name, **options): + if self.collection_exists(name): return return self._database.create_collection(name, **options) - def exist_table(self, table_name): - return table_name in self.tables() - - def create_table(self, name, **options): - if self.exist_table(name): - return - - return self._database.create_collection(name, **options) - - def exist_table(self, table_name): - return table_name in self.tables() - - def tables(self): - """List available tables - Returns: - list of table names - """ - collection_names = self.collections() - for table_name in collection_names: - if table_name in ("system.indexes",): - continue - yield table_name - @auto_reconnect def collections(self): - return self._database.collection_names() + for col_name in self._database.collection_names(): + if col_name not in ("system.indexes",): + yield col_name - @check_active_table + @check_active_collection @auto_reconnect def insert_one(self, item, **options): assert isinstance(item, dict), "item must be of type " - return self._database[self.active_table].insert_one(item, **options) + return self._database[self.active_collection].insert_one( + item, **options + ) - @check_active_table + @check_active_collection @auto_reconnect def insert_many(self, items, ordered=True, **options): # check if all items are valid @@ -194,72 +180,74 @@ class CustomDbConnector: assert isinstance(item, dict), "`item` must be of type " options["ordered"] = ordered - return self._database[self.active_table].insert_many(items, **options) + return self._database[self.active_collection].insert_many( + items, **options + ) - @check_active_table + @check_active_collection @auto_reconnect def find(self, filter, projection=None, sort=None, **options): options["sort"] = sort - return self._database[self.active_table].find( + return self._database[self.active_collection].find( filter, projection, **options ) - @check_active_table + @check_active_collection @auto_reconnect def find_one(self, filter, projection=None, sort=None, **options): assert isinstance(filter, dict), "filter must be " options["sort"] = sort - return self._database[self.active_table].find_one( + return self._database[self.active_collection].find_one( filter, projection, **options ) - @check_active_table + @check_active_collection @auto_reconnect def replace_one(self, filter, replacement, **options): - return self._database[self.active_table].replace_one( + return self._database[self.active_collection].replace_one( filter, replacement, **options ) - @check_active_table + @check_active_collection @auto_reconnect def update_one(self, filter, update, **options): - return self._database[self.active_table].update_one( + return self._database[self.active_collection].update_one( filter, update, **options ) - @check_active_table + @check_active_collection @auto_reconnect def update_many(self, filter, update, **options): - return self._database[self.active_table].update_many( + return self._database[self.active_collection].update_many( filter, update, **options ) - @check_active_table + @check_active_collection @auto_reconnect def distinct(self, **options): - return self._database[self.active_table].distinct(**options) + return self._database[self.active_collection].distinct(**options) - @check_active_table + @check_active_collection @auto_reconnect def drop_collection(self, name_or_collection, **options): - return self._database[self.active_table].drop( + return self._database[self.active_collection].drop( name_or_collection, **options ) - @check_active_table + @check_active_collection @auto_reconnect def delete_one(self, filter, collation=None, **options): options["collation"] = collation - return self._database[self.active_table].delete_one( + return self._database[self.active_collection].delete_one( filter, **options ) - @check_active_table + @check_active_collection @auto_reconnect def delete_many(self, filter, collation=None, **options): options["collation"] = collation - return self._database[self.active_table].delete_many( + return self._database[self.active_collection].delete_many( filter, **options ) diff --git a/pype/modules/ftrack/ftrack_server/lib.py b/pype/modules/ftrack/ftrack_server/lib.py index ee6b1216dc..79b708b17a 100644 --- a/pype/modules/ftrack/ftrack_server/lib.py +++ b/pype/modules/ftrack/ftrack_server/lib.py @@ -26,7 +26,7 @@ from pype.api import ( compose_url ) -from pype.modules.ftrack.lib.custom_db_connector import CustomDbConnector +from .custom_db_connector import CustomDbConnector TOPIC_STATUS_SERVER = "pype.event.server.status" @@ -153,9 +153,9 @@ class StorerEventHub(SocketBaseEventHub): class ProcessEventHub(SocketBaseEventHub): hearbeat_msg = b"processor" - uri, port, database, table_name = get_ftrack_event_mongo_info() + uri, port, database, collection_name = get_ftrack_event_mongo_info() - is_table_created = False + is_collection_created = False pypelog = Logger().get_logger("Session Processor") def __init__(self, *args, **kwargs): @@ -163,7 +163,7 @@ class ProcessEventHub(SocketBaseEventHub): self.uri, self.database, self.port, - self.table_name + self.collection_name ) super(ProcessEventHub, self).__init__(*args, **kwargs) @@ -184,7 +184,7 @@ class ProcessEventHub(SocketBaseEventHub): "Error with Mongo access, probably permissions." "Check if exist database with name \"{}\"" " and collection \"{}\" inside." - ).format(self.database, self.table_name)) + ).format(self.database, self.collection_name)) self.sock.sendall(b"MongoError") sys.exit(0) diff --git a/pype/modules/ftrack/ftrack_server/sub_event_storer.py b/pype/modules/ftrack/ftrack_server/sub_event_storer.py index 1635f6cea3..2f4395c8db 100644 --- a/pype/modules/ftrack/ftrack_server/sub_event_storer.py +++ b/pype/modules/ftrack/ftrack_server/sub_event_storer.py @@ -12,7 +12,9 @@ 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 CustomDbConnector +from pype.modules.ftrack.ftrack_server.custom_db_connector import ( + CustomDbConnector +) from pype.api import Logger log = Logger().get_logger("Event storer") @@ -23,8 +25,8 @@ class SessionFactory: session = None -uri, port, database, table_name = get_ftrack_event_mongo_info() -dbcon = CustomDbConnector(uri, database, port, table_name) +uri, port, database, collection_name = get_ftrack_event_mongo_info() +dbcon = CustomDbConnector(uri, database, port, collection_name) # ignore_topics = ["ftrack.meta.connected"] ignore_topics = [] @@ -200,7 +202,7 @@ def main(args): "Error with Mongo access, probably permissions." "Check if exist database with name \"{}\"" " and collection \"{}\" inside." - ).format(database, table_name)) + ).format(database, collection_name)) sock.sendall(b"MongoError") finally: diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index 03124ab10d..292ce752cf 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -1022,7 +1022,7 @@ class SyncEntitiesFactory: continue ent_path_items = [ent["name"] for ent in entity["link"]] - parents = ent_path_items[1:len(ent_path_items)-1:] + parents = ent_path_items[1:len(ent_path_items) - 1:] hierarchy = "" if len(parents) > 0: hierarchy = os.path.sep.join(parents) @@ -1141,7 +1141,7 @@ class SyncEntitiesFactory: if not is_right and not else_match_better: entity = entity_dict["entity"] ent_path_items = [ent["name"] for ent in entity["link"]] - parents = ent_path_items[1:len(ent_path_items)-1:] + parents = ent_path_items[1:len(ent_path_items) - 1:] av_parents = av_ent_by_mongo_id["data"]["parents"] if av_parents == parents: is_right = True diff --git a/pype/modules/ftrack/lib/ftrack_base_handler.py b/pype/modules/ftrack/lib/ftrack_base_handler.py index ce6607d6bf..d322fbaf23 100644 --- a/pype/modules/ftrack/lib/ftrack_base_handler.py +++ b/pype/modules/ftrack/lib/ftrack_base_handler.py @@ -2,7 +2,7 @@ import functools import time from pype.api import Logger import ftrack_api -from pype.modules.ftrack.ftrack_server.lib import SocketSession +from pype.modules.ftrack import ftrack_server class MissingPermision(Exception): @@ -41,7 +41,7 @@ class BaseHandler(object): self.log = Logger().get_logger(self.__class__.__name__) if not( isinstance(session, ftrack_api.session.Session) or - isinstance(session, SocketSession) + isinstance(session, ftrack_server.lib.SocketSession) ): raise Exception(( "Session object entered with args is instance of \"{}\"" @@ -49,7 +49,7 @@ class BaseHandler(object): ).format( str(type(session)), str(ftrack_api.session.Session), - str(SocketSession) + str(ftrack_server.lib.SocketSession) )) self._session = session diff --git a/pype/modules/ftrack/tray/login_dialog.py b/pype/modules/ftrack/tray/login_dialog.py index 7730ee1609..94ad29e478 100644 --- a/pype/modules/ftrack/tray/login_dialog.py +++ b/pype/modules/ftrack/tray/login_dialog.py @@ -1,7 +1,7 @@ import os import requests from avalon import style -from pype.modules.ftrack import credentials +from pype.modules.ftrack.lib import credentials from . import login_tools from pype.api import resources from Qt import QtCore, QtGui, QtWidgets @@ -238,6 +238,8 @@ class CredentialsDialog(QtWidgets.QDialog): # If there is an existing server thread running we need to stop it. if self._login_server_thread: + if self._login_server_thread.isAlive(): + self._login_server_thread.stop() self._login_server_thread.join() self._login_server_thread = None diff --git a/pype/modules/ftrack/tray/login_tools.py b/pype/modules/ftrack/tray/login_tools.py index e7d22fbc19..d3297eaa76 100644 --- a/pype/modules/ftrack/tray/login_tools.py +++ b/pype/modules/ftrack/tray/login_tools.py @@ -61,12 +61,17 @@ class LoginServerThread(threading.Thread): def __init__(self, url, callback): self.url = url self.callback = callback + self._server = None super(LoginServerThread, self).__init__() def _handle_login(self, api_user, api_key): '''Login to server with *api_user* and *api_key*.''' self.callback(api_user, api_key) + def stop(self): + if self._server: + self._server.server_close() + def run(self): '''Listen for events.''' self._server = HTTPServer( diff --git a/pype/modules/logging/gui/__init__.py b/pype/modules/logging/tray/gui/__init__.py similarity index 100% rename from pype/modules/logging/gui/__init__.py rename to pype/modules/logging/tray/gui/__init__.py diff --git a/pype/modules/logging/gui/app.py b/pype/modules/logging/tray/gui/app.py similarity index 100% rename from pype/modules/logging/gui/app.py rename to pype/modules/logging/tray/gui/app.py diff --git a/pype/modules/logging/gui/models.py b/pype/modules/logging/tray/gui/models.py similarity index 100% rename from pype/modules/logging/gui/models.py rename to pype/modules/logging/tray/gui/models.py diff --git a/pype/modules/logging/gui/widgets.py b/pype/modules/logging/tray/gui/widgets.py similarity index 100% rename from pype/modules/logging/gui/widgets.py rename to pype/modules/logging/tray/gui/widgets.py diff --git a/pype/modules/logging/tray/logging_module.py b/pype/modules/logging/tray/logging_module.py index 9b26d5d9bf..84b40f68e1 100644 --- a/pype/modules/logging/tray/logging_module.py +++ b/pype/modules/logging/tray/logging_module.py @@ -1,6 +1,4 @@ -from Qt import QtWidgets from pype.api import Logger -from ..gui.app import LogsWindow class LoggingModule: @@ -8,7 +6,13 @@ class LoggingModule: self.parent = parent self.log = Logger().get_logger(self.__class__.__name__, "logging") + self.window = None + + self.tray_init(main_parent, parent) + + def tray_init(self, main_parent, parent): try: + from .gui.app import LogsWindow self.window = LogsWindow() self.tray_menu = self._tray_menu except Exception: @@ -18,12 +22,12 @@ class LoggingModule: # Definition of Tray menu def _tray_menu(self, parent_menu): + from Qt import QtWidgets # Menu for Tray App menu = QtWidgets.QMenu('Logging', parent_menu) - # menu.setProperty('submenu', 'on') show_action = QtWidgets.QAction("Show Logs", menu) - show_action.triggered.connect(self.on_show_logs) + show_action.triggered.connect(self._show_logs_gui) menu.addAction(show_action) parent_menu.addMenu(menu) @@ -34,5 +38,6 @@ class LoggingModule: def process_modules(self, modules): return - def on_show_logs(self): - self.window.show() + def _show_logs_gui(self): + if self.window: + self.window.show() diff --git a/pype/modules/muster/muster.py b/pype/modules/muster/muster.py index 629fb12635..beb30690ac 100644 --- a/pype/modules/muster/muster.py +++ b/pype/modules/muster/muster.py @@ -1,10 +1,7 @@ -import appdirs -from avalon import style -from Qt import QtWidgets import os import json -from .widget_login import MusterLogin -from avalon.vendor import requests +import appdirs +import requests class MusterModule: @@ -21,6 +18,11 @@ class MusterModule: self.cred_path = os.path.join( self.cred_folder_path, self.cred_filename ) + self.tray_init(main_parent, parent) + + def tray_init(self, main_parent, parent): + from .widget_login import MusterLogin + self.main_parent = main_parent self.parent = parent self.widget_login = MusterLogin(main_parent, self) @@ -38,10 +40,6 @@ class MusterModule: pass def process_modules(self, modules): - - def api_callback(): - self.aShowLogin.trigger() - if "RestApiServer" in modules: def api_show_login(): self.aShowLogin.trigger() @@ -51,13 +49,12 @@ class MusterModule: # Definition of Tray menu def tray_menu(self, parent): - """ - Add **change credentials** option to tray menu. - """ + """Add **change credentials** option to tray menu.""" + from Qt import QtWidgets + # Menu for Tray App self.menu = QtWidgets.QMenu('Muster', parent) self.menu.setProperty('submenu', 'on') - self.menu.setStyleSheet(style.load_stylesheet()) # Actions self.aShowLogin = QtWidgets.QAction( @@ -91,9 +88,9 @@ class MusterModule: if not MUSTER_REST_URL: raise AttributeError("Muster REST API url not set") params = { - 'username': username, - 'password': password - } + 'username': username, + 'password': password + } api_entry = '/api/login' response = self._requests_post( MUSTER_REST_URL + api_entry, params=params) diff --git a/pype/modules/rest_api/rest_api.py b/pype/modules/rest_api/rest_api.py index cc98b56a3b..3e0c646560 100644 --- a/pype/modules/rest_api/rest_api.py +++ b/pype/modules/rest_api/rest_api.py @@ -1,6 +1,6 @@ import os import socket -from Qt import QtCore +import threading from socketserver import ThreadingMixIn from http.server import HTTPServer @@ -155,14 +155,15 @@ class RestApiServer: def is_running(self): return self.rest_api_thread.is_running + def tray_exit(self): + self.stop() + def stop(self): - self.rest_api_thread.is_running = False - - def thread_stopped(self): - self._is_running = False + self.rest_api_thread.stop() + self.rest_api_thread.join() -class RestApiThread(QtCore.QThread): +class RestApiThread(threading.Thread): """ Listener for REST requests. It is possible to register callbacks for url paths. @@ -174,6 +175,12 @@ class RestApiThread(QtCore.QThread): self.is_running = False self.module = module self.port = port + self.httpd = None + + def stop(self): + self.is_running = False + if self.httpd: + self.httpd.server_close() def run(self): self.is_running = True @@ -185,12 +192,14 @@ class RestApiThread(QtCore.QThread): ) with ThreadingSimpleServer(("", self.port), Handler) as httpd: + self.httpd = httpd while self.is_running: httpd.handle_request() + except Exception: log.warning( "Rest Api Server service has failed", exc_info=True ) + self.httpd = None self.is_running = False - self.module.thread_stopped() diff --git a/pype/modules/standalonepublish/standalonepublish_module.py b/pype/modules/standalonepublish/standalonepublish_module.py index ed997bfd9f..f8bc0c6f24 100644 --- a/pype/modules/standalonepublish/standalonepublish_module.py +++ b/pype/modules/standalonepublish/standalonepublish_module.py @@ -2,7 +2,6 @@ import os import sys import subprocess import pype -from pype import tools class StandAlonePublishModule: @@ -30,6 +29,7 @@ class StandAlonePublishModule: )) def show(self): + from pype import tools standalone_publisher_tool_path = os.path.join( os.path.dirname(tools.__file__), "standalonepublish" diff --git a/pype/modules/timers_manager/__init__.py b/pype/modules/timers_manager/__init__.py index a8a478d7ae..9de205f088 100644 --- a/pype/modules/timers_manager/__init__.py +++ b/pype/modules/timers_manager/__init__.py @@ -1,5 +1,4 @@ from .timers_manager import TimersManager -from .widget_user_idle import WidgetUserIdle CLASS_DEFINIION = TimersManager diff --git a/pype/modules/timers_manager/timers_manager.py b/pype/modules/timers_manager/timers_manager.py index 82ba1013f0..62767c24f1 100644 --- a/pype/modules/timers_manager/timers_manager.py +++ b/pype/modules/timers_manager/timers_manager.py @@ -1,21 +1,7 @@ -from .widget_user_idle import WidgetUserIdle, SignalHandler -from pype.api import Logger, config +from pype.api import Logger -class Singleton(type): - """ Signleton implementation - """ - _instances = {} - - def __call__(cls, *args, **kwargs): - if cls not in cls._instances: - cls._instances[cls] = super( - Singleton, cls - ).__call__(*args, **kwargs) - return cls._instances[cls] - - -class TimersManager(metaclass=Singleton): +class TimersManager: """ Handles about Timers. Should be able to start/stop all timers at once. @@ -41,7 +27,13 @@ class TimersManager(metaclass=Singleton): self.idle_man = None self.signal_handler = None + + self.trat_init(tray_widget, main_widget) + + def trat_init(self, tray_widget, main_widget): + from .widget_user_idle import WidgetUserIdle, SignalHandler self.widget_user_idle = WidgetUserIdle(self, tray_widget) + self.signal_handler = SignalHandler(self) def set_signal_times(self): try: @@ -119,7 +111,6 @@ class TimersManager(metaclass=Singleton): """ if 'IdleManager' in modules: - self.signal_handler = SignalHandler(self) if self.set_signal_times() is True: self.register_to_idle_manager(modules['IdleManager']) diff --git a/pype/modules/user/user_module.py b/pype/modules/user/user_module.py index f2de9dc2fb..dc57fe4a63 100644 --- a/pype/modules/user/user_module.py +++ b/pype/modules/user/user_module.py @@ -3,8 +3,6 @@ import json import getpass import appdirs -from Qt import QtWidgets -from .widget_user import UserWidget from pype.api import Logger @@ -24,6 +22,12 @@ class UserModule: self.cred_path = os.path.normpath(os.path.join( self.cred_folder_path, self.cred_filename )) + self.widget_login = None + + self.tray_init(main_parent, parent) + + def tray_init(self, main_parent=None, parent=None): + from .widget_user import UserWidget self.widget_login = UserWidget(self) self.load_credentials() @@ -66,6 +70,7 @@ class UserModule: # Definition of Tray menu def tray_menu(self, parent_menu): + from Qt import QtWidgets """Add menu or action to Tray(or parent)'s menu""" action = QtWidgets.QAction("Username", parent_menu) action.triggered.connect(self.show_widget) @@ -121,7 +126,8 @@ class UserModule: self.cred = {"username": username} os.environ[self.env_name] = username - self.widget_login.set_user(username) + if self.widget_login: + self.widget_login.set_user(username) try: file = open(self.cred_path, "w") file.write(json.dumps(self.cred)) diff --git a/pype/modules/websocket_server/websocket_server.py b/pype/modules/websocket_server/websocket_server.py index 1152c65e00..daf4b03103 100644 --- a/pype/modules/websocket_server/websocket_server.py +++ b/pype/modules/websocket_server/websocket_server.py @@ -31,12 +31,13 @@ class WebSocketServer(): self.client = None self.handlers = {} + port = None websocket_url = os.getenv("WEBSOCKET_URL") if websocket_url: parsed = urllib.parse.urlparse(websocket_url) port = parsed.port if not port: - port = 8099 # fallback + port = 8098 # fallback self.app = web.Application() diff --git a/pype/modules_manager.py b/pype/modules_manager.py new file mode 100644 index 0000000000..6538187ea9 --- /dev/null +++ b/pype/modules_manager.py @@ -0,0 +1,102 @@ +import os +import inspect + +import pype.modules +from pype.modules import PypeModule +from pype.settings import system_settings +from pype.api import Logger + + +class PypeModuleManager: + skip_module_names = ("__pycache__", ) + + def __init__(self): + self.log = Logger().get_logger( + "{}.{}".format(__name__, self.__class__.__name__) + ) + + self.pype_modules = self.find_pype_modules() + + def modules_environments(self): + environments = {} + for pype_module in self.pype_modules.values(): + environments.update(pype_module.startup_environments()) + return environments + + def find_pype_modules(self): + settings = system_settings() + modules = [] + dirpath = os.path.dirname(pype.modules.__file__) + for module_name in os.listdir(dirpath): + # Check if path lead to a folder + full_path = os.path.join(dirpath, module_name) + if not os.path.isdir(full_path): + continue + + # Skip known invalid names + if module_name in self.skip_module_names: + continue + + import_name = "pype.modules.{}".format(module_name) + try: + modules.append( + __import__(import_name, fromlist=[""]) + ) + + except Exception: + self.log.warning( + "Couldn't import {}".format(import_name), exc_info=True + ) + + pype_module_classes = [] + for module in modules: + try: + pype_module_classes.extend( + self._classes_from_module(PypeModule, module) + ) + except Exception: + self.log.warning( + "Couldn't import {}".format(import_name), exc_info=True + ) + + pype_modules = {} + for pype_module_class in pype_module_classes: + try: + pype_module = pype_module_class(settings) + if pype_module.enabled: + pype_modules[pype_module.id] = pype_module + except Exception: + self.log.warning( + "Couldn't create instance of {}".format( + pype_module_class.__class__.__name__ + ), + exc_info=True + ) + return pype_modules + + def _classes_from_module(self, superclass, module): + classes = list() + + def recursive_bases(klass): + output = [] + output.extend(klass.__bases__) + for base in klass.__bases__: + output.extend(recursive_bases(base)) + return output + + for name in dir(module): + # It could be anything at this point + obj = getattr(module, name) + + if not inspect.isclass(obj) or not len(obj.__bases__) > 0: + continue + + # Use string comparison rather than `issubclass` + # in order to support reloading of this module. + bases = recursive_bases(obj) + if not any(base.__name__ == superclass.__name__ for base in bases): + continue + + classes.append(obj) + + return classes diff --git a/pype/plugins/ftrack/publish/integrate_ftrack_api.py b/pype/plugins/ftrack/publish/integrate_ftrack_api.py index 0c4c6d49b5..2c8e06a099 100644 --- a/pype/plugins/ftrack/publish/integrate_ftrack_api.py +++ b/pype/plugins/ftrack/publish/integrate_ftrack_api.py @@ -97,6 +97,7 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): except Exception: tp, value, tb = sys.exc_info() session.rollback() + session._configure_locations() six.reraise(tp, value, tb) def process(self, instance): @@ -178,6 +179,7 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): except Exception: tp, value, tb = sys.exc_info() session.rollback() + session._configure_locations() six.reraise(tp, value, tb) # Adding metadata @@ -228,6 +230,7 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): except Exception: tp, value, tb = sys.exc_info() session.rollback() + session._configure_locations() six.reraise(tp, value, tb) # Adding metadata @@ -242,6 +245,7 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): session.commit() except Exception: session.rollback() + session._configure_locations() self.log.warning(( "Comment was not possible to set for AssetVersion" "\"{0}\". Can't set it's value to: \"{1}\"" @@ -258,6 +262,7 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): continue except Exception: session.rollback() + session._configure_locations() self.log.warning(( "Custom Attrubute \"{0}\"" @@ -272,6 +277,7 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): except Exception: tp, value, tb = sys.exc_info() session.rollback() + session._configure_locations() six.reraise(tp, value, tb) # Component @@ -316,6 +322,7 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): except Exception: tp, value, tb = sys.exc_info() session.rollback() + session._configure_locations() six.reraise(tp, value, tb) # Reset members in memory @@ -432,6 +439,7 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): except Exception: tp, value, tb = sys.exc_info() session.rollback() + session._configure_locations() six.reraise(tp, value, tb) if assetversion_entity not in used_asset_versions: diff --git a/pype/plugins/ftrack/publish/integrate_ftrack_note.py b/pype/plugins/ftrack/publish/integrate_ftrack_note.py index 9566207145..acd295854d 100644 --- a/pype/plugins/ftrack/publish/integrate_ftrack_note.py +++ b/pype/plugins/ftrack/publish/integrate_ftrack_note.py @@ -145,4 +145,5 @@ class IntegrateFtrackNote(pyblish.api.InstancePlugin): except Exception: tp, value, tb = sys.exc_info() session.rollback() + session._configure_locations() six.reraise(tp, value, tb) diff --git a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py index e8a6151efd..7d4e0333d6 100644 --- a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py +++ b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py @@ -2,7 +2,6 @@ import sys import six import pyblish.api from avalon import io -from pprint import pformat try: from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_AUTO_SYNC @@ -46,9 +45,6 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): hierarchy_context = self.context.data["hierarchyContext"] - self.log.debug( - f"__ hierarchy_context: `{pformat(hierarchy_context)}`") - self.session = self.context.data["ftrackSession"] project_name = self.context.data["projectEntity"]["name"] query = 'Project where full_name is "{}"'.format(project_name) @@ -134,6 +130,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): except Exception: tp, value, tb = sys.exc_info() self.session.rollback() + self.session._configure_locations() six.reraise(tp, value, tb) # TASKS @@ -162,6 +159,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): except Exception: tp, value, tb = sys.exc_info() self.session.rollback() + self.session._configure_locations() six.reraise(tp, value, tb) # Incoming links. @@ -171,6 +169,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): except Exception: tp, value, tb = sys.exc_info() self.session.rollback() + self.session._configure_locations() six.reraise(tp, value, tb) # Create notes. @@ -191,6 +190,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): except Exception: tp, value, tb = sys.exc_info() self.session.rollback() + self.session._configure_locations() six.reraise(tp, value, tb) # Import children. @@ -207,6 +207,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): except Exception: tp, value, tb = sys.exc_info() self.session.rollback() + self.session._configure_locations() six.reraise(tp, value, tb) # Create new links. @@ -248,6 +249,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): except Exception: tp, value, tb = sys.exc_info() self.session.rollback() + self.session._configure_locations() six.reraise(tp, value, tb) return task @@ -262,6 +264,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): except Exception: tp, value, tb = sys.exc_info() self.session.rollback() + self.session._configure_locations() six.reraise(tp, value, tb) return entity @@ -276,7 +279,8 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): except Exception: tp, value, tb = sys.exc_info() self.session.rollback() - raise + self.session._configure_locations() + six.reraise(tp, value, tb) def auto_sync_on(self, project): @@ -289,4 +293,5 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): except Exception: tp, value, tb = sys.exc_info() self.session.rollback() - raise + self.session._configure_locations() + six.reraise(tp, value, tb) diff --git a/pype/plugins/global/load/copy_file.py b/pype/plugins/global/load/copy_file.py index bbb8e1d6f7..1acacf6b27 100644 --- a/pype/plugins/global/load/copy_file.py +++ b/pype/plugins/global/load/copy_file.py @@ -20,8 +20,8 @@ class CopyFile(api.Loader): def copy_file_to_clipboard(path): from avalon.vendor.Qt import QtCore, QtWidgets - app = QtWidgets.QApplication.instance() - assert app, "Must have running QApplication instance" + clipboard = QtWidgets.QApplication.clipboard() + assert clipboard, "Must have running QApplication instance" # Build mime data for clipboard data = QtCore.QMimeData() @@ -29,5 +29,4 @@ class CopyFile(api.Loader): data.setUrls([url]) # Set to Clipboard - clipboard = app.clipboard() clipboard.setMimeData(data) diff --git a/pype/plugins/global/load/copy_file_path.py b/pype/plugins/global/load/copy_file_path.py index cfda9dc271..f64f3e76d8 100644 --- a/pype/plugins/global/load/copy_file_path.py +++ b/pype/plugins/global/load/copy_file_path.py @@ -19,11 +19,10 @@ class CopyFilePath(api.Loader): @staticmethod def copy_path_to_clipboard(path): - from avalon.vendor.Qt import QtCore, QtWidgets + from avalon.vendor.Qt import QtWidgets - app = QtWidgets.QApplication.instance() - assert app, "Must have running QApplication instance" + clipboard = QtWidgets.QApplication.clipboard() + assert clipboard, "Must have running QApplication instance" # Set to Clipboard - clipboard = app.clipboard() clipboard.setText(os.path.normpath(path)) diff --git a/pype/plugins/global/publish/collect_anatomy_instance_data.py b/pype/plugins/global/publish/collect_anatomy_instance_data.py index 44a4d43946..446f671b86 100644 --- a/pype/plugins/global/publish/collect_anatomy_instance_data.py +++ b/pype/plugins/global/publish/collect_anatomy_instance_data.py @@ -23,123 +23,256 @@ Provides: import copy import json +import collections from avalon import io import pyblish.api -class CollectAnatomyInstanceData(pyblish.api.InstancePlugin): - """Collect Instance specific Anatomy data.""" +class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): + """Collect Instance specific Anatomy data. + + Plugin is running for all instances on context even not active instances. + """ order = pyblish.api.CollectorOrder + 0.49 label = "Collect Anatomy Instance data" - def process(self, instance): - # get all the stuff from the database - anatomy_data = copy.deepcopy(instance.context.data["anatomyData"]) - project_entity = instance.context.data["projectEntity"] - context_asset_entity = instance.context.data["assetEntity"] - instance_asset_entity = instance.data.get("assetEntity") + def process(self, context): + self.log.info("Collecting anatomy data for all instances.") - asset_name = instance.data["asset"] + self.fill_missing_asset_docs(context) + self.fill_latest_versions(context) + self.fill_anatomy_data(context) - # There is possibility that assetEntity on instance is already set - # which can happen in standalone publisher - if ( - instance_asset_entity - and instance_asset_entity["name"] == asset_name - ): - asset_entity = instance_asset_entity + self.log.info("Anatomy Data collection finished.") - # Check if asset name is the same as what is in context - # - they may be different, e.g. in NukeStudio - elif context_asset_entity["name"] == asset_name: - asset_entity = context_asset_entity + def fill_missing_asset_docs(self, context): + self.log.debug("Qeurying asset documents for instances.") - else: - asset_entity = io.find_one({ - "type": "asset", - "name": asset_name, - "parent": project_entity["_id"] - }) + context_asset_doc = context.data["assetEntity"] - subset_name = instance.data["subset"] - version_number = instance.data.get("version") - latest_version = None + instances_with_missing_asset_doc = collections.defaultdict(list) + for instance in context: + instance_asset_doc = instance.data.get("assetEntity") + _asset_name = instance.data["asset"] - if asset_entity: - subset_entity = io.find_one({ - "type": "subset", - "name": subset_name, - "parent": asset_entity["_id"] - }) + # There is possibility that assetEntity on instance is already set + # which can happen in standalone publisher + if ( + instance_asset_doc + and instance_asset_doc["name"] == _asset_name + ): + continue + + # Check if asset name is the same as what is in context + # - they may be different, e.g. in NukeStudio + if context_asset_doc["name"] == _asset_name: + instance.data["assetEntity"] = context_asset_doc - if subset_entity is None: - self.log.debug("Subset entity does not exist yet.") else: - version_entity = io.find_one( - { - "type": "version", - "parent": subset_entity["_id"] - }, - sort=[("name", -1)] - ) - if version_entity: - latest_version = version_entity["name"] + instances_with_missing_asset_doc[_asset_name].append(instance) - # If version is not specified for instance or context - if version_number is None: - # TODO we should be able to change default version by studio - # preferences (like start with version number `0`) - version_number = 1 - # use latest version (+1) if already any exist - if latest_version is not None: - version_number += int(latest_version) + if not instances_with_missing_asset_doc: + self.log.debug("All instances already had right asset document.") + return - anatomy_updates = { - "asset": asset_name, - "family": instance.data["family"], - "subset": subset_name, - "version": version_number + asset_names = list(instances_with_missing_asset_doc.keys()) + self.log.debug("Querying asset documents with names: {}".format( + ", ".join(["\"{}\"".format(name) for name in asset_names]) + )) + asset_docs = io.find({ + "type": "asset", + "name": {"$in": asset_names} + }) + asset_docs_by_name = { + asset_doc["name"]: asset_doc + for asset_doc in asset_docs } - if ( - asset_entity - and asset_entity["_id"] != context_asset_entity["_id"] - ): - parents = asset_entity["data"].get("parents") or list() - anatomy_updates["hierarchy"] = "/".join(parents) - task_name = instance.data.get("task") - if task_name: - anatomy_updates["task"] = task_name + not_found_asset_names = [] + for asset_name, instances in instances_with_missing_asset_doc.items(): + asset_doc = asset_docs_by_name.get(asset_name) + if not asset_doc: + not_found_asset_names.append(asset_name) + continue - # Version should not be collected since may be instance - anatomy_data.update(anatomy_updates) + for _instance in instances: + _instance.data["assetEntity"] = asset_doc - resolution_width = instance.data.get("resolutionWidth") - if resolution_width: - anatomy_data["resolution_width"] = resolution_width + if not_found_asset_names: + joined_asset_names = ", ".join( + ["\"{}\"".format(name) for name in not_found_asset_names] + ) + self.log.warning(( + "Not found asset documents with names \"{}\"." + ).format(joined_asset_names)) - resolution_height = instance.data.get("resolutionHeight") - if resolution_height: - anatomy_data["resolution_height"] = resolution_height + def fill_latest_versions(self, context): + """Try to find latest version for each instance's subset. - pixel_aspect = instance.data.get("pixelAspect") - if pixel_aspect: - anatomy_data["pixel_aspect"] = float("{:0.2f}".format( - float(pixel_aspect))) + Key "latestVersion" is always set to latest version or `None`. - fps = instance.data.get("fps") - if fps: - anatomy_data["fps"] = float("{:0.2f}".format( - float(fps))) + Args: + context (pyblish.Context) - instance.data["projectEntity"] = project_entity - instance.data["assetEntity"] = asset_entity - instance.data["anatomyData"] = anatomy_data - instance.data["latestVersion"] = latest_version - # TODO should be version number set here? - instance.data["version"] = version_number + Returns: + None - self.log.info("Instance anatomy Data collected") - self.log.debug(json.dumps(anatomy_data, indent=4)) + """ + self.log.debug("Qeurying latest versions for instances.") + + hierarchy = {} + subset_names = set() + asset_ids = set() + for instance in context: + # Make sure `"latestVersion"` key is set + latest_version = instance.data.get("latestVersion") + instance.data["latestVersion"] = latest_version + + # Skip instances withou "assetEntity" + asset_doc = instance.data.get("assetEntity") + if not asset_doc: + continue + + # Store asset ids and subset names for queries + asset_id = asset_doc["_id"] + subset_name = instance.data["subset"] + asset_ids.add(asset_id) + subset_names.add(subset_name) + + # Prepare instance hiearchy for faster filling latest versions + if asset_id not in hierarchy: + hierarchy[asset_id] = {} + if subset_name not in hierarchy[asset_id]: + hierarchy[asset_id][subset_name] = [] + hierarchy[asset_id][subset_name].append(instance) + + subset_docs = list(io.find({ + "type": "subset", + "parent": {"$in": list(asset_ids)}, + "name": {"$in": list(subset_names)} + })) + + subset_ids = [ + subset_doc["_id"] + for subset_doc in subset_docs + ] + + last_version_by_subset_id = self._query_last_versions(subset_ids) + for subset_doc in subset_docs: + subset_id = subset_doc["_id"] + last_version = last_version_by_subset_id.get(subset_id) + if last_version is None: + continue + + asset_id = subset_doc["parent"] + subset_name = subset_doc["name"] + _instances = hierarchy[asset_id][subset_name] + for _instance in _instances: + _instance.data["latestVersion"] = last_version + + def _query_last_versions(self, subset_ids): + """Retrieve all latest versions for entered subset_ids. + + Args: + subset_ids (list): List of subset ids with type `ObjectId`. + + Returns: + dict: Key is subset id and value is last version name. + """ + _pipeline = [ + # Find all versions of those subsets + {"$match": { + "type": "version", + "parent": {"$in": subset_ids} + }}, + # Sorting versions all together + {"$sort": {"name": 1}}, + # Group them by "parent", but only take the last + {"$group": { + "_id": "$parent", + "_version_id": {"$last": "$_id"}, + "name": {"$last": "$name"} + }} + ] + + last_version_by_subset_id = {} + for doc in io.aggregate(_pipeline): + subset_id = doc["_id"] + last_version_by_subset_id[subset_id] = doc["name"] + + return last_version_by_subset_id + + def fill_anatomy_data(self, context): + self.log.debug("Storing anatomy data to instance data.") + + project_doc = context.data["projectEntity"] + context_asset_doc = context.data["assetEntity"] + + for instance in context: + version_number = instance.data.get("version") + # If version is not specified for instance or context + if version_number is None: + # TODO we should be able to change default version by studio + # preferences (like start with version number `0`) + version_number = 1 + # use latest version (+1) if already any exist + latest_version = instance.data["latestVersion"] + if latest_version is not None: + version_number += int(latest_version) + + anatomy_updates = { + "asset": instance.data["asset"], + "family": instance.data["family"], + "subset": instance.data["subset"], + "version": version_number + } + + # Hiearchy + asset_doc = instance.data.get("assetEntity") + if asset_doc and asset_doc["_id"] != context_asset_doc["_id"]: + parents = asset_doc["data"].get("parents") or list() + anatomy_updates["hierarchy"] = "/".join(parents) + + # Task + task_name = instance.data.get("task") + if task_name: + anatomy_updates["task"] = task_name + + # Additional data + resolution_width = instance.data.get("resolutionWidth") + if resolution_width: + anatomy_updates["resolution_width"] = resolution_width + + resolution_height = instance.data.get("resolutionHeight") + if resolution_height: + anatomy_updates["resolution_height"] = resolution_height + + pixel_aspect = instance.data.get("pixelAspect") + if pixel_aspect: + anatomy_updates["pixel_aspect"] = float( + "{:0.2f}".format(float(pixel_aspect)) + ) + + fps = instance.data.get("fps") + if fps: + anatomy_updates["fps"] = float("{:0.2f}".format(float(fps))) + + anatomy_data = copy.deepcopy(context.data["anatomyData"]) + anatomy_data.update(anatomy_updates) + + # Store anatomy data + instance.data["projectEntity"] = project_doc + instance.data["anatomyData"] = anatomy_data + instance.data["version"] = version_number + + # Log collected data + instance_name = instance.data["name"] + instance_label = instance.data.get("label") + if instance_label: + instance_name += "({})".format(instance_label) + self.log.debug("Anatomy data for instance {}: {}".format( + instance_name, + json.dumps(anatomy_data, indent=4) + )) diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index 4443cfe223..6e8da1b054 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -195,11 +195,14 @@ class ExtractBurnin(pype.api.Extractor): if "delete" in new_repre["tags"]: new_repre["tags"].remove("delete") - # Update name and outputName to be able have multiple outputs - # Join previous "outputName" with filename suffix - new_name = "_".join([new_repre["outputName"], filename_suffix]) - new_repre["name"] = new_name - new_repre["outputName"] = new_name + if len(repre_burnin_defs.keys()) > 1: + # Update name and outputName to be + # able have multiple outputs in case of more burnin presets + # Join previous "outputName" with filename suffix + new_name = "_".join( + [new_repre["outputName"], filename_suffix]) + new_repre["name"] = new_name + new_repre["outputName"] = new_name # Prepare paths and files for process. self.input_output_paths(new_repre, temp_data, filename_suffix) diff --git a/pype/plugins/global/publish/extract_hierarchy_avalon.py b/pype/plugins/global/publish/extract_hierarchy_avalon.py index 4253c35929..5d11eae058 100644 --- a/pype/plugins/global/publish/extract_hierarchy_avalon.py +++ b/pype/plugins/global/publish/extract_hierarchy_avalon.py @@ -1,6 +1,6 @@ import pyblish.api from avalon import io - +from copy import deepcopy class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): """Create entities in Avalon based on collected data.""" @@ -14,14 +14,12 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): if "hierarchyContext" not in context.data: self.log.info("skipping IntegrateHierarchyToAvalon") return + hierarchy_context = deepcopy(context.data["hierarchyContext"]) if not io.Session: io.install() active_assets = [] - hierarchy_context = context.data["hierarchyContext"] - hierarchy_assets = self._get_assets(hierarchy_context) - # filter only the active publishing insatnces for instance in context: if instance.data.get("publish") is False: @@ -32,13 +30,13 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): active_assets.append(instance.data["asset"]) - # filter out only assets which are activated as isntances - new_hierarchy_assets = {k: v for k, v in hierarchy_assets.items() - if k in active_assets} + # remove duplicity in list + self.active_assets = list(set(active_assets)) + self.log.debug("__ self.active_assets: {}".format(self.active_assets)) - # modify the hierarchy context so there are only fitred assets - self._set_assets(hierarchy_context, new_hierarchy_assets) + hierarchy_context = self._get_assets(hierarchy_context) + self.log.debug("__ hierarchy_context: {}".format(hierarchy_context)) input_data = context.data["hierarchyContext"] = hierarchy_context self.project = None @@ -178,35 +176,18 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): Usually the last part of deep dictionary which is not having any children """ + input_dict_copy = deepcopy(input_dict) + for key in input_dict.keys(): + self.log.debug("__ key: {}".format(key)) # check if child key is available if input_dict[key].get("childs"): # loop deeper - return self._get_assets(input_dict[key]["childs"]) + input_dict_copy[key]["childs"] = self._get_assets( + input_dict[key]["childs"]) else: - # give the dictionary with assets - return input_dict + # filter out unwanted assets + if key not in self.active_assets: + input_dict_copy.pop(key, None) - def _set_assets(self, input_dict, new_assets=None): - """ Modify the hierarchy context dictionary. - It will replace the asset dictionary with only the filtred one. - """ - for key in input_dict.keys(): - # check if child key is available - if input_dict[key].get("childs"): - # return if this is just for testing purpose and no - # new_assets property is avalable - if not new_assets: - return True - - # test for deeper inner children availabelity - if self._set_assets(input_dict[key]["childs"]): - # if one level deeper is still children available - # then process farther - self._set_assets(input_dict[key]["childs"], new_assets) - else: - # or just assign the filtred asset ditionary - input_dict[key]["childs"] = new_assets - else: - # test didnt find more childs in input dictionary - return None + return input_dict_copy diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index 0bae1b2ddc..318c843b80 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -633,6 +633,26 @@ class ExtractReview(pyblish.api.InstancePlugin): input_width = int(input_data["width"]) input_height = int(input_data["height"]) + # Make sure input width and height is not an odd number + input_width_is_odd = bool(input_width % 2 != 0) + input_height_is_odd = bool(input_height % 2 != 0) + if input_width_is_odd or input_height_is_odd: + # Add padding to input and make sure this filter is at first place + filters.append("pad=width=ceil(iw/2)*2:height=ceil(ih/2)*2") + + # Change input width or height as first filter will change them + if input_width_is_odd: + self.log.info(( + "Converting input width from odd to even number. {} -> {}" + ).format(input_width, input_width + 1)) + input_width += 1 + + if input_height_is_odd: + self.log.info(( + "Converting input height from odd to even number. {} -> {}" + ).format(input_height, input_height + 1)) + input_height += 1 + self.log.debug("pixel_aspect: `{}`".format(pixel_aspect)) self.log.debug("input_width: `{}`".format(input_width)) self.log.debug("input_height: `{}`".format(input_height)) @@ -654,6 +674,22 @@ class ExtractReview(pyblish.api.InstancePlugin): output_width = int(output_width) output_height = int(output_height) + # Make sure output width and height is not an odd number + # When this can happen: + # - if output definition has set width and height with odd number + # - `instance.data` contain width and height with odd numbeer + if output_width % 2 != 0: + self.log.warning(( + "Converting output width from odd to even number. {} -> {}" + ).format(output_width, output_width + 1)) + output_width += 1 + + if output_height % 2 != 0: + self.log.warning(( + "Converting output height from odd to even number. {} -> {}" + ).format(output_height, output_height + 1)) + output_height += 1 + self.log.debug( "Output resolution is {}x{}".format(output_width, output_height) ) diff --git a/pype/plugins/global/publish/validate_intent.py b/pype/plugins/global/publish/validate_intent.py new file mode 100644 index 0000000000..80bcb0e164 --- /dev/null +++ b/pype/plugins/global/publish/validate_intent.py @@ -0,0 +1,31 @@ +import pyblish.api +import os + + +class ValidateIntent(pyblish.api.ContextPlugin): + """Validate intent of the publish. + + It is required to fill the intent of this publish. Chech the log + for more details + """ + + order = pyblish.api.ValidatorOrder + + label = "Validate Intent" + # TODO: this should be off by default and only activated viac config + tasks = ["animation"] + hosts = ["harmony"] + if os.environ.get("AVALON_TASK") not in tasks: + active = False + + def process(self, context): + msg = ( + "Please make sure that you select the intent of this publish." + ) + + intent = context.data.get("intent") + self.log.debug(intent) + assert intent, msg + + intent_value = intent.get("value") + assert intent is not "", msg diff --git a/pype/plugins/maya/load/load_audio.py b/pype/plugins/maya/load/load_audio.py index ca38082ed0..81bcca48e1 100644 --- a/pype/plugins/maya/load/load_audio.py +++ b/pype/plugins/maya/load/load_audio.py @@ -1,7 +1,7 @@ from maya import cmds, mel import pymel.core as pc -from avalon import api +from avalon import api, io from avalon.maya.pipeline import containerise from avalon.maya import lib @@ -58,6 +58,13 @@ class AudioLoader(api.Loader): type="string" ) + # Set frame range. + version = io.find_one({"_id": representation["parent"]}) + subset = io.find_one({"_id": version["parent"]}) + asset = io.find_one({"_id": subset["parent"]}) + audio_node.sourceStart.set(1 - asset["data"]["frameStart"]) + audio_node.sourceEnd.set(asset["data"]["frameEnd"]) + def switch(self, container, representation): self.update(container, representation) diff --git a/pype/plugins/maya/load/load_image_plane.py b/pype/plugins/maya/load/load_image_plane.py index 7d8ae27f89..e17382f7ed 100644 --- a/pype/plugins/maya/load/load_image_plane.py +++ b/pype/plugins/maya/load/load_image_plane.py @@ -1,7 +1,7 @@ import pymel.core as pc import maya.cmds as cmds -from avalon import api +from avalon import api, io from avalon.maya.pipeline import containerise from avalon.maya import lib from Qt import QtWidgets @@ -147,6 +147,17 @@ class ImagePlaneLoader(api.Loader): type="string" ) + # Set frame range. + version = io.find_one({"_id": representation["parent"]}) + subset = io.find_one({"_id": version["parent"]}) + asset = io.find_one({"_id": subset["parent"]}) + start_frame = asset["data"]["frameStart"] + end_frame = asset["data"]["frameEnd"] + image_plane_shape.frameOffset.set(1 - start_frame) + image_plane_shape.frameIn.set(start_frame) + image_plane_shape.frameOut.set(end_frame) + image_plane_shape.frameCache.set(end_frame) + def switch(self, container, representation): self.update(container, representation) diff --git a/pype/plugins/maya/publish/extract_camera_mayaScene.py b/pype/plugins/maya/publish/extract_camera_mayaScene.py index 03dde031e9..1a0f4694d1 100644 --- a/pype/plugins/maya/publish/extract_camera_mayaScene.py +++ b/pype/plugins/maya/publish/extract_camera_mayaScene.py @@ -101,7 +101,7 @@ class ExtractCameraMayaScene(pype.api.Extractor): self.log.info( "Using {} as scene type".format(self.scene_type)) break - except AttributeError: + except KeyError: # no preset found pass diff --git a/pype/plugins/maya/publish/extract_maya_scene_raw.py b/pype/plugins/maya/publish/extract_maya_scene_raw.py index 2971572552..d273646af8 100644 --- a/pype/plugins/maya/publish/extract_maya_scene_raw.py +++ b/pype/plugins/maya/publish/extract_maya_scene_raw.py @@ -33,7 +33,7 @@ class ExtractMayaSceneRaw(pype.api.Extractor): self.log.info( "Using {} as scene type".format(self.scene_type)) break - except AttributeError: + except KeyError: # no preset found pass # Define extract output file path diff --git a/pype/plugins/maya/publish/extract_model.py b/pype/plugins/maya/publish/extract_model.py index 330e471e53..d77e65f989 100644 --- a/pype/plugins/maya/publish/extract_model.py +++ b/pype/plugins/maya/publish/extract_model.py @@ -41,7 +41,7 @@ class ExtractModel(pype.api.Extractor): self.log.info( "Using {} as scene type".format(self.scene_type)) break - except AttributeError: + except KeyError: # no preset found pass # Define extract output file path diff --git a/pype/plugins/maya/publish/extract_yeti_rig.py b/pype/plugins/maya/publish/extract_yeti_rig.py index 2f66d3e026..d48a956b88 100644 --- a/pype/plugins/maya/publish/extract_yeti_rig.py +++ b/pype/plugins/maya/publish/extract_yeti_rig.py @@ -111,7 +111,7 @@ class ExtractYetiRig(pype.api.Extractor): self.log.info( "Using {} as scene type".format(self.scene_type)) break - except AttributeError: + except KeyError: # no preset found pass yeti_nodes = cmds.ls(instance, type="pgYetiMaya") diff --git a/pype/plugins/nukestudio/publish/extract_review_cutup_video.py b/pype/plugins/nukestudio/publish/extract_review_cutup_video.py index a4fbf90bed..d1ce3675b1 100644 --- a/pype/plugins/nukestudio/publish/extract_review_cutup_video.py +++ b/pype/plugins/nukestudio/publish/extract_review_cutup_video.py @@ -76,7 +76,7 @@ class ExtractReviewCutUpVideo(pype.api.Extractor): # check if audio stream is in input video file ffprob_cmd = ( - "{ffprobe_path} -i {full_input_path} -show_streams " + "{ffprobe_path} -i \"{full_input_path}\" -show_streams " "-select_streams a -loglevel error" ).format(**locals()) self.log.debug("ffprob_cmd: {}".format(ffprob_cmd)) @@ -106,7 +106,7 @@ class ExtractReviewCutUpVideo(pype.api.Extractor): # try to get video native resolution data try: resolution_output = pype.api.subprocess(( - "{ffprobe_path} -i {full_input_path} -v error " + "{ffprobe_path} -i \"{full_input_path}\" -v error " "-select_streams v:0 -show_entries " "stream=width,height -of csv=s=x:p=0" ).format(**locals())) @@ -193,7 +193,7 @@ class ExtractReviewCutUpVideo(pype.api.Extractor): # append ffmpeg input video clip input_args.append("-ss {:0.2f}".format(start_sec)) input_args.append("-t {:0.2f}".format(duration_sec)) - input_args.append("-i {}".format(full_input_path)) + input_args.append("-i \"{}\"".format(full_input_path)) # add copy audio video codec if only shortening clip if ("_cut-bigger" in tags) and (not empty_add): @@ -203,8 +203,7 @@ class ExtractReviewCutUpVideo(pype.api.Extractor): output_args.append("-intra") # output filename - output_args.append("-y") - output_args.append(full_output_path) + output_args.append("-y \"{}\"".format(full_output_path)) mov_args = [ ffmpeg_path, diff --git a/pype/plugins/standalonepublisher/publish/collect_clip_instances.py b/pype/plugins/standalonepublisher/publish/collect_clip_instances.py index a7af8df143..def0c13a78 100644 --- a/pype/plugins/standalonepublisher/publish/collect_clip_instances.py +++ b/pype/plugins/standalonepublisher/publish/collect_clip_instances.py @@ -17,13 +17,13 @@ class CollectClipInstances(pyblish.api.InstancePlugin): subsets = { "referenceMain": { "family": "review", - "families": ["review", "ftrack"], + "families": ["clip", "ftrack"], # "ftrackFamily": "review", "extension": ".mp4" }, "audioMain": { "family": "audio", - "families": ["ftrack"], + "families": ["clip", "ftrack"], # "ftrackFamily": "audio", "extension": ".wav", # "version": 1 diff --git a/pype/plugins/standalonepublisher/publish/collect_instance_data.py b/pype/plugins/standalonepublisher/publish/collect_instance_data.py new file mode 100644 index 0000000000..1b32ea9144 --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/collect_instance_data.py @@ -0,0 +1,29 @@ +""" +Requires: + Nothing + +Provides: + Instance +""" + +import pyblish.api +from pprint import pformat + + +class CollectInstanceData(pyblish.api.InstancePlugin): + """ + Collector with only one reason for its existence - remove 'ftrack' + family implicitly added by Standalone Publisher + """ + + label = "Collect instance data" + order = pyblish.api.CollectorOrder + 0.49 + families = ["render", "plate"] + hosts = ["standalonepublisher"] + + def process(self, instance): + fps = instance.data["assetEntity"]["data"]["fps"] + instance.data.update({ + "fps": fps + }) + self.log.debug(f"instance.data: {pformat(instance.data)}") diff --git a/pype/plugins/standalonepublisher/publish/extract_shot_data.py b/pype/plugins/standalonepublisher/publish/extract_shot_data.py index c39247d6d6..d5af7638ee 100644 --- a/pype/plugins/standalonepublisher/publish/extract_shot_data.py +++ b/pype/plugins/standalonepublisher/publish/extract_shot_data.py @@ -10,7 +10,7 @@ class ExtractShotData(pype.api.Extractor): label = "Extract Shot Data" hosts = ["standalonepublisher"] - families = ["review", "audio"] + families = ["clip"] # presets diff --git a/pype/plugins/standalonepublisher/publish/extract_thumbnail.py b/pype/plugins/standalonepublisher/publish/extract_thumbnail.py index cddc9c3a82..5882775083 100644 --- a/pype/plugins/standalonepublisher/publish/extract_thumbnail.py +++ b/pype/plugins/standalonepublisher/publish/extract_thumbnail.py @@ -64,6 +64,7 @@ class ExtractThumbnailSP(pyblish.api.InstancePlugin): else: # Convert to jpeg if not yet full_input_path = os.path.join(thumbnail_repre["stagingDir"], file) + full_input_path = '"{}"'.format(full_input_path) self.log.info("input {}".format(full_input_path)) full_thumbnail_path = tempfile.mkstemp(suffix=".jpg")[1] diff --git a/pype/plugins/standalonepublisher/publish/validate_editorial_resources.py b/pype/plugins/standalonepublisher/publish/validate_editorial_resources.py index ebc449c4ec..7e1694fbd1 100644 --- a/pype/plugins/standalonepublisher/publish/validate_editorial_resources.py +++ b/pype/plugins/standalonepublisher/publish/validate_editorial_resources.py @@ -1,5 +1,3 @@ -import os - import pyblish.api import pype.api @@ -9,10 +7,14 @@ class ValidateEditorialResources(pyblish.api.InstancePlugin): label = "Validate Editorial Resources" hosts = ["standalonepublisher"] - families = ["audio", "review"] + families = ["clip"] + order = pype.api.ValidateContentsOrder def process(self, instance): + self.log.debug( + f"Instance: {instance}, Families: " + f"{[instance.data['family']] + instance.data['families']}") check_file = instance.data["editorialVideoPath"] msg = f"Missing \"{check_file}\"." assert check_file, msg diff --git a/pype/resources/app_icons/hiero.png b/pype/resources/app_icons/hiero.png new file mode 100644 index 0000000000..04bbf6265b Binary files /dev/null and b/pype/resources/app_icons/hiero.png differ diff --git a/pype/scripts/otio_burnin.py b/pype/scripts/otio_burnin.py index 156896a759..6607726c73 100644 --- a/pype/scripts/otio_burnin.py +++ b/pype/scripts/otio_burnin.py @@ -15,7 +15,7 @@ ffprobe_path = pype.lib.get_ffmpeg_tool_path("ffprobe") FFMPEG = ( - '{} -loglevel panic -i %(input)s %(filters)s %(args)s%(output)s' + '{} -loglevel panic -i "%(input)s" %(filters)s %(args)s%(output)s' ).format(ffmpeg_path) FFPROBE = ( diff --git a/pype/settings/__init__.py b/pype/settings/__init__.py index 7e73d541a4..7a99ba0b2f 100644 --- a/pype/settings/__init__.py +++ b/pype/settings/__init__.py @@ -1,9 +1,11 @@ from .lib import ( system_settings, - project_settings + project_settings, + environments ) __all__ = ( "system_settings", - "project_settings" + "project_settings", + "environments" ) diff --git a/pype/settings/defaults/system_settings/environments/avalon.json b/pype/settings/defaults/environments/avalon.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/avalon.json rename to pype/settings/defaults/environments/avalon.json diff --git a/pype/settings/defaults/system_settings/environments/blender.json b/pype/settings/defaults/environments/blender.json similarity index 82% rename from pype/settings/defaults/system_settings/environments/blender.json rename to pype/settings/defaults/environments/blender.json index 6f4f6a012d..00a4070b8e 100644 --- a/pype/settings/defaults/system_settings/environments/blender.json +++ b/pype/settings/defaults/environments/blender.json @@ -3,5 +3,6 @@ "PYTHONPATH": [ "{PYPE_SETUP_PATH}/repos/avalon-core/setup/blender", "{PYTHONPATH}" - ] + ], + "CREATE_NEW_CONSOLE": "yes" } diff --git a/pype/settings/defaults/system_settings/environments/celaction.json b/pype/settings/defaults/environments/celaction.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/celaction.json rename to pype/settings/defaults/environments/celaction.json diff --git a/pype/settings/defaults/system_settings/environments/deadline.json b/pype/settings/defaults/environments/deadline.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/deadline.json rename to pype/settings/defaults/environments/deadline.json diff --git a/pype/settings/defaults/system_settings/environments/ftrack.json b/pype/settings/defaults/environments/ftrack.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/ftrack.json rename to pype/settings/defaults/environments/ftrack.json diff --git a/pype/settings/defaults/system_settings/environments/global.json b/pype/settings/defaults/environments/global.json similarity index 91% rename from pype/settings/defaults/system_settings/environments/global.json rename to pype/settings/defaults/environments/global.json index ef528e6857..ba467d2f5d 100644 --- a/pype/settings/defaults/system_settings/environments/global.json +++ b/pype/settings/defaults/environments/global.json @@ -6,9 +6,9 @@ "PYPE_PROJECT_PLUGINS": "", "STUDIO_SOFT": "{PYP_SETUP_ROOT}/soft", "FFMPEG_PATH": { - "windows": "{VIRTUAL_ENV}/localized/ffmpeg_exec/windows/bin;{PYPE_SETUP_PATH}/vendor/ffmpeg_exec/windows/bin", - "darwin": "{VIRTUAL_ENV}/localized/ffmpeg_exec/darwin/bin:{PYPE_SETUP_PATH}/vendor/ffmpeg_exec/darwin/bin", - "linux": "{VIRTUAL_ENV}/localized/ffmpeg_exec/linux:{PYPE_SETUP_PATH}/vendor/ffmpeg_exec/linux" + "windows": "{VIRTUAL_ENV}/localized/ffmpeg_exec/windows/bin;{PYPE_SETUP_PATH}/vendor/bin/ffmpeg_exec/windows/bin", + "darwin": "{VIRTUAL_ENV}/localized/ffmpeg_exec/darwin/bin:{PYPE_SETUP_PATH}/vendor/bin/ffmpeg_exec/darwin/bin", + "linux": "{VIRTUAL_ENV}/localized/ffmpeg_exec/linux:{PYPE_SETUP_PATH}/vendor/bin/ffmpeg_exec/linux" }, "DJV_PATH": { "windows": [ diff --git a/pype/settings/defaults/system_settings/environments/harmony.json b/pype/settings/defaults/environments/harmony.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/harmony.json rename to pype/settings/defaults/environments/harmony.json diff --git a/pype/settings/defaults/system_settings/environments/houdini.json b/pype/settings/defaults/environments/houdini.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/houdini.json rename to pype/settings/defaults/environments/houdini.json diff --git a/pype/settings/defaults/system_settings/environments/maya.json b/pype/settings/defaults/environments/maya.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/maya.json rename to pype/settings/defaults/environments/maya.json diff --git a/pype/settings/defaults/system_settings/environments/maya_2018.json b/pype/settings/defaults/environments/maya_2018.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/maya_2018.json rename to pype/settings/defaults/environments/maya_2018.json diff --git a/pype/settings/defaults/system_settings/environments/maya_2020.json b/pype/settings/defaults/environments/maya_2020.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/maya_2020.json rename to pype/settings/defaults/environments/maya_2020.json diff --git a/pype/settings/defaults/system_settings/environments/mayabatch.json b/pype/settings/defaults/environments/mayabatch.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/mayabatch.json rename to pype/settings/defaults/environments/mayabatch.json diff --git a/pype/settings/defaults/system_settings/environments/mayabatch_2019.json b/pype/settings/defaults/environments/mayabatch_2019.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/mayabatch_2019.json rename to pype/settings/defaults/environments/mayabatch_2019.json diff --git a/pype/settings/defaults/system_settings/environments/mtoa_3.1.1.json b/pype/settings/defaults/environments/mtoa_3.1.1.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/mtoa_3.1.1.json rename to pype/settings/defaults/environments/mtoa_3.1.1.json diff --git a/pype/settings/defaults/system_settings/environments/muster.json b/pype/settings/defaults/environments/muster.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/muster.json rename to pype/settings/defaults/environments/muster.json diff --git a/pype/settings/defaults/system_settings/environments/nuke.json b/pype/settings/defaults/environments/nuke.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/nuke.json rename to pype/settings/defaults/environments/nuke.json diff --git a/pype/settings/defaults/system_settings/environments/nukestudio.json b/pype/settings/defaults/environments/nukestudio.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/nukestudio.json rename to pype/settings/defaults/environments/nukestudio.json diff --git a/pype/settings/defaults/system_settings/environments/nukestudio_10.0.json b/pype/settings/defaults/environments/nukestudio_10.0.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/nukestudio_10.0.json rename to pype/settings/defaults/environments/nukestudio_10.0.json diff --git a/pype/settings/defaults/system_settings/environments/nukex.json b/pype/settings/defaults/environments/nukex.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/nukex.json rename to pype/settings/defaults/environments/nukex.json diff --git a/pype/settings/defaults/system_settings/environments/nukex_10.0.json b/pype/settings/defaults/environments/nukex_10.0.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/nukex_10.0.json rename to pype/settings/defaults/environments/nukex_10.0.json diff --git a/pype/settings/defaults/environments/photoshop.json b/pype/settings/defaults/environments/photoshop.json new file mode 100644 index 0000000000..d39634ce20 --- /dev/null +++ b/pype/settings/defaults/environments/photoshop.json @@ -0,0 +1,7 @@ +{ + "AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH": "1", + "PYTHONPATH": "{PYTHONPATH}", + "PYPE_LOG_NO_COLORS": "Yes", + "WEBSOCKET_URL": "ws://localhost:8099/ws/", + "WORKFILES_SAVE_AS": "Yes" +} diff --git a/pype/settings/defaults/system_settings/environments/premiere.json b/pype/settings/defaults/environments/premiere.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/premiere.json rename to pype/settings/defaults/environments/premiere.json diff --git a/pype/settings/defaults/system_settings/environments/resolve.json b/pype/settings/defaults/environments/resolve.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/resolve.json rename to pype/settings/defaults/environments/resolve.json diff --git a/pype/settings/defaults/system_settings/environments/storyboardpro.json b/pype/settings/defaults/environments/storyboardpro.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/storyboardpro.json rename to pype/settings/defaults/environments/storyboardpro.json diff --git a/pype/settings/defaults/system_settings/environments/unreal_4.24.json b/pype/settings/defaults/environments/unreal_4.24.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/unreal_4.24.json rename to pype/settings/defaults/environments/unreal_4.24.json diff --git a/pype/settings/defaults/system_settings/environments/vray_4300.json b/pype/settings/defaults/environments/vray_4300.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/vray_4300.json rename to pype/settings/defaults/environments/vray_4300.json diff --git a/pype/settings/defaults/launchers/blender_2.80.toml b/pype/settings/defaults/launchers/blender_2.80.toml new file mode 100644 index 0000000000..88b5ea0c11 --- /dev/null +++ b/pype/settings/defaults/launchers/blender_2.80.toml @@ -0,0 +1,8 @@ +application_dir = "blender" +executable = "blender_2.80" +schema = "avalon-core:application-1.0" +label = "Blender" +label_variant = "2.80" +ftrack_label = "Blender" +icon = "app_icons/blender.png" +ftrack_icon = "{}/app_icons/blender.png" diff --git a/pype/settings/defaults/launchers/blender_2.81.toml b/pype/settings/defaults/launchers/blender_2.81.toml new file mode 100644 index 0000000000..072eaa8141 --- /dev/null +++ b/pype/settings/defaults/launchers/blender_2.81.toml @@ -0,0 +1,9 @@ +application_dir = "blender" +executable = "blender_2.81" +schema = "avalon-core:application-1.0" +label = "Blender" +label_variant = "2.81" +icon = "app_icons/blender.png" + +ftrack_label = "Blender" +ftrack_icon = '{}/app_icons/blender.png' diff --git a/pype/settings/defaults/launchers/blender_2.82.toml b/pype/settings/defaults/launchers/blender_2.82.toml new file mode 100644 index 0000000000..a485f790f1 --- /dev/null +++ b/pype/settings/defaults/launchers/blender_2.82.toml @@ -0,0 +1,9 @@ +application_dir = "blender" +executable = "blender_2.82" +schema = "avalon-core:application-1.0" +label = "Blender" +label_variant = "2.82" +icon = "app_icons/blender.png" + +ftrack_label = "Blender" +ftrack_icon = '{}/app_icons/blender.png' diff --git a/pype/settings/defaults/launchers/blender_2.83.toml b/pype/settings/defaults/launchers/blender_2.83.toml new file mode 100644 index 0000000000..0f98151d01 --- /dev/null +++ b/pype/settings/defaults/launchers/blender_2.83.toml @@ -0,0 +1,9 @@ +application_dir = "blender" +executable = "blender_2.83" +schema = "avalon-core:application-1.0" +label = "Blender" +label_variant = "2.83" +icon = "app_icons/blender.png" + +ftrack_label = "Blender" +ftrack_icon = '{}/app_icons/blender.png' diff --git a/pype/settings/defaults/launchers/celaction_local.toml b/pype/settings/defaults/launchers/celaction_local.toml new file mode 100644 index 0000000000..6cc5d4fa0e --- /dev/null +++ b/pype/settings/defaults/launchers/celaction_local.toml @@ -0,0 +1,9 @@ +executable = "celaction_local" +schema = "avalon-core:application-1.0" +application_dir = "celaction" +label = "CelAction2D" +icon = "app_icons/celaction_local.png" +launch_hook = "pype/hooks/celaction/prelaunch.py/CelactionPrelaunchHook" + +ftrack_label = "CelAction2D" +ftrack_icon = '{}/app_icons/celaction_local.png' diff --git a/pype/settings/defaults/launchers/celaction_publish.toml b/pype/settings/defaults/launchers/celaction_publish.toml new file mode 100644 index 0000000000..dc7ac82673 --- /dev/null +++ b/pype/settings/defaults/launchers/celaction_publish.toml @@ -0,0 +1,8 @@ +schema = "avalon-core:application-1.0" +application_dir = "shell" +executable = "celaction_publish" +label = "Celaction Shell" +icon = "app_icons/celaction.png" + +[environment] +CREATE_NEW_CONSOLE = "Yes" diff --git a/pype/settings/defaults/launchers/darwin/blender_2.82 b/pype/settings/defaults/launchers/darwin/blender_2.82 new file mode 100644 index 0000000000..8254411ea2 --- /dev/null +++ b/pype/settings/defaults/launchers/darwin/blender_2.82 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +open -a blender $@ diff --git a/pype/settings/defaults/launchers/darwin/harmony_17 b/pype/settings/defaults/launchers/darwin/harmony_17 new file mode 100644 index 0000000000..b7eba2c2d0 --- /dev/null +++ b/pype/settings/defaults/launchers/darwin/harmony_17 @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +DIRNAME="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +set >~/environment.tmp +if [ $? -ne -0 ] ; then + echo "ERROR: cannot write to '~/environment.tmp'!" + read -n 1 -s -r -p "Press any key to exit" + return +fi +open -a Terminal.app "$DIRNAME/harmony_17_launch" diff --git a/pype/settings/defaults/launchers/darwin/harmony_17_launch b/pype/settings/defaults/launchers/darwin/harmony_17_launch new file mode 100644 index 0000000000..5dcf5db57e --- /dev/null +++ b/pype/settings/defaults/launchers/darwin/harmony_17_launch @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +source ~/environment.tmp +export $(cut -d= -f1 ~/environment.tmp) +exe="/Applications/Toon Boom Harmony 17 Premium/Harmony Premium.app/Contents/MacOS/Harmony Premium" +$PYPE_PYTHON_EXE -c "import avalon.harmony;avalon.harmony.launch('$exe')" diff --git a/pype/settings/defaults/launchers/darwin/python3 b/pype/settings/defaults/launchers/darwin/python3 new file mode 100644 index 0000000000..c2b82c7638 --- /dev/null +++ b/pype/settings/defaults/launchers/darwin/python3 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +open /usr/bin/python3 --args $@ diff --git a/pype/settings/defaults/launchers/harmony_17.toml b/pype/settings/defaults/launchers/harmony_17.toml new file mode 100644 index 0000000000..dd1c929b1b --- /dev/null +++ b/pype/settings/defaults/launchers/harmony_17.toml @@ -0,0 +1,9 @@ +application_dir = "harmony" +label = "Harmony" +label_variant = "17" +ftrack_label = "Harmony" +schema = "avalon-core:application-1.0" +executable = "harmony_17" +description = "" +icon = "app_icons/harmony.png" +ftrack_icon = '{}/app_icons/harmony.png' diff --git a/pype/settings/defaults/launchers/houdini_16.toml b/pype/settings/defaults/launchers/houdini_16.toml new file mode 100644 index 0000000000..0a0876a264 --- /dev/null +++ b/pype/settings/defaults/launchers/houdini_16.toml @@ -0,0 +1,8 @@ +executable = "houdini_16" +schema = "avalon-core:application-1.0" +application_dir = "houdini" +label = "Houdini" +label_variant = "16" +ftrack_label = "Houdini" +icon = "app_icons/houdini.png" +ftrack_icon = '{}/app_icons/houdini.png' diff --git a/pype/settings/defaults/launchers/houdini_17.toml b/pype/settings/defaults/launchers/houdini_17.toml new file mode 100644 index 0000000000..203f5cdb9b --- /dev/null +++ b/pype/settings/defaults/launchers/houdini_17.toml @@ -0,0 +1,8 @@ +executable = "houdini_17" +schema = "avalon-core:application-1.0" +application_dir = "houdini" +label = "Houdini" +label_variant = "17" +ftrack_label = "Houdini" +icon = "app_icons/houdini.png" +ftrack_icon = '{}/app_icons/houdini.png' diff --git a/pype/settings/defaults/launchers/houdini_18.toml b/pype/settings/defaults/launchers/houdini_18.toml new file mode 100644 index 0000000000..40f530c291 --- /dev/null +++ b/pype/settings/defaults/launchers/houdini_18.toml @@ -0,0 +1,8 @@ +executable = "houdini_18" +schema = "avalon-core:application-1.0" +application_dir = "houdini" +label = "Houdini" +label_variant = "18" +ftrack_label = "Houdini" +icon = "app_icons/houdini.png" +ftrack_icon = '{}/app_icons/houdini.png' diff --git a/pype/settings/defaults/launchers/linux/maya2016 b/pype/settings/defaults/launchers/linux/maya2016 new file mode 100644 index 0000000000..98424304b1 --- /dev/null +++ b/pype/settings/defaults/launchers/linux/maya2016 @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +maya_path = "/usr/autodesk/maya2016/bin/maya" + +if [[ -z $PYPE_LOG_NO_COLORS ]]; then + $maya_path -file "$AVALON_LAST_WORKFILE" $@ +else + $maya_path $@ diff --git a/pype/settings/defaults/launchers/linux/maya2017 b/pype/settings/defaults/launchers/linux/maya2017 new file mode 100644 index 0000000000..7a2662a55e --- /dev/null +++ b/pype/settings/defaults/launchers/linux/maya2017 @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +maya_path = "/usr/autodesk/maya2017/bin/maya" + +if [[ -z $AVALON_LAST_WORKFILE ]]; then + $maya_path -file "$AVALON_LAST_WORKFILE" $@ +else + $maya_path $@ diff --git a/pype/settings/defaults/launchers/linux/maya2018 b/pype/settings/defaults/launchers/linux/maya2018 new file mode 100644 index 0000000000..db832b3fe7 --- /dev/null +++ b/pype/settings/defaults/launchers/linux/maya2018 @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +maya_path = "/usr/autodesk/maya2018/bin/maya" + +if [[ -z $AVALON_LAST_WORKFILE ]]; then + $maya_path -file "$AVALON_LAST_WORKFILE" $@ +else + $maya_path $@ diff --git a/pype/settings/defaults/launchers/linux/maya2019 b/pype/settings/defaults/launchers/linux/maya2019 new file mode 100644 index 0000000000..8398734ab9 --- /dev/null +++ b/pype/settings/defaults/launchers/linux/maya2019 @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +maya_path = "/usr/autodesk/maya2019/bin/maya" + +if [[ -z $AVALON_LAST_WORKFILE ]]; then + $maya_path -file "$AVALON_LAST_WORKFILE" $@ +else + $maya_path $@ diff --git a/pype/settings/defaults/launchers/linux/maya2020 b/pype/settings/defaults/launchers/linux/maya2020 new file mode 100644 index 0000000000..18a1edd598 --- /dev/null +++ b/pype/settings/defaults/launchers/linux/maya2020 @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +maya_path = "/usr/autodesk/maya2020/bin/maya" + +if [[ -z $AVALON_LAST_WORKFILE ]]; then + $maya_path -file "$AVALON_LAST_WORKFILE" $@ +else + $maya_path $@ diff --git a/pype/settings/defaults/launchers/linux/nuke11.3 b/pype/settings/defaults/launchers/linux/nuke11.3 new file mode 100644 index 0000000000..b1c9a90d74 --- /dev/null +++ b/pype/settings/defaults/launchers/linux/nuke11.3 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gnome-terminal -e '/usr/local/Nuke11.3v5/Nuke11.3' diff --git a/pype/settings/defaults/launchers/linux/nuke12.0 b/pype/settings/defaults/launchers/linux/nuke12.0 new file mode 100644 index 0000000000..99ea1a6b0c --- /dev/null +++ b/pype/settings/defaults/launchers/linux/nuke12.0 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gnome-terminal -e '/usr/local/Nuke12.0v1/Nuke12.0' diff --git a/pype/settings/defaults/launchers/linux/nukestudio11.3 b/pype/settings/defaults/launchers/linux/nukestudio11.3 new file mode 100644 index 0000000000..750d54a7d5 --- /dev/null +++ b/pype/settings/defaults/launchers/linux/nukestudio11.3 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gnome-terminal -e '/usr/local/Nuke11.3v5/Nuke11.3 --studio' diff --git a/pype/settings/defaults/launchers/linux/nukestudio12.0 b/pype/settings/defaults/launchers/linux/nukestudio12.0 new file mode 100644 index 0000000000..ba5cf654a8 --- /dev/null +++ b/pype/settings/defaults/launchers/linux/nukestudio12.0 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gnome-terminal -e '/usr/local/Nuke12.0v1/Nuke12.0 --studio' diff --git a/pype/settings/defaults/launchers/linux/nukex11.3 b/pype/settings/defaults/launchers/linux/nukex11.3 new file mode 100644 index 0000000000..d913e4b961 --- /dev/null +++ b/pype/settings/defaults/launchers/linux/nukex11.3 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gnome-terminal -e '/usr/local/Nuke11.3v5/Nuke11.3 -nukex' diff --git a/pype/settings/defaults/launchers/linux/nukex12.0 b/pype/settings/defaults/launchers/linux/nukex12.0 new file mode 100644 index 0000000000..da2721c48b --- /dev/null +++ b/pype/settings/defaults/launchers/linux/nukex12.0 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gnome-terminal -e '/usr/local/Nuke12.0v1/Nuke12.0 -nukex' diff --git a/pype/settings/defaults/launchers/maya_2016.toml b/pype/settings/defaults/launchers/maya_2016.toml new file mode 100644 index 0000000000..24a463d9c6 --- /dev/null +++ b/pype/settings/defaults/launchers/maya_2016.toml @@ -0,0 +1,27 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya" +label_variant = "2016" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2016" +description = "" +icon = "app_icons/maya.png" +ftrack_icon = '{}/app_icons/maya.png' + +[copy] +"{PYPE_MODULE_ROOT}/pype/resources/maya/workspace.mel" = "workspace.mel" + +[environment] +MAYA_DISABLE_CLIC_IPM = "Yes" # Disable the AdSSO process +MAYA_DISABLE_CIP = "Yes" # Shorten time to boot +MAYA_DISABLE_CER = "Yes" +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/settings/defaults/launchers/maya_2017.toml b/pype/settings/defaults/launchers/maya_2017.toml new file mode 100644 index 0000000000..5295862e87 --- /dev/null +++ b/pype/settings/defaults/launchers/maya_2017.toml @@ -0,0 +1,29 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya" +label_variant = "2017" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2017" +description = "" +icon = "app_icons/maya.png" +ftrack_icon = '{}/app_icons/maya.png' + +[copy] +"{PYPE_MODULE_ROOT}/pype/resources/maya/workspace.mel" = "workspace.mel" + +[environment] +MAYA_DISABLE_CLIC_IPM = "Yes" # Disable the AdSSO process +MAYA_DISABLE_CIP = "Yes" # Shorten time to boot +MAYA_DISABLE_CER = "Yes" +PYMEL_SKIP_MEL_INIT = "Yes" +LC_ALL= "C" # Mute color management warnings +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/settings/defaults/launchers/maya_2018.toml b/pype/settings/defaults/launchers/maya_2018.toml new file mode 100644 index 0000000000..2bdff2094d --- /dev/null +++ b/pype/settings/defaults/launchers/maya_2018.toml @@ -0,0 +1,15 @@ +application_dir = "maya" +default_dirs = [ + "renders" +] +label = "Autodesk Maya" +label_variant = "2018" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2018" +description = "" +icon = "app_icons/maya.png" +ftrack_icon = '{}/app_icons/maya.png' + +[copy] +"{PYPE_MODULE_ROOT}/pype/resources/maya/workspace.mel" = "workspace.mel" diff --git a/pype/settings/defaults/launchers/maya_2019.toml b/pype/settings/defaults/launchers/maya_2019.toml new file mode 100644 index 0000000000..8eb88179f9 --- /dev/null +++ b/pype/settings/defaults/launchers/maya_2019.toml @@ -0,0 +1,15 @@ +application_dir = "maya" +default_dirs = [ + "renders" +] +label = "Autodesk Maya" +label_variant = "2019" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2019" +description = "" +icon = "app_icons/maya.png" +ftrack_icon = '{}/app_icons/maya.png' + +[copy] +"{PYPE_MODULE_ROOT}/pype/resources/maya/workspace.mel" = "workspace.mel" diff --git a/pype/settings/defaults/launchers/maya_2020.toml b/pype/settings/defaults/launchers/maya_2020.toml new file mode 100644 index 0000000000..693de0cf9e --- /dev/null +++ b/pype/settings/defaults/launchers/maya_2020.toml @@ -0,0 +1,15 @@ +application_dir = "maya" +default_dirs = [ + "renders" +] +label = "Autodesk Maya" +label_variant = "2020" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2020" +description = "" +icon = "app_icons/maya.png" +ftrack_icon = '{}/app_icons/maya.png' + +[copy] +"{PYPE_MODULE_ROOT}/pype/resources/maya/workspace.mel" = "workspace.mel" diff --git a/pype/settings/defaults/launchers/mayabatch_2019.toml b/pype/settings/defaults/launchers/mayabatch_2019.toml new file mode 100644 index 0000000000..a928618d2b --- /dev/null +++ b/pype/settings/defaults/launchers/mayabatch_2019.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2019x64" +schema = "avalon-core:application-1.0" +executable = "mayabatch2019" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/settings/defaults/launchers/mayabatch_2020.toml b/pype/settings/defaults/launchers/mayabatch_2020.toml new file mode 100644 index 0000000000..cd1e1e4474 --- /dev/null +++ b/pype/settings/defaults/launchers/mayabatch_2020.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2020x64" +schema = "avalon-core:application-1.0" +executable = "mayabatch2020" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/settings/defaults/launchers/mayapy2016.toml b/pype/settings/defaults/launchers/mayapy2016.toml new file mode 100644 index 0000000000..ad1e3dee86 --- /dev/null +++ b/pype/settings/defaults/launchers/mayapy2016.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2016x64" +schema = "avalon-core:application-1.0" +executable = "mayapy2016" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/settings/defaults/launchers/mayapy2017.toml b/pype/settings/defaults/launchers/mayapy2017.toml new file mode 100644 index 0000000000..8d2095ff47 --- /dev/null +++ b/pype/settings/defaults/launchers/mayapy2017.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2017x64" +schema = "avalon-core:application-1.0" +executable = "mayapy2017" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/settings/defaults/launchers/mayapy2018.toml b/pype/settings/defaults/launchers/mayapy2018.toml new file mode 100644 index 0000000000..597744fd85 --- /dev/null +++ b/pype/settings/defaults/launchers/mayapy2018.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2018x64" +schema = "avalon-core:application-1.0" +executable = "mayapy2017" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/settings/defaults/launchers/mayapy2019.toml b/pype/settings/defaults/launchers/mayapy2019.toml new file mode 100644 index 0000000000..3c8a9860f9 --- /dev/null +++ b/pype/settings/defaults/launchers/mayapy2019.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2019x64" +schema = "avalon-core:application-1.0" +executable = "mayapy2019" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/settings/defaults/launchers/mayapy2020.toml b/pype/settings/defaults/launchers/mayapy2020.toml new file mode 100644 index 0000000000..8f2d2e4a67 --- /dev/null +++ b/pype/settings/defaults/launchers/mayapy2020.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2020x64" +schema = "avalon-core:application-1.0" +executable = "mayapy2020" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/settings/defaults/launchers/myapp.toml b/pype/settings/defaults/launchers/myapp.toml new file mode 100644 index 0000000000..21da0d52b2 --- /dev/null +++ b/pype/settings/defaults/launchers/myapp.toml @@ -0,0 +1,5 @@ +executable = "python" +schema = "avalon-core:application-1.0" +application_dir = "myapp" +label = "My App" +arguments = [ "-c", "import sys; from Qt import QtWidgets; if __name__ == '__main__':;\n app = QtWidgets.QApplication(sys.argv);\n window = QtWidgets.QWidget();\n window.setWindowTitle(\"My App\");\n window.resize(400, 300);\n window.show();\n app.exec_();\n",] \ No newline at end of file diff --git a/pype/settings/defaults/launchers/nuke_10.0.toml b/pype/settings/defaults/launchers/nuke_10.0.toml new file mode 100644 index 0000000000..d4dd028942 --- /dev/null +++ b/pype/settings/defaults/launchers/nuke_10.0.toml @@ -0,0 +1,8 @@ +executable = "nuke10.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke" +label_variant = "10.0v4" +ftrack_label = "Nuke" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nuke_11.0.toml b/pype/settings/defaults/launchers/nuke_11.0.toml new file mode 100644 index 0000000000..10ff6aca37 --- /dev/null +++ b/pype/settings/defaults/launchers/nuke_11.0.toml @@ -0,0 +1,8 @@ +executable = "nuke11.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke" +label_variant = "11.0" +ftrack_label = "Nuke" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nuke_11.2.toml b/pype/settings/defaults/launchers/nuke_11.2.toml new file mode 100644 index 0000000000..530c7f610e --- /dev/null +++ b/pype/settings/defaults/launchers/nuke_11.2.toml @@ -0,0 +1,8 @@ +executable = "nuke11.2" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke" +label_variant = "11.2" +ftrack_label = "Nuke" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nuke_11.3.toml b/pype/settings/defaults/launchers/nuke_11.3.toml new file mode 100644 index 0000000000..c9ff005feb --- /dev/null +++ b/pype/settings/defaults/launchers/nuke_11.3.toml @@ -0,0 +1,8 @@ +executable = "nuke11.3" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke" +label_variant = "11.3" +ftrack_label = "Nuke" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nuke_12.0.toml b/pype/settings/defaults/launchers/nuke_12.0.toml new file mode 100644 index 0000000000..9ac1084fbf --- /dev/null +++ b/pype/settings/defaults/launchers/nuke_12.0.toml @@ -0,0 +1,8 @@ +executable = "nuke12.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke" +label_variant = "12.0" +ftrack_label = "Nuke" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nukestudio_10.0.toml b/pype/settings/defaults/launchers/nukestudio_10.0.toml new file mode 100644 index 0000000000..6c554aff62 --- /dev/null +++ b/pype/settings/defaults/launchers/nukestudio_10.0.toml @@ -0,0 +1,8 @@ +executable = "nukestudio10.0" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio" +label_variant = "10.0" +ftrack_label = "NukeStudio" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nukestudio_11.0.toml b/pype/settings/defaults/launchers/nukestudio_11.0.toml new file mode 100644 index 0000000000..482aa6587e --- /dev/null +++ b/pype/settings/defaults/launchers/nukestudio_11.0.toml @@ -0,0 +1,8 @@ +executable = "nukestudio11.0" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio" +label_variant = "11.0" +ftrack_label = "NukeStudio" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nukestudio_11.2.toml b/pype/settings/defaults/launchers/nukestudio_11.2.toml new file mode 100644 index 0000000000..78d1de3d8b --- /dev/null +++ b/pype/settings/defaults/launchers/nukestudio_11.2.toml @@ -0,0 +1,8 @@ +executable = "nukestudio11.2" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio" +label_variant = "11.2" +ftrack_label = "NukeStudio" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nukestudio_11.3.toml b/pype/settings/defaults/launchers/nukestudio_11.3.toml new file mode 100644 index 0000000000..35c6a08b2f --- /dev/null +++ b/pype/settings/defaults/launchers/nukestudio_11.3.toml @@ -0,0 +1,8 @@ +executable = "nukestudio11.3" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio" +label_variant = "11.3" +ftrack_label = "NukeStudio" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nukestudio_12.0.toml b/pype/settings/defaults/launchers/nukestudio_12.0.toml new file mode 100644 index 0000000000..2754116aef --- /dev/null +++ b/pype/settings/defaults/launchers/nukestudio_12.0.toml @@ -0,0 +1,8 @@ +executable = "nukestudio12.0" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio" +label_variant = "12.0" +ftrack_label = "NukeStudio" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nukex_10.0.toml b/pype/settings/defaults/launchers/nukex_10.0.toml new file mode 100644 index 0000000000..48da30fe16 --- /dev/null +++ b/pype/settings/defaults/launchers/nukex_10.0.toml @@ -0,0 +1,8 @@ +executable = "nukex10.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX" +label_variant = "10.0" +ftrack_label = "NukeX" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/settings/defaults/launchers/nukex_11.0.toml b/pype/settings/defaults/launchers/nukex_11.0.toml new file mode 100644 index 0000000000..8f353e9e00 --- /dev/null +++ b/pype/settings/defaults/launchers/nukex_11.0.toml @@ -0,0 +1,8 @@ +executable = "nukex11.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX" +label_variant = "11.0" +ftrack_label = "NukeX" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/settings/defaults/launchers/nukex_11.2.toml b/pype/settings/defaults/launchers/nukex_11.2.toml new file mode 100644 index 0000000000..38e37fa4c9 --- /dev/null +++ b/pype/settings/defaults/launchers/nukex_11.2.toml @@ -0,0 +1,8 @@ +executable = "nukex11.2" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX" +label_variant = "11.2" +ftrack_label = "NukeX" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/settings/defaults/launchers/nukex_11.3.toml b/pype/settings/defaults/launchers/nukex_11.3.toml new file mode 100644 index 0000000000..42969c5e69 --- /dev/null +++ b/pype/settings/defaults/launchers/nukex_11.3.toml @@ -0,0 +1,8 @@ +executable = "nukex11.3" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX" +label_variant = "11.3" +ftrack_label = "NukeX" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/settings/defaults/launchers/nukex_12.0.toml b/pype/settings/defaults/launchers/nukex_12.0.toml new file mode 100644 index 0000000000..19d27a12d7 --- /dev/null +++ b/pype/settings/defaults/launchers/nukex_12.0.toml @@ -0,0 +1,8 @@ +executable = "nukex12.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX" +label_variant = "12.0" +ftrack_label = "NukeX" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/settings/defaults/launchers/photoshop_2020.toml b/pype/settings/defaults/launchers/photoshop_2020.toml new file mode 100644 index 0000000000..8164af929f --- /dev/null +++ b/pype/settings/defaults/launchers/photoshop_2020.toml @@ -0,0 +1,9 @@ +executable = "photoshop_2020" +schema = "avalon-core:application-1.0" +application_dir = "photoshop" +label = "Adobe Photoshop" +label_variant = "2020" +icon = "app_icons/photoshop.png" +ftrack_label = "Photoshop" +ftrack_icon = '{}/app_icons/photoshop.png' +launch_hook = "pype/hooks/photoshop/prelaunch.py/PhotoshopPrelaunch" diff --git a/pype/settings/defaults/launchers/premiere_2019.toml b/pype/settings/defaults/launchers/premiere_2019.toml new file mode 100644 index 0000000000..d03395e022 --- /dev/null +++ b/pype/settings/defaults/launchers/premiere_2019.toml @@ -0,0 +1,9 @@ +executable = "premiere_pro_2019" +schema = "avalon-core:application-1.0" +application_dir = "premiere" +label = "Adobe Premiere Pro CC" +label_variant = "2019" +icon = "app_icons/premiere.png" + +ftrack_label = "Premiere" +ftrack_icon = '{}/app_icons/premiere.png' diff --git a/pype/settings/defaults/launchers/premiere_2020.toml b/pype/settings/defaults/launchers/premiere_2020.toml new file mode 100644 index 0000000000..01c7b5b745 --- /dev/null +++ b/pype/settings/defaults/launchers/premiere_2020.toml @@ -0,0 +1,10 @@ +executable = "premiere_pro_2020" +schema = "avalon-core:application-1.0" +application_dir = "premiere" +label = "Adobe Premiere Pro CC" +label_variant = "2020" +launch_hook = "pype/hooks/premiere/prelaunch.py/PremierePrelaunch" +icon = "app_icons/premiere.png" + +ftrack_label = "Premiere" +ftrack_icon = '{}/app_icons/premiere.png' diff --git a/pype/settings/defaults/launchers/python_2.toml b/pype/settings/defaults/launchers/python_2.toml new file mode 100644 index 0000000000..f1c1ca7e68 --- /dev/null +++ b/pype/settings/defaults/launchers/python_2.toml @@ -0,0 +1,12 @@ +schema = "avalon-core:application-1.0" +application_dir = "python" +executable = "python" +label = "Python" +label_variant = "2" +icon = "app_icons/python.png" + +ftrack_label = "Python" +ftrack_icon = '{}/app_icons/python.png' + +[environment] +CREATE_NEW_CONSOLE = "Yes" diff --git a/pype/settings/defaults/launchers/python_3.toml b/pype/settings/defaults/launchers/python_3.toml new file mode 100644 index 0000000000..90fb10eaeb --- /dev/null +++ b/pype/settings/defaults/launchers/python_3.toml @@ -0,0 +1,12 @@ +schema = "avalon-core:application-1.0" +application_dir = "python" +executable = "python3" +label = "Python" +label_variant = "3" +icon = "app_icons/python.png" + +ftrack_label = "Python" +ftrack_icon = '{}/app_icons/python.png' + +[environment] +CREATE_NEW_CONSOLE = "Yes" diff --git a/pype/settings/defaults/launchers/resolve_16.toml b/pype/settings/defaults/launchers/resolve_16.toml new file mode 100644 index 0000000000..47918a22a6 --- /dev/null +++ b/pype/settings/defaults/launchers/resolve_16.toml @@ -0,0 +1,10 @@ +executable = "resolve_16" +schema = "avalon-core:application-1.0" +application_dir = "resolve" +label = "BM DaVinci Resolve" +label_variant = "16" +launch_hook = "pype/hooks/resolve/prelaunch.py/ResolvePrelaunch" +icon = "app_icons/resolve.png" + +ftrack_label = "BM DaVinci Resolve" +ftrack_icon = '{}/app_icons/resolve.png' diff --git a/pype/settings/defaults/launchers/shell.toml b/pype/settings/defaults/launchers/shell.toml new file mode 100644 index 0000000000..959ad392ea --- /dev/null +++ b/pype/settings/defaults/launchers/shell.toml @@ -0,0 +1,7 @@ +schema = "avalon-core:application-1.0" +application_dir = "shell" +executable = "shell" +label = "Shell" + +[environment] +CREATE_NEW_CONSOLE = "Yes" \ No newline at end of file diff --git a/pype/settings/defaults/launchers/storyboardpro_7.toml b/pype/settings/defaults/launchers/storyboardpro_7.toml new file mode 100644 index 0000000000..067f10a23a --- /dev/null +++ b/pype/settings/defaults/launchers/storyboardpro_7.toml @@ -0,0 +1,9 @@ +application_dir = "storyboardpro" +label = "Storyboard Pro" +label_variant = "7" +ftrack_label = "Storyboard Pro" +schema = "avalon-core:application-1.0" +executable = "storyboardpro_7" +description = "" +icon = "app_icons/storyboardpro.png" +ftrack_icon = '{}/app_icons/storyboardpro.png' diff --git a/pype/settings/defaults/launchers/unreal_4.24.toml b/pype/settings/defaults/launchers/unreal_4.24.toml new file mode 100644 index 0000000000..10b14e7f59 --- /dev/null +++ b/pype/settings/defaults/launchers/unreal_4.24.toml @@ -0,0 +1,10 @@ +executable = "unreal" +schema = "avalon-core:application-1.0" +application_dir = "unreal" +label = "Unreal Editor" +label_variant = "4.24" +icon = "app_icons/ue4.png" +launch_hook = "pype/hooks/unreal/unreal_prelaunch.py/UnrealPrelaunch" + +ftrack_label = "UnrealEditor" +ftrack_icon = '{}/app_icons/ue4.png' diff --git a/pype/settings/defaults/launchers/windows/blender_2.80.bat b/pype/settings/defaults/launchers/windows/blender_2.80.bat new file mode 100644 index 0000000000..5b8a37356b --- /dev/null +++ b/pype/settings/defaults/launchers/windows/blender_2.80.bat @@ -0,0 +1,11 @@ +set __app__="Blender" +set __exe__="C:\Program Files\Blender Foundation\Blender 2.80\blender.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/blender_2.81.bat b/pype/settings/defaults/launchers/windows/blender_2.81.bat new file mode 100644 index 0000000000..a900b18eda --- /dev/null +++ b/pype/settings/defaults/launchers/windows/blender_2.81.bat @@ -0,0 +1,11 @@ +set __app__="Blender" +set __exe__="C:\Program Files\Blender Foundation\Blender 2.81\blender.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/blender_2.82.bat b/pype/settings/defaults/launchers/windows/blender_2.82.bat new file mode 100644 index 0000000000..7105c1efe1 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/blender_2.82.bat @@ -0,0 +1,11 @@ +set __app__="Blender" +set __exe__="C:\Program Files\Blender Foundation\Blender 2.82\blender.exe" --python-use-system-env +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/blender_2.83.bat b/pype/settings/defaults/launchers/windows/blender_2.83.bat new file mode 100644 index 0000000000..671952f0d7 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/blender_2.83.bat @@ -0,0 +1,11 @@ +set __app__="Blender" +set __exe__="C:\Program Files\Blender Foundation\Blender 2.83\blender.exe" --python-use-system-env +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/celaction_local.bat b/pype/settings/defaults/launchers/windows/celaction_local.bat new file mode 100644 index 0000000000..8f2171617e --- /dev/null +++ b/pype/settings/defaults/launchers/windows/celaction_local.bat @@ -0,0 +1,19 @@ +set __app__="CelAction2D" +set __app_dir__="C:\Program Files (x86)\CelAction\" +set __exe__="C:\Program Files (x86)\CelAction\CelAction2D.exe" + +if not exist %__exe__% goto :missing_app + +pushd %__app_dir__% + +if "%PYPE_CELACTION_PROJECT_FILE%"=="" ( + start %__app__% %__exe__% %* +) else ( + start %__app__% %__exe__% "%PYPE_CELACTION_PROJECT_FILE%" %* +) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/celaction_publish.bat b/pype/settings/defaults/launchers/windows/celaction_publish.bat new file mode 100644 index 0000000000..77ec2ac24e --- /dev/null +++ b/pype/settings/defaults/launchers/windows/celaction_publish.bat @@ -0,0 +1,3 @@ +echo %* + +%PYPE_PYTHON_EXE% "%PYPE_MODULE_ROOT%\pype\hosts\celaction\cli.py" %* diff --git a/pype/settings/defaults/launchers/windows/harmony_17.bat b/pype/settings/defaults/launchers/windows/harmony_17.bat new file mode 100644 index 0000000000..0822650875 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/harmony_17.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Harmony 17" +set __exe__="C:/Program Files (x86)/Toon Boom Animation/Toon Boom Harmony 17 Premium/win64/bin/HarmonyPremium.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% cmd.exe /k "python -c ^"import avalon.harmony;avalon.harmony.launch("%__exe__%")^"" + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/houdini_16.bat b/pype/settings/defaults/launchers/windows/houdini_16.bat new file mode 100644 index 0000000000..018ba08b4c --- /dev/null +++ b/pype/settings/defaults/launchers/windows/houdini_16.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Houdini 16.0" +set __exe__="C:\Program Files\Side Effects Software\Houdini 16.0.621\bin\houdini.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/houdini_17.bat b/pype/settings/defaults/launchers/windows/houdini_17.bat new file mode 100644 index 0000000000..950a599623 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/houdini_17.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Houdini 17.0" +set __exe__="C:\Program Files\Side Effects Software\Houdini 17.0.459\bin\houdini.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/houdini_18.bat b/pype/settings/defaults/launchers/windows/houdini_18.bat new file mode 100644 index 0000000000..3d6b1ae258 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/houdini_18.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Houdini 18.0" +set __exe__="C:\Program Files\Side Effects Software\Houdini 18.0.287\bin\houdini.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/maya2016.bat b/pype/settings/defaults/launchers/windows/maya2016.bat new file mode 100644 index 0000000000..54f15cf269 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/maya2016.bat @@ -0,0 +1,17 @@ +@echo off + +set __app__="Maya 2016" +set __exe__="C:\Program Files\Autodesk\Maya2016\bin\maya.exe" +if not exist %__exe__% goto :missing_app + +if "%AVALON_LAST_WORKFILE%"=="" ( + start %__app__% %__exe__% %* +) else ( + start %__app__% %__exe__% -file "%AVALON_LAST_WORKFILE%" %* +) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/maya2017.bat b/pype/settings/defaults/launchers/windows/maya2017.bat new file mode 100644 index 0000000000..5c2aeb495c --- /dev/null +++ b/pype/settings/defaults/launchers/windows/maya2017.bat @@ -0,0 +1,17 @@ +@echo off + +set __app__="Maya 2017" +set __exe__="C:\Program Files\Autodesk\Maya2017\bin\maya.exe" +if not exist %__exe__% goto :missing_app + +if "%AVALON_LAST_WORKFILE%"=="" ( + start %__app__% %__exe__% %* +) else ( + start %__app__% %__exe__% -file "%AVALON_LAST_WORKFILE%" %* +) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/maya2018.bat b/pype/settings/defaults/launchers/windows/maya2018.bat new file mode 100644 index 0000000000..28cf776c77 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/maya2018.bat @@ -0,0 +1,17 @@ +@echo off + +set __app__="Maya 2018" +set __exe__="C:\Program Files\Autodesk\Maya2018\bin\maya.exe" +if not exist %__exe__% goto :missing_app + +if "%AVALON_LAST_WORKFILE%"=="" ( + start %__app__% %__exe__% %* +) else ( + start %__app__% %__exe__% -file "%AVALON_LAST_WORKFILE%" %* +) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/maya2019.bat b/pype/settings/defaults/launchers/windows/maya2019.bat new file mode 100644 index 0000000000..7e80dd2557 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/maya2019.bat @@ -0,0 +1,17 @@ +@echo off + +set __app__="Maya 2019" +set __exe__="C:\Program Files\Autodesk\Maya2019\bin\maya.exe" +if not exist %__exe__% goto :missing_app + +if "%AVALON_LAST_WORKFILE%"=="" ( + start %__app__% %__exe__% %* +) else ( + start %__app__% %__exe__% -file "%AVALON_LAST_WORKFILE%" %* +) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/maya2020.bat b/pype/settings/defaults/launchers/windows/maya2020.bat new file mode 100644 index 0000000000..b2acb5df5a --- /dev/null +++ b/pype/settings/defaults/launchers/windows/maya2020.bat @@ -0,0 +1,17 @@ +@echo off + +set __app__="Maya 2020" +set __exe__="C:\Program Files\Autodesk\maya2020\bin\maya.exe" +if not exist %__exe__% goto :missing_app + +if "%AVALON_LAST_WORKFILE%"=="" ( + start %__app__% %__exe__% %* +) else ( + start %__app__% %__exe__% -file "%AVALON_LAST_WORKFILE%" %* +) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/mayabatch2019.bat b/pype/settings/defaults/launchers/windows/mayabatch2019.bat new file mode 100644 index 0000000000..ddd9b9b956 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/mayabatch2019.bat @@ -0,0 +1,14 @@ +@echo off + +set __app__="Maya Batch 2019" +set __exe__="C:\Program Files\Autodesk\Maya2019\bin\mayabatch.exe" +if not exist %__exe__% goto :missing_app + +echo "running maya : %*" +%__exe__% %* +echo "done." +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/mayabatch2020.bat b/pype/settings/defaults/launchers/windows/mayabatch2020.bat new file mode 100644 index 0000000000..b1cbc6dbb6 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/mayabatch2020.bat @@ -0,0 +1,14 @@ +@echo off + +set __app__="Maya Batch 2020" +set __exe__="C:\Program Files\Autodesk\Maya2020\bin\mayabatch.exe" +if not exist %__exe__% goto :missing_app + +echo "running maya : %*" +%__exe__% %* +echo "done." +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/mayapy2016.bat b/pype/settings/defaults/launchers/windows/mayapy2016.bat new file mode 100644 index 0000000000..205991fd3d --- /dev/null +++ b/pype/settings/defaults/launchers/windows/mayapy2016.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Mayapy 2016" +set __exe__="C:\Program Files\Autodesk\Maya2016\bin\mayapy.exe" +if not exist %__exe__% goto :missing_app + +call %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found at %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/mayapy2017.bat b/pype/settings/defaults/launchers/windows/mayapy2017.bat new file mode 100644 index 0000000000..14aacc5a7f --- /dev/null +++ b/pype/settings/defaults/launchers/windows/mayapy2017.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Mayapy 2017" +set __exe__="C:\Program Files\Autodesk\Maya2017\bin\mayapy.exe" +if not exist %__exe__% goto :missing_app + +call %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found at %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/mayapy2018.bat b/pype/settings/defaults/launchers/windows/mayapy2018.bat new file mode 100644 index 0000000000..c47c472f46 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/mayapy2018.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Mayapy 2018" +set __exe__="C:\Program Files\Autodesk\Maya2018\bin\mayapy.exe" +if not exist %__exe__% goto :missing_app + +call %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found at %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/mayapy2019.bat b/pype/settings/defaults/launchers/windows/mayapy2019.bat new file mode 100644 index 0000000000..73ca5b2d40 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/mayapy2019.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Mayapy 2019" +set __exe__="C:\Program Files\Autodesk\Maya2019\bin\mayapy.exe" +if not exist %__exe__% goto :missing_app + +call %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found at %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/mayapy2020.bat b/pype/settings/defaults/launchers/windows/mayapy2020.bat new file mode 100644 index 0000000000..770a03dcf5 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/mayapy2020.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Mayapy 2020" +set __exe__="C:\Program Files\Autodesk\Maya2020\bin\mayapy.exe" +if not exist %__exe__% goto :missing_app + +call %__exe__% %* + +goto :eofS + +:missing_app + echo ERROR: %__app__% not found at %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/nuke10.0.bat b/pype/settings/defaults/launchers/windows/nuke10.0.bat new file mode 100644 index 0000000000..a47cbdfb20 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/nuke10.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Nuke10.0v4" +set __exe__="C:\Program Files\Nuke10.0v4\Nuke10.0.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/nuke11.0.bat b/pype/settings/defaults/launchers/windows/nuke11.0.bat new file mode 100644 index 0000000000..a374c5cf5b --- /dev/null +++ b/pype/settings/defaults/launchers/windows/nuke11.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Nuke11.0v4" +set __exe__="C:\Program Files\Nuke11.0v4\Nuke11.0.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/nuke11.2.bat b/pype/settings/defaults/launchers/windows/nuke11.2.bat new file mode 100644 index 0000000000..4c777ac28c --- /dev/null +++ b/pype/settings/defaults/launchers/windows/nuke11.2.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Nuke11.2v3" +set __exe__="C:\Program Files\Nuke11.2v3\Nuke11.2.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/nuke11.3.bat b/pype/settings/defaults/launchers/windows/nuke11.3.bat new file mode 100644 index 0000000000..a023f5f46f --- /dev/null +++ b/pype/settings/defaults/launchers/windows/nuke11.3.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Nuke11.3v1" +set __exe__="C:\Program Files\Nuke11.3v1\Nuke11.3.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/nuke12.0.bat b/pype/settings/defaults/launchers/windows/nuke12.0.bat new file mode 100644 index 0000000000..d8fb5772bb --- /dev/null +++ b/pype/settings/defaults/launchers/windows/nuke12.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Nuke12.0v1" +set __exe__="C:\Program Files\Nuke12.0v1\Nuke12.0.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/nukestudio10.0.bat b/pype/settings/defaults/launchers/windows/nukestudio10.0.bat new file mode 100644 index 0000000000..82f833667c --- /dev/null +++ b/pype/settings/defaults/launchers/windows/nukestudio10.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeStudio10.0v4" +set __exe__="C:\Program Files\Nuke10.0v4\Nuke10.0.exe" --studio +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/nukestudio11.0.bat b/pype/settings/defaults/launchers/windows/nukestudio11.0.bat new file mode 100644 index 0000000000..b66797727e --- /dev/null +++ b/pype/settings/defaults/launchers/windows/nukestudio11.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeStudio11.0v4" +set __exe__="C:\Program Files\Nuke11.0v4\Nuke11.0.exe" -studio +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/nukestudio11.2.bat b/pype/settings/defaults/launchers/windows/nukestudio11.2.bat new file mode 100644 index 0000000000..a653d816b4 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/nukestudio11.2.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeStudio11.2v3" +set __exe__="C:\Program Files\Nuke11.2v3\Nuke11.2.exe" -studio +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/nukestudio11.3.bat b/pype/settings/defaults/launchers/windows/nukestudio11.3.bat new file mode 100644 index 0000000000..62c8718873 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/nukestudio11.3.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeStudio11.3v1" +set __exe__="C:\Program Files\Nuke11.3v1\Nuke11.3.exe" --studio +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/nukestudio12.0.bat b/pype/settings/defaults/launchers/windows/nukestudio12.0.bat new file mode 100644 index 0000000000..488232bcbf --- /dev/null +++ b/pype/settings/defaults/launchers/windows/nukestudio12.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeStudio12.0v1" +set __exe__="C:\Program Files\Nuke12.0v1\Nuke12.0.exe" --studio +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/nukex10.0.bat b/pype/settings/defaults/launchers/windows/nukex10.0.bat new file mode 100644 index 0000000000..1759706a7b --- /dev/null +++ b/pype/settings/defaults/launchers/windows/nukex10.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeX10.0v4" +set __exe__="C:\Program Files\Nuke10.0v4\Nuke10.0.exe" -nukex +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/nukex11.0.bat b/pype/settings/defaults/launchers/windows/nukex11.0.bat new file mode 100644 index 0000000000..b554a7b6fa --- /dev/null +++ b/pype/settings/defaults/launchers/windows/nukex11.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeX11.0v4" +set __exe__="C:\Program Files\Nuke11.0v4\Nuke11.0.exe" --nukex +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/nukex11.2.bat b/pype/settings/defaults/launchers/windows/nukex11.2.bat new file mode 100644 index 0000000000..a4cb5dec5c --- /dev/null +++ b/pype/settings/defaults/launchers/windows/nukex11.2.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeX11.2v3" +set __exe__="C:\Program Files\Nuke11.2v3\Nuke11.2.exe" --nukex +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/nukex11.3.bat b/pype/settings/defaults/launchers/windows/nukex11.3.bat new file mode 100644 index 0000000000..490b55cf4c --- /dev/null +++ b/pype/settings/defaults/launchers/windows/nukex11.3.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeX11.3v1" +set __exe__="C:\Program Files\Nuke11.3v1\Nuke11.3.exe" --nukex +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/nukex12.0.bat b/pype/settings/defaults/launchers/windows/nukex12.0.bat new file mode 100644 index 0000000000..26adf0d3f1 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/nukex12.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeX12.0v1" +set __exe__="C:\Program Files\Nuke12.0v1\Nuke12.0.exe" --nukex +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/photoshop_2020.bat b/pype/settings/defaults/launchers/windows/photoshop_2020.bat new file mode 100644 index 0000000000..6b90922ef6 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/photoshop_2020.bat @@ -0,0 +1,15 @@ +@echo off + +set __app__="Photoshop 2020" +set __exe__="C:\Program Files\Adobe\Adobe Photoshop 2020\Photoshop.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% cmd.exe /k "%PYPE_PYTHON_EXE% -c ^"import avalon.photoshop;avalon.photoshop.launch("%__exe__%")^"" + +goto :eof + +pause + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/premiere_pro_2019.bat b/pype/settings/defaults/launchers/windows/premiere_pro_2019.bat new file mode 100644 index 0000000000..4886737d2f --- /dev/null +++ b/pype/settings/defaults/launchers/windows/premiere_pro_2019.bat @@ -0,0 +1,14 @@ +@echo off + +set __app__="Adobe Premiere Pro" +set __exe__="C:\Program Files\Adobe\Adobe Premiere Pro CC 2019\Adobe Premiere Pro.exe" +if not exist %__exe__% goto :missing_app + +python -u %PREMIERA_PATH%\init.py +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/premiere_pro_2020.bat b/pype/settings/defaults/launchers/windows/premiere_pro_2020.bat new file mode 100644 index 0000000000..14662d3be3 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/premiere_pro_2020.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Adobe Premiere Pro" +set __exe__="C:\Program Files\Adobe\Adobe Premiere Pro 2020\Adobe Premiere Pro.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/python3.bat b/pype/settings/defaults/launchers/windows/python3.bat new file mode 100644 index 0000000000..c7c116fe72 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/python3.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Python36" +set __exe__="C:\Python36\python.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/resolve_16.bat b/pype/settings/defaults/launchers/windows/resolve_16.bat new file mode 100644 index 0000000000..1a5d964e6b --- /dev/null +++ b/pype/settings/defaults/launchers/windows/resolve_16.bat @@ -0,0 +1,17 @@ +@echo off + +set __app__="Resolve" +set __appy__="Resolve Python Console" +set __exe__="C:/Program Files/Blackmagic Design/DaVinci Resolve/Resolve.exe" +set __py__="%PYTHON36_RESOLVE%/python.exe" + +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* +IF "%RESOLVE_DEV%"=="True" (start %__appy__% %__py__% -i %PRE_PYTHON_SCRIPT%) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/shell.bat b/pype/settings/defaults/launchers/windows/shell.bat new file mode 100644 index 0000000000..eb0895364f --- /dev/null +++ b/pype/settings/defaults/launchers/windows/shell.bat @@ -0,0 +1,2 @@ +@echo off +start cmd diff --git a/pype/settings/defaults/launchers/windows/storyboardpro_7.bat b/pype/settings/defaults/launchers/windows/storyboardpro_7.bat new file mode 100644 index 0000000000..122edac572 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/storyboardpro_7.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Storyboard Pro 7" +set __exe__="C:/Program Files (x86)/Toon Boom Animation/Toon Boom Storyboard Pro 7/win64/bin/StoryboardPro.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% cmd.exe /k "python -c ^"import avalon.storyboardpro;avalon.storyboardpro.launch("%__exe__%")^"" + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/launchers/windows/unreal.bat b/pype/settings/defaults/launchers/windows/unreal.bat new file mode 100644 index 0000000000..7771aaa5a5 --- /dev/null +++ b/pype/settings/defaults/launchers/windows/unreal.bat @@ -0,0 +1,11 @@ +set __app__="Unreal Editor" +set __exe__="%AVALON_CURRENT_UNREAL_ENGINE%\Engine\Binaries\Win64\UE4Editor.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %PYPE_UNREAL_PROJECT_FILE% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/environments/photoshop.json b/pype/settings/defaults/system_settings/environments/photoshop.json deleted file mode 100644 index 2208a88665..0000000000 --- a/pype/settings/defaults/system_settings/environments/photoshop.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH": "1", - "PYTHONPATH": "{PYTHONPATH}" -} diff --git a/pype/settings/lib.py b/pype/settings/lib.py index 388557ca9b..96c3829388 100644 --- a/pype/settings/lib.py +++ b/pype/settings/lib.py @@ -6,9 +6,11 @@ import copy log = logging.getLogger(__name__) # Metadata keys for work with studio and project overrides -OVERRIDEN_KEY = "__overriden_keys__" +M_OVERRIDEN_KEY = "__overriden_keys__" +# Metadata key for storing information about environments +M_ENVIRONMENT_KEY = "__environment_keys__" # NOTE key popping not implemented yet -POP_KEY = "__pop_key__" +M_POP_KEY = "__pop_key__" # Folder where studio overrides are stored STUDIO_OVERRIDES_PATH = os.environ["PYPE_PROJECT_CONFIGS"] @@ -19,6 +21,12 @@ SYSTEM_SETTINGS_PATH = os.path.join( STUDIO_OVERRIDES_PATH, SYSTEM_SETTINGS_KEY + ".json" ) +# File where studio's environment overrides are stored +ENVIRONMENTS_KEY = "environments" +ENVIRONMENTS_PATH = os.path.join( + STUDIO_OVERRIDES_PATH, ENVIRONMENTS_KEY + ".json" +) + # File where studio's default project overrides are stored PROJECT_SETTINGS_KEY = "project_settings" PROJECT_SETTINGS_FILENAME = PROJECT_SETTINGS_KEY + ".json" @@ -105,6 +113,32 @@ def load_json(fpath): return {} +def find_environments(data): + if not data or not isinstance(data, dict): + return + + output = {} + if M_ENVIRONMENT_KEY in data: + metadata = data.pop(M_ENVIRONMENT_KEY) + for env_group_key, env_keys in metadata.items(): + output[env_group_key] = {} + for key in env_keys: + output[env_group_key][key] = data[key] + + for value in data.values(): + result = find_environments(value) + if not result: + continue + + for env_group_key, env_values in result.items(): + if env_group_key not in output: + output[env_group_key] = {} + + for env_key, env_value in env_values.items(): + output[env_group_key][env_key] = env_value + return output + + def subkey_merge(_dict, value, keys): key = keys.pop(0) if not keys: @@ -162,6 +196,12 @@ def studio_system_settings(): return {} +def studio_environments(): + if os.path.exists(ENVIRONMENTS_PATH): + return load_json(ENVIRONMENTS_PATH) + return {} + + def studio_project_settings(): if os.path.exists(PROJECT_SETTINGS_PATH): return load_json(PROJECT_SETTINGS_PATH) @@ -211,13 +251,13 @@ def project_anatomy_overrides(project_name): def merge_overrides(global_dict, override_dict): - if OVERRIDEN_KEY in override_dict: - overriden_keys = set(override_dict.pop(OVERRIDEN_KEY)) + if M_OVERRIDEN_KEY in override_dict: + overriden_keys = set(override_dict.pop(M_OVERRIDEN_KEY)) else: overriden_keys = set() for key, value in override_dict.items(): - if value == POP_KEY: + if value == M_POP_KEY: global_dict.pop(key) elif ( @@ -256,3 +296,11 @@ def project_settings(project_name): project_overrides = project_settings_overrides(project_name) return apply_overrides(studio_overrides, project_overrides) + + +def environments(): + envs = copy.deepcopy(default_settings()[ENVIRONMENTS_KEY]) + envs_from_system_settings = find_environments(system_settings()) + for env_group_key, values in envs_from_system_settings.items(): + envs[env_group_key] = values + return envs diff --git a/pype/tools/settings/settings/README.md b/pype/tools/settings/settings/README.md index e8b7fcdb57..4f4e9d305a 100644 --- a/pype/tools/settings/settings/README.md +++ b/pype/tools/settings/settings/README.md @@ -19,6 +19,7 @@ - GUI schemas are huge json files, to be able to split whole configuration into multiple schema there's type `schema` - system configuration schemas are stored in `~/tools/settings/settings/gui_schemas/system_schema/` and project configurations in `~/tools/settings/settings/gui_schemas/projects_schema/` - each schema name is filename of json file except extension (without ".json") +- if content is dictionary content will be used as `schema` else will be used as `schema_template` ### schema - can have only key `"children"` which is list of strings, each string should represent another schema (order matters) string represebts name of the schema @@ -31,6 +32,87 @@ } ``` +### schema_template +- allows to define schema "templates" to not duplicate same content multiple times +```javascript +// EXAMPLE json file content (filename: example_template.json) +[ + { + "__default_values__": { + "multipath_executables": true + } + }, { + "type": "raw-json", + "label": "{host_label} Environments", + "key": "{host_name}_environments", + "env_group_key": "{host_name}" + }, { + "type": "path-widget", + "key": "{host_name}_executables", + "label": "{host_label} - Full paths to executables", + "multiplatform": "{multipath_executables}", + "multipath": true + } +] +``` +```javascript +// EXAMPLE usage of the template in schema +{ + "type": "dict", + "key": "schema_template_examples", + "label": "Schema template examples", + "children": [ + { + "type": "schema_template", + // filename of template (example_template.json) + "name": "example_template", + "template_data": { + "host_label": "Maya 2019", + "host_name": "maya_2019", + "multipath_executables": false + } + }, { + "type": "schema_template", + "name": "example_template", + "template_data": { + "host_label": "Maya 2020", + "host_name": "maya_2020" + } + } + ] +} +``` +- item in schema mush contain `"type"` and `"name"` keys but it is also expected that `"template_data"` will be entered too +- all items in the list, except `__default_values__`, will replace `schema_template` item in schema +- template may contain another template or schema +- it is expected that schema template will have unfilled fields as in example + - unfilled fields are allowed only in values of schema dictionary +```javascript +{ + ... + // Allowed + "key": "{to_fill}" + ... + // Not allowed + "{to_fill}": "value" + ... +} +``` +- Unfilled fields can be also used for non string values, in that case value must contain only one key and value for fill must contain right type. +```javascript +{ + ... + // Allowed + "multiplatform": "{executable_multiplatform}" + ... + // Not allowed + "multiplatform": "{executable_multiplatform}_enhanced_string" + ... +} +``` +- It is possible to define default values for unfilled fields to do so one of items in list must be dictionary with key `"__default_values__"` and value as dictionary with default key: values (as in example above). + + ## Basic Dictionary inputs - these inputs wraps another inputs into {key: value} relation diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_examples.json b/pype/tools/settings/settings/gui_schemas/system_schema/example_schema.json similarity index 93% rename from pype/tools/settings/settings/gui_schemas/system_schema/1_examples.json rename to pype/tools/settings/settings/gui_schemas/system_schema/example_schema.json index dd0f7f20d1..7612e54116 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/1_examples.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/example_schema.json @@ -5,6 +5,40 @@ "is_file": true, "children": [ { + "type": "dict", + "key": "schema_template_exaples", + "label": "Schema template examples", + "children": [ + { + "type": "schema_template", + "name": "example_template", + "template_data": { + "host_label": "Maya 2019", + "host_name": "maya_2019", + "multipath_executables": false + } + }, { + "type": "schema_template", + "name": "example_template", + "template_data": { + "host_label": "Maya 2020", + "host_name": "maya_2020" + } + } + ] + }, { + "key": "env_group_test", + "label": "EnvGroup Test", + "type": "dict", + "children": [ + { + "key": "key_to_store_in_system_settings", + "label": "Testing environment group", + "type": "raw-json", + "env_group_key": "test_group" + } + ] + }, { "key": "dict_wrapper", "type": "dict-invisible", "children": [ diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/example_template.json b/pype/tools/settings/settings/gui_schemas/system_schema/example_template.json new file mode 100644 index 0000000000..48a3c955b9 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/example_template.json @@ -0,0 +1,18 @@ +[ + { + "__default_values__": { + "multipath_executables": true + } + }, { + "type": "raw-json", + "label": "{host_label} Environments", + "key": "{host_name}_environments", + "env_group_key": "{host_name}" + }, { + "type": "path-widget", + "key": "{host_name}_executables", + "label": "{host_label} - Full paths to executables", + "multiplatform": "{multipath_executables}", + "multipath": true + } +] diff --git a/pype/tools/settings/settings/widgets/base.py b/pype/tools/settings/settings/widgets/base.py index 404d7b700b..7cbe7c2f6f 100644 --- a/pype/tools/settings/settings/widgets/base.py +++ b/pype/tools/settings/settings/widgets/base.py @@ -47,6 +47,7 @@ class SystemWidget(QtWidgets.QWidget): self._ignore_value_changes = False self.input_fields = [] + self.environ_fields = [] scroll_widget = QtWidgets.QScrollArea(self) scroll_widget.setObjectName("GroupWidget") @@ -130,14 +131,18 @@ class SystemWidget(QtWidgets.QWidget): for input_field in self.input_fields: input_field.hierarchical_style_update() + def add_environ_field(self, input_field): + self.environ_fields.append(input_field) + def reset(self): reset_default_settings() - if self.content_layout.count() != 0: - for widget in self.input_fields: - self.content_layout.removeWidget(widget) - widget.deleteLater() - self.input_fields.clear() + self.input_fields.clear() + self.environ_fields.clear() + while self.content_layout.count() != 0: + widget = self.content_layout.itemAt(0).widget() + self.content_layout.removeWidget(widget) + widget.deleteLater() self.schema = lib.gui_schema("system_schema", "0_system_gui_schema") self.keys = self.schema.get("keys", []) @@ -214,7 +219,7 @@ class SystemWidget(QtWidgets.QWidget): all_values = _all_values # Skip first key - all_values = all_values["system"] + all_values = lib.convert_gui_data_with_metadata(all_values["system"]) prject_defaults_dir = os.path.join( DEFAULTS_DIR, SYSTEM_SETTINGS_KEY @@ -246,16 +251,19 @@ class SystemWidget(QtWidgets.QWidget): def _update_values(self): self.ignore_value_changes = True - default_values = { + default_values = lib.convert_data_to_gui_data({ "system": default_settings()[SYSTEM_SETTINGS_KEY] - } + }) for input_field in self.input_fields: input_field.update_default_values(default_values) if self._hide_studio_overrides: system_values = lib.NOT_SET else: - system_values = {"system": studio_system_settings()} + system_values = lib.convert_overrides_to_gui_data( + {"system": studio_system_settings()} + ) + for input_field in self.input_fields: input_field.update_studio_values(system_values) @@ -730,17 +738,20 @@ class ProjectWidget(QtWidgets.QWidget): def _update_values(self): self.ignore_value_changes = True - default_values = {"project": default_settings()} + default_values = default_values = lib.convert_data_to_gui_data( + {"project": default_settings()} + ) for input_field in self.input_fields: input_field.update_default_values(default_values) if self._hide_studio_overrides: studio_values = lib.NOT_SET else: - studio_values = {"project": { + studio_values = lib.convert_overrides_to_gui_data({"project": { PROJECT_SETTINGS_KEY: studio_project_settings(), PROJECT_ANATOMY_KEY: studio_project_anatomy() - }} + }}) + for input_field in self.input_fields: input_field.update_studio_values(studio_values) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index addce2b6b5..375dde566e 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -42,6 +42,8 @@ class SettingObject: # Will allow to show actions for the item type (disabled for proxies) else # item is skipped and try to trigger actions on it's parent. allow_actions = True + # If item can store environment values + allow_to_environment = False # All item types must have implemented Qt signal which is emitted when # it's or it's children value has changed, value_changed = None @@ -61,6 +63,24 @@ class SettingObject: key = getattr(self, "key", None) raise InvalidValueType(self.valid_value_types, type(value), key) + def merge_metadata(self, current_metadata, new_metadata): + for key, value in new_metadata.items(): + if key not in current_metadata: + current_metadata[key] = value + + elif key == "groups": + current_metadata[key].extend(value) + + elif key == "environments": + for group_key, subvalue in value.items(): + if group_key not in current_metadata[key]: + current_metadata[key][group_key] = [] + current_metadata[key][group_key].extend(subvalue) + + else: + raise KeyError("Unknown metadata key: \"{}\"".format(key)) + return current_metadata + def _set_default_attributes(self): """Create and reset attributes required for all item types. @@ -80,6 +100,9 @@ class SettingObject: self._as_widget = False self._is_group = False + # If value should be stored to environments + self._env_group_key = None + self._any_parent_as_widget = None self._any_parent_is_group = None @@ -115,6 +138,20 @@ class SettingObject: self._is_group = input_data.get("is_group", False) # TODO not implemented yet self._is_nullable = input_data.get("is_nullable", False) + self._env_group_key = input_data.get("env_group_key") + + if self.is_environ: + if not self.allow_to_environment: + raise TypeError(( + "Item {} does not allow to store environment values" + ).format(input_data["type"])) + + if self.as_widget: + raise TypeError(( + "Item is used as widget and" + " marked to store environments at the same time." + )) + self.add_environ_field(self) any_parent_as_widget = parent.as_widget if not any_parent_as_widget: @@ -171,6 +208,17 @@ class SettingObject: """ return self._has_studio_override or self._parent.has_studio_override + @property + def is_environ(self): + return self._env_group_key is not None + + @property + def env_group_key(self): + return self._env_group_key + + def add_environ_field(self, input_field): + self._parent.add_environ_field(input_field) + @property def as_widget(self): """Item is used as widget in parent item. @@ -300,6 +348,13 @@ class SettingObject: """Output for saving changes or overrides.""" return {self.key: self.item_value()} + def environment_value(self): + raise NotImplementedError( + "{} Method `environment_value` not implemented!".format( + repr(self) + ) + ) + @classmethod def style_state( cls, has_studio_override, is_invalid, is_overriden, is_modified @@ -1187,7 +1242,7 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): class RawJsonWidget(QtWidgets.QWidget, InputObject): default_input_value = "{}" value_changed = QtCore.Signal(object) - valid_value_types = (str, dict, list, type(NOT_SET)) + allow_to_environment = True def __init__( self, input_data, parent, @@ -1238,7 +1293,21 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): def item_value(self): if self.is_invalid: return NOT_SET - return self.input_field.json_value() + + value = self.input_field.json_value() + if not self.is_environ: + return value + + output = {} + for key, value in value.items(): + output[key.upper()] = value + + output[METADATA_KEY] = { + "environments": { + self.env_group_key: list(output.keys()) + } + } + return output class ListItem(QtWidgets.QWidget, SettingObject): @@ -2629,6 +2698,33 @@ class DictWidget(QtWidgets.QWidget, SettingObject): output.update(input_field.config_value()) return output + def _override_values(self, project_overrides): + values = {} + groups = [] + for input_field in self.input_fields: + if project_overrides: + value, is_group = input_field.overrides() + else: + value, is_group = input_field.studio_overrides() + if value is NOT_SET: + continue + + if METADATA_KEY in value and METADATA_KEY in values: + new_metadata = value.pop(METADATA_KEY) + values[METADATA_KEY] = self.merge_metadata( + values[METADATA_KEY], new_metadata + ) + + values.update(value) + if is_group: + groups.extend(value.keys()) + + if groups: + if METADATA_KEY not in values: + values[METADATA_KEY] = {} + values[METADATA_KEY]["groups"] = groups + return {self.key: values}, self.is_group + def studio_overrides(self): if ( not (self.as_widget or self.any_parent_as_widget) @@ -2636,34 +2732,12 @@ class DictWidget(QtWidgets.QWidget, SettingObject): and not self.child_has_studio_override ): return NOT_SET, False - - values = {} - groups = [] - for input_field in self.input_fields: - value, is_group = input_field.studio_overrides() - if value is not NOT_SET: - values.update(value) - if is_group: - groups.extend(value.keys()) - if groups: - values[METADATA_KEY] = {"groups": groups} - return {self.key: values}, self.is_group + return self._override_values(False) def overrides(self): if not self.is_overriden and not self.child_overriden: return NOT_SET, False - - values = {} - groups = [] - for input_field in self.input_fields: - value, is_group = input_field.overrides() - if value is not NOT_SET: - values.update(value) - if is_group: - groups.extend(value.keys()) - if groups: - values[METADATA_KEY] = {"groups": groups} - return {self.key: values}, self.is_group + return self._override_values(True) class DictInvisible(QtWidgets.QWidget, SettingObject): @@ -2897,6 +2971,33 @@ class DictInvisible(QtWidgets.QWidget, SettingObject): ) self._was_overriden = bool(self._is_overriden) + def _override_values(self, project_overrides): + values = {} + groups = [] + for input_field in self.input_fields: + if project_overrides: + value, is_group = input_field.overrides() + else: + value, is_group = input_field.studio_overrides() + if value is NOT_SET: + continue + + if METADATA_KEY in value and METADATA_KEY in values: + new_metadata = value.pop(METADATA_KEY) + values[METADATA_KEY] = self.merge_metadata( + values[METADATA_KEY], new_metadata + ) + + values.update(value) + if is_group: + groups.extend(value.keys()) + + if groups: + if METADATA_KEY not in values: + values[METADATA_KEY] = {} + values[METADATA_KEY]["groups"] = groups + return {self.key: values}, self.is_group + def studio_overrides(self): if ( not (self.as_widget or self.any_parent_as_widget) @@ -2904,34 +3005,12 @@ class DictInvisible(QtWidgets.QWidget, SettingObject): and not self.child_has_studio_override ): return NOT_SET, False - - values = {} - groups = [] - for input_field in self.input_fields: - value, is_group = input_field.studio_overrides() - if value is not NOT_SET: - values.update(value) - if is_group: - groups.extend(value.keys()) - if groups: - values[METADATA_KEY] = {"groups": groups} - return {self.key: values}, self.is_group + return self._override_values(False) def overrides(self): if not self.is_overriden and not self.child_overriden: return NOT_SET, False - - values = {} - groups = [] - for input_field in self.input_fields: - value, is_group = input_field.overrides() - if value is not NOT_SET: - values.update(value) - if is_group: - groups.extend(value.keys()) - if groups: - values[METADATA_KEY] = {"groups": groups} - return {self.key: values}, self.is_group + return self._override_values(True) class PathWidget(QtWidgets.QWidget, SettingObject): diff --git a/pype/tools/settings/settings/widgets/lib.py b/pype/tools/settings/settings/widgets/lib.py index cf2bd7f8af..569e7bfbb7 100644 --- a/pype/tools/settings/settings/widgets/lib.py +++ b/pype/tools/settings/settings/widgets/lib.py @@ -1,7 +1,8 @@ import os +import re import json import copy -from pype.settings.lib import OVERRIDEN_KEY +from pype.settings.lib import M_OVERRIDEN_KEY, M_ENVIRONMENT_KEY from queue import Queue @@ -11,10 +12,50 @@ class TypeToKlass: NOT_SET = type("NOT_SET", (), {"__bool__": lambda obj: False})() -METADATA_KEY = type("METADATA_KEY", (), {}) +METADATA_KEY = type("METADATA_KEY", (), {})() OVERRIDE_VERSION = 1 CHILD_OFFSET = 15 +key_pattern = re.compile(r"(\{.*?[^{0]*\})") + + +def convert_gui_data_with_metadata(data, ignored_keys=None): + if not data or not isinstance(data, dict): + return data + + if ignored_keys is None: + ignored_keys = tuple() + + output = {} + if METADATA_KEY in data: + metadata = data.pop(METADATA_KEY) + for key, value in metadata.items(): + if key in ignored_keys or key == "groups": + continue + + if key == "environments": + output[M_ENVIRONMENT_KEY] = value + else: + raise KeyError("Unknown metadata key \"{}\"".format(key)) + + for key, value in data.items(): + output[key] = convert_gui_data_with_metadata(value, ignored_keys) + return output + + +def convert_data_to_gui_data(data, first=True): + if not data or not isinstance(data, dict): + return data + + output = {} + if M_ENVIRONMENT_KEY in data: + data.pop(M_ENVIRONMENT_KEY) + + for key, value in data.items(): + output[key] = convert_data_to_gui_data(value, False) + + return output + def convert_gui_data_to_overrides(data, first=True): if not data or not isinstance(data, dict): @@ -23,14 +64,15 @@ def convert_gui_data_to_overrides(data, first=True): output = {} if first: output["__override_version__"] = OVERRIDE_VERSION + data = convert_gui_data_with_metadata(data) if METADATA_KEY in data: metadata = data.pop(METADATA_KEY) for key, value in metadata.items(): if key == "groups": - output[OVERRIDEN_KEY] = value + output[M_OVERRIDEN_KEY] = value else: - KeyError("Unknown metadata key \"{}\"".format(key)) + raise KeyError("Unknown metadata key \"{}\"".format(key)) for key, value in data.items(): output[key] = convert_gui_data_to_overrides(value, False) @@ -41,9 +83,12 @@ def convert_overrides_to_gui_data(data, first=True): if not data or not isinstance(data, dict): return data + if first: + data = convert_data_to_gui_data(data) + output = {} - if OVERRIDEN_KEY in data: - groups = data.pop(OVERRIDEN_KEY) + if M_OVERRIDEN_KEY in data: + groups = data.pop(M_OVERRIDEN_KEY) if METADATA_KEY not in output: output[METADATA_KEY] = {} output[METADATA_KEY]["groups"] = groups @@ -54,7 +99,111 @@ def convert_overrides_to_gui_data(data, first=True): return output -def _fill_inner_schemas(schema_data, schema_collection): +def _fill_schema_template_data( + template, template_data, required_keys=None, missing_keys=None +): + first = False + if required_keys is None: + first = True + required_keys = set() + missing_keys = set() + + _template = [] + default_values = {} + for item in template: + if isinstance(item, dict) and "__default_values__" in item: + default_values = item["__default_values__"] + else: + _template.append(item) + template = _template + + for key, value in default_values.items(): + if key not in template_data: + template_data[key] = value + + if not template: + output = template + + elif isinstance(template, list): + output = [] + for item in template: + output.append(_fill_schema_template_data( + item, template_data, required_keys, missing_keys + )) + + elif isinstance(template, dict): + output = {} + for key, value in template.items(): + output[key] = _fill_schema_template_data( + value, template_data, required_keys, missing_keys + ) + + elif isinstance(template, str): + # TODO find much better way how to handle filling template data + for replacement_string in key_pattern.findall(template): + key = str(replacement_string[1:-1]) + required_keys.add(key) + if key not in template_data: + missing_keys.add(key) + continue + + value = template_data[key] + if replacement_string == template: + # Replace the value with value from templates data + # - with this is possible to set value with different type + template = value + else: + # Only replace the key in string + template = template.replace(replacement_string, value) + output = template + + else: + output = template + + if first and missing_keys: + raise SchemaTemplateMissingKeys(missing_keys, required_keys) + + return output + + +def _fill_schema_template(child_data, schema_collection, schema_templates): + template_name = child_data["name"] + template = schema_templates.get(template_name) + if template is None: + if template_name in schema_collection: + raise KeyError(( + "Schema \"{}\" is used as `schema_template`" + ).format(template_name)) + raise KeyError("Schema template \"{}\" was not found".format( + template_name + )) + + template_data = child_data.get("template_data") or {} + try: + filled_child = _fill_schema_template_data( + template, template_data + ) + + except SchemaTemplateMissingKeys as exc: + raise SchemaTemplateMissingKeys( + exc.missing_keys, exc.required_keys, template_name + ) + + output = [] + for item in filled_child: + filled_item = _fill_inner_schemas( + item, schema_collection, schema_templates + ) + if filled_item["type"] == "schema_template": + output.extend(_fill_schema_template( + filled_item, schema_collection, schema_templates + )) + else: + output.append(filled_item) + return output + + +def _fill_inner_schemas(schema_data, schema_collection, schema_templates): if schema_data["type"] == "schema": raise ValueError("First item in schema data can't be schema.") @@ -64,21 +213,62 @@ def _fill_inner_schemas(schema_data, schema_collection): new_children = [] for child in children: - if child["type"] != "schema": - new_child = _fill_inner_schemas(child, schema_collection) - new_children.append(new_child) + child_type = child["type"] + if child_type == "schema": + schema_name = child["name"] + if schema_name not in schema_collection: + if schema_name in schema_templates: + raise KeyError(( + "Schema template \"{}\" is used as `schema`" + ).format(schema_name)) + raise KeyError( + "Schema \"{}\" was not found".format(schema_name) + ) + + filled_child = _fill_inner_schemas( + schema_collection[schema_name], + schema_collection, + schema_templates + ) + + elif child_type == "schema_template": + for filled_child in _fill_schema_template( + child, schema_collection, schema_templates + ): + new_children.append(filled_child) continue - new_child = _fill_inner_schemas( - schema_collection[child["name"]], - schema_collection - ) - new_children.append(new_child) + else: + filled_child = _fill_inner_schemas( + child, schema_collection, schema_templates + ) + + new_children.append(filled_child) schema_data["children"] = new_children return schema_data +class SchemaTemplateMissingKeys(Exception): + def __init__(self, missing_keys, required_keys, template_name=None): + self.missing_keys = missing_keys + self.required_keys = required_keys + if template_name: + msg = f"Schema template \"{template_name}\" require more keys.\n" + else: + msg = "" + msg += "Required keys: {}\nMissing keys: {}".format( + self.join_keys(required_keys), + self.join_keys(missing_keys) + ) + super(SchemaTemplateMissingKeys, self).__init__(msg) + + def join_keys(self, keys): + return ", ".join([ + f"\"{key}\"" for key in keys + ]) + + class SchemaMissingFileInfo(Exception): def __init__(self, invalid): full_path_keys = [] @@ -120,6 +310,21 @@ class SchemaDuplicatedKeys(Exception): super(SchemaDuplicatedKeys, self).__init__(msg) +class SchemaDuplicatedEnvGroupKeys(Exception): + def __init__(self, invalid): + items = [] + for key_path, keys in invalid.items(): + joined_keys = ", ".join([ + "\"{}\"".format(key) for key in keys + ]) + items.append("\"{}\" ({})".format(key_path, joined_keys)) + + msg = ( + "Schema items contain duplicated environment group keys. {}" + ).format(" || ".join(items)) + super(SchemaDuplicatedEnvGroupKeys, self).__init__(msg) + + def file_keys_from_schema(schema_data): output = [] item_type = schema_data["type"] @@ -277,10 +482,50 @@ def validate_keys_are_unique(schema_data, keys=None): raise SchemaDuplicatedKeys(invalid) +def validate_environment_groups_uniquenes( + schema_data, env_groups=None, keys=None +): + is_first = False + if env_groups is None: + is_first = True + env_groups = {} + keys = [] + + my_keys = copy.deepcopy(keys) + key = schema_data.get("key") + if key: + my_keys.append(key) + + env_group_key = schema_data.get("env_group_key") + if env_group_key: + if env_group_key not in env_groups: + env_groups[env_group_key] = [] + env_groups[env_group_key].append("/".join(my_keys)) + + children = schema_data.get("children") + if not children: + return + + for child in children: + validate_environment_groups_uniquenes( + child, env_groups, copy.deepcopy(my_keys) + ) + + if is_first: + invalid = {} + for env_group_key, key_paths in env_groups.items(): + if len(key_paths) > 1: + invalid[env_group_key] = key_paths + + if invalid: + raise SchemaDuplicatedEnvGroupKeys(invalid) + + def validate_schema(schema_data): validate_all_has_ending_file(schema_data) validate_is_group_is_unique_in_hierarchy(schema_data) validate_keys_are_unique(schema_data) + validate_environment_groups_uniquenes(schema_data) def gui_schema(subfolder, main_schema_name): @@ -292,23 +537,30 @@ def gui_schema(subfolder, main_schema_name): ) loaded_schemas = {} - for filename in os.listdir(dirpath): - basename, ext = os.path.splitext(filename) - if ext != ".json": - continue + loaded_schema_templates = {} + for root, _, filenames in os.walk(dirpath): + for filename in filenames: + basename, ext = os.path.splitext(filename) + if ext != ".json": + continue - filepath = os.path.join(dirpath, filename) - with open(filepath, "r") as json_stream: - try: - schema_data = json.load(json_stream) - except Exception as e: - raise Exception((f"Unable to parse JSON file {json_stream}\n " - f" - {e}")) from e - loaded_schemas[basename] = schema_data + filepath = os.path.join(root, filename) + with open(filepath, "r") as json_stream: + try: + schema_data = json.load(json_stream) + except Exception as exc: + raise Exception(( + f"Unable to parse JSON file {filepath}\n{exc}" + )) from exc + if isinstance(schema_data, list): + loaded_schema_templates[basename] = schema_data + else: + loaded_schemas[basename] = schema_data main_schema = _fill_inner_schemas( loaded_schemas[main_schema_name], - loaded_schemas + loaded_schemas, + loaded_schema_templates ) validate_schema(main_schema) return main_schema diff --git a/pype/tools/standalonepublish/widgets/widget_drop_frame.py b/pype/tools/standalonepublish/widgets/widget_drop_frame.py index e13f701b30..a7abe1b24c 100644 --- a/pype/tools/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/tools/standalonepublish/widgets/widget_drop_frame.py @@ -268,9 +268,10 @@ class DropDataFrame(QtWidgets.QFrame): args = [ ffprobe_path, '-v', 'quiet', - '-print_format', 'json', + '-print_format json', '-show_format', - '-show_streams', filepath + '-show_streams', + '"{}"'.format(filepath) ] ffprobe_p = subprocess.Popen( ' '.join(args), diff --git a/pype/version.py b/pype/version.py index 96fc614cb2..0f90260218 100644 --- a/pype/version.py +++ b/pype/version.py @@ -1 +1 @@ -__version__ = "2.12.1" +__version__ = "2.12.2"