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/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 75b0a34d8e..7d4e0333d6 100644
--- a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py
+++ b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py
@@ -130,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
@@ -158,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.
@@ -167,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.
@@ -187,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.
@@ -203,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.
@@ -244,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
@@ -258,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
@@ -272,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):
@@ -285,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/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/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 69%
rename from pype/settings/defaults/system_settings/environments/global.json
rename to pype/settings/defaults/environments/global.json
index ef528e6857..717e337db8 100644
--- a/pype/settings/defaults/system_settings/environments/global.json
+++ b/pype/settings/defaults/environments/global.json
@@ -6,21 +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"
- },
- "DJV_PATH": {
- "windows": [
- "C:/Program Files/djv-1.1.0-Windows-64/bin/djv_view.exe",
- "C:/Program Files/DJV/bin/djv_view.exe",
- "{STUDIO_SOFT}/djv/windows/bin/djv_view.exe"
- ],
- "linux": [
- "usr/local/djv/djv_view",
- "{STUDIO_SOFT}/djv/linux/bin/djv_view"
- ],
- "darwin": "Application/DJV.app/Contents/MacOS/DJV"
+ "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"
},
"PATH": [
"{PYPE_CONFIG}/launchers",
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/defaults/system_settings/global/applications.json b/pype/settings/defaults/system_settings/global/applications.json
index e85e5864d9..b85ec5369c 100644
--- a/pype/settings/defaults/system_settings/global/applications.json
+++ b/pype/settings/defaults/system_settings/global/applications.json
@@ -1,34 +1,862 @@
{
- "blender_2.80": false,
- "blender_2.81": false,
- "blender_2.82": false,
- "blender_2.83": true,
- "celaction_local": true,
- "celaction_remote": true,
- "harmony_17": true,
- "maya_2017": false,
- "maya_2018": false,
- "maya_2019": true,
- "maya_2020": true,
- "nuke_10.0": false,
- "nuke_11.2": false,
- "nuke_11.3": true,
- "nuke_12.0": true,
- "nukex_10.0": false,
- "nukex_11.2": false,
- "nukex_11.3": true,
- "nukex_12.0": true,
- "nukestudio_10.0": false,
- "nukestudio_11.2": false,
- "nukestudio_11.3": true,
- "nukestudio_12.0": true,
- "houdini_16": false,
- "houdini_16.5": false,
- "houdini_17": false,
- "houdini_18": true,
- "premiere_2019": false,
- "premiere_2020": true,
- "resolve_16": true,
- "storyboardpro_7": true,
- "unreal_4.24": true
+ "maya": {
+ "enabled": true,
+ "environment": {
+ "__environment_keys__": {
+ "maya": [
+ "PYTHONPATH",
+ "MAYA_DISABLE_CLIC_IPM",
+ "MAYA_DISABLE_CIP",
+ "MAYA_DISABLE_CER",
+ "PYMEL_SKIP_MEL_INIT",
+ "LC_ALL",
+ "PYPE_LOG_NO_COLORS"
+ ]
+ },
+ "PYTHONPATH": [
+ "{PYPE_SETUP_PATH}/repos/avalon-core/setup/maya",
+ "{PYPE_SETUP_PATH}/repos/maya-look-assigner",
+ "{PYTHON_ENV}/python2/Lib/site-packages",
+ "{PYTHONPATH}"
+ ],
+ "MAYA_DISABLE_CLIC_IPM": "Yes",
+ "MAYA_DISABLE_CIP": "Yes",
+ "MAYA_DISABLE_CER": "Yes",
+ "PYMEL_SKIP_MEL_INIT": "Yes",
+ "LC_ALL": "C",
+ "PYPE_LOG_NO_COLORS": "Yes"
+ },
+ "maya_2020": {
+ "enabled": true,
+ "maya_executables": {
+ "windows": [
+ "C:\\Program Files\\Autodesk\\maya2020\\bin\\maya.exe"
+ ],
+ "darwin": [
+ ""
+ ],
+ "linux": [
+ "/usr/autodesk/maya2020/bin/maya"
+ ]
+ },
+ "environment": {
+ "__environment_keys__": {
+ "maya_2020": [
+ "MAYA_VERSION",
+ "MAYA_LOCATION",
+ "DYLD_LIBRARY_PATH"
+ ]
+ },
+ "MAYA_VERSION": "2020",
+ "MAYA_LOCATION": {
+ "darwin": "/Applications/Autodesk/maya{MAYA_VERSION}/Maya.app/Contents",
+ "linux": "/usr/autodesk/maya{MAYA_VERSION}",
+ "windows": "C:/Program Files/Autodesk/Maya{MAYA_VERSION}"
+ },
+ "DYLD_LIBRARY_PATH": {
+ "darwin": "{MAYA_LOCATION}/MacOS"
+ }
+ }
+ },
+ "maya_2019": {
+ "enabled": true,
+ "maya_executables": {
+ "windows": [
+ "C:\\Program Files\\Autodesk\\maya2019\\bin\\maya.exe"
+ ],
+ "darwin": [
+ ""
+ ],
+ "linux": [
+ "/usr/autodesk/maya2019/bin/maya"
+ ]
+ },
+ "environment": {
+ "__environment_keys__": {
+ "maya_2019": [
+ "MAYA_VERSION",
+ "MAYA_LOCATION",
+ "DYLD_LIBRARY_PATH"
+ ]
+ },
+ "MAYA_VERSION": "2019",
+ "MAYA_LOCATION": {
+ "darwin": "/Applications/Autodesk/maya{MAYA_VERSION}/Maya.app/Contents",
+ "linux": "/usr/autodesk/maya{MAYA_VERSION}",
+ "windows": "C:/Program Files/Autodesk/Maya{MAYA_VERSION}"
+ },
+ "DYLD_LIBRARY_PATH": {
+ "darwin": "{MAYA_LOCATION}/MacOS"
+ }
+ }
+ },
+ "maya_2018": {
+ "enabled": true,
+ "maya_executables": {
+ "windows": [
+ "C:\\Program Files\\Autodesk\\maya2018\\bin\\maya.exe"
+ ],
+ "darwin": [
+ ""
+ ],
+ "linux": [
+ "/usr/autodesk/maya2018/bin/maya"
+ ]
+ },
+ "environment": {
+ "__environment_keys__": {
+ "maya_2018": [
+ "MAYA_VERSION",
+ "MAYA_LOCATION",
+ "DYLD_LIBRARY_PATH"
+ ]
+ },
+ "MAYA_VERSION": "2018",
+ "MAYA_LOCATION": {
+ "darwin": "/Applications/Autodesk/maya{MAYA_VERSION}/Maya.app/Contents",
+ "linux": "/usr/autodesk/maya{MAYA_VERSION}",
+ "windows": "C:/Program Files/Autodesk/Maya{MAYA_VERSION}"
+ },
+ "DYLD_LIBRARY_PATH": {
+ "darwin": "{MAYA_LOCATION}/MacOS"
+ }
+ }
+ }
+ },
+ "nuke": {
+ "enabled": true,
+ "environment": {
+ "__environment_keys__": {
+ "nuke": [
+ "NUKE_PATH",
+ "PATH"
+ ]
+ },
+ "NUKE_PATH": [
+ "{PYPE_SETUP_PATH}/repos/avalon-core/setup/nuke/nuke_path",
+ "{PYPE_MODULE_ROOT}/setup/nuke/nuke_path",
+ "{PYPE_STUDIO_PLUGINS}/nuke"
+ ],
+ "PATH": {
+ "windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}"
+ }
+ },
+ "nuke_12.0": {
+ "enabled": true,
+ "nuke_executables": {
+ "windows": [
+ "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe"
+ ],
+ "darwin": [],
+ "linux": [
+ "/usr/local/Nuke12.0v1/Nuke12.0"
+ ]
+ },
+ "environment": {
+ "__environment_keys__": {
+ "nuke_12.0": []
+ }
+ }
+ },
+ "nuke_11.3": {
+ "enabled": true,
+ "nuke_executables": {
+ "windows": [
+ "C:\\Program Files\\Nuke11.3v4\\Nuke11.3.exe",
+ "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe"
+ ],
+ "darwin": [],
+ "linux": [
+ "/usr/local/Nuke11.3v4/Nuke11.3",
+ "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe"
+ ]
+ },
+ "environment": {
+ "__environment_keys__": {
+ "nuke_11.3": []
+ }
+ }
+ },
+ "nuke_11.2": {
+ "enabled": true,
+ "nuke_executables": {
+ "windows": [
+ "C:\\Program Files\\Nuke11.2v3\\Nuke11.2.exe",
+ "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe",
+ "C:\\Program Files\\Nuke11.2v1\\Nuke11.2.exe"
+ ],
+ "darwin": [],
+ "linux": [
+ "/usr/local/Nuke11.2v3/Nuke11.2",
+ "/usr/local/Nuke11.2v2/Nuke11.2",
+ "/usr/local/Nuke11.2v1/Nuke11.2"
+ ]
+ },
+ "environment": {
+ "__environment_keys__": {
+ "nuke_11.2": []
+ }
+ }
+ }
+ },
+ "nukex": {
+ "enabled": true,
+ "environment": {
+ "__environment_keys__": {
+ "nukex": [
+ "NUKE_PATH",
+ "PATH"
+ ]
+ },
+ "NUKE_PATH": [
+ "{PYPE_SETUP_PATH}/repos/avalon-core/setup/nuke/nuke_path",
+ "{PYPE_MODULE_ROOT}/setup/nuke/nuke_path",
+ "{PYPE_STUDIO_PLUGINS}/nuke"
+ ],
+ "PATH": {
+ "windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}"
+ }
+ },
+ "nukex_12.0": {
+ "enabled": true,
+ "nukex_executables": {
+ "windows": [
+ "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe -nukex"
+ ],
+ "darwin": [],
+ "linux": [
+ "/usr/local/Nuke12.0v1/Nuke12.0 -nukex"
+ ]
+ },
+ "environment": {
+ "__environment_keys__": {
+ "nukex_12.0": []
+ }
+ }
+ },
+ "nukex_11.3": {
+ "enabled": true,
+ "nukex_executables": {
+ "windows": [
+ "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe -nukex"
+ ],
+ "darwin": [],
+ "linux": [
+ "/usr/local/Nuke11.3v1/Nuke11.3 -nukex"
+ ]
+ },
+ "environment": {
+ "__environment_keys__": {
+ "nukex_11.3": []
+ }
+ }
+ },
+ "nukex_11.2": {
+ "enabled": true,
+ "nukex_executables": {
+ "windows": [
+ "C:\\Program Files\\Nuke11.2v3\\Nuke11.2.exe -nukex"
+ ],
+ "darwin": [],
+ "linux": [
+ "/usr/local/Nuke11.2v3/Nuke11.2 -nukex"
+ ]
+ },
+ "environment": {
+ "__environment_keys__": {
+ "nukex_11.2": []
+ }
+ }
+ }
+ },
+ "nukestudio": {
+ "enabled": true,
+ "environment": {
+ "__environment_keys__": {
+ "nukestudio": [
+ "HIERO_PLUGIN_PATH",
+ "PATH",
+ "WORKFILES_STARTUP",
+ "TAG_ASSETBUILD_STARTUP",
+ "PYPE_LOG_NO_COLORS"
+ ]
+ },
+ "HIERO_PLUGIN_PATH": [
+ "{PYPE_MODULE_ROOT}/setup/nukestudio/hiero_plugin_path"
+ ],
+ "PATH": {
+ "windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}"
+ },
+ "WORKFILES_STARTUP": "0",
+ "TAG_ASSETBUILD_STARTUP": "0",
+ "PYPE_LOG_NO_COLORS": "True"
+ },
+ "nukestudio_12.0": {
+ "enabled": true,
+ "nukestudio_executables": {
+ "windows": [
+ "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe -studio"
+ ],
+ "darwin": [],
+ "linux": [
+ "/usr/local/Nuke12.0v1/Nuke12.0 -studio"
+ ]
+ },
+ "environment": {
+ "__environment_keys__": {
+ "nukestudio_12.0": []
+ }
+ }
+ },
+ "nukestudio_11.3": {
+ "enabled": true,
+ "nukestudio_executables": {
+ "windows": [
+ "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe -studio"
+ ],
+ "darwin": [],
+ "linux": [
+ "/usr/local/Nuke11.3v1/Nuke11.3 -studio"
+ ]
+ },
+ "environment": {
+ "__environment_keys__": {
+ "nukestudio_11.3": []
+ }
+ }
+ },
+ "nukestudio_11.2": {
+ "enabled": true,
+ "nukestudio_executables": {
+ "windows": [
+ "C:\\Program Files\\Nuke11.2v3\\Nuke11.2.exe -studio"
+ ],
+ "darwin": [],
+ "linux": [
+ "/usr/local/Nuke11.2v3/Nuke11.2 -studio"
+ ]
+ },
+ "environment": {
+ "__environment_keys__": {
+ "nukestudio_11.2": []
+ }
+ }
+ }
+ },
+ "hiero": {
+ "enabled": true,
+ "environment": {
+ "__environment_keys__": {
+ "hiero": [
+ "HIERO_PLUGIN_PATH",
+ "PATH",
+ "WORKFILES_STARTUP",
+ "TAG_ASSETBUILD_STARTUP",
+ "PYPE_LOG_NO_COLORS"
+ ]
+ },
+ "HIERO_PLUGIN_PATH": [
+ "{PYPE_MODULE_ROOT}/setup/nukestudio/hiero_plugin_path"
+ ],
+ "PATH": {
+ "windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}"
+ },
+ "WORKFILES_STARTUP": "0",
+ "TAG_ASSETBUILD_STARTUP": "0",
+ "PYPE_LOG_NO_COLORS": "True"
+ },
+ "hiero_12.0": {
+ "enabled": true,
+ "hiero_executables": {
+ "windows": [
+ "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe -hiero"
+ ],
+ "darwin": [],
+ "linux": [
+ "/usr/local/Nuke12.0v1/Nuke12.0 -hiero"
+ ]
+ },
+ "environment": {
+ "__environment_keys__": {
+ "hiero_12.0": []
+ }
+ }
+ },
+ "hiero_11.3": {
+ "enabled": true,
+ "hiero_executables": {
+ "windows": [
+ "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe -hiero"
+ ],
+ "darwin": [],
+ "linux": [
+ "/usr/local/Nuke11.3v1/Nuke11.3 -hiero"
+ ]
+ },
+ "environment": {
+ "__environment_keys__": {
+ "hiero_11.3": []
+ }
+ }
+ },
+ "hiero_11.2": {
+ "enabled": true,
+ "hiero_executables": {
+ "windows": [],
+ "darwin": [],
+ "linux": []
+ },
+ "environment": {
+ "__environment_keys__": {
+ "hiero_11.2": []
+ }
+ }
+ }
+ },
+ "fusion": {
+ "enabled": true,
+ "environment": {
+ "__environment_keys__": {
+ "fusion": []
+ }
+ },
+ "fusion_16": {
+ "enabled": true,
+ "fusion_executables": {
+ "windows": [],
+ "darwin": [],
+ "linux": []
+ },
+ "environment": {
+ "__environment_keys__": {
+ "fusion_16": []
+ }
+ }
+ },
+ "fusion_9": {
+ "enabled": true,
+ "fusion_executables": {
+ "windows": [],
+ "darwin": [],
+ "linux": []
+ },
+ "environment": {
+ "__environment_keys__": {
+ "fusion_9": []
+ }
+ }
+ }
+ },
+ "resolve": {
+ "enabled": true,
+ "environment": {
+ "__environment_keys__": {
+ "resolve": [
+ "RESOLVE_UTILITY_SCRIPTS_SOURCE_DIR",
+ "RESOLVE_SCRIPT_API",
+ "RESOLVE_SCRIPT_LIB",
+ "RESOLVE_UTILITY_SCRIPTS_DIR",
+ "PYTHON36_RESOLVE",
+ "PYTHONPATH",
+ "PATH",
+ "PRE_PYTHON_SCRIPT",
+ "PYPE_LOG_NO_COLORS",
+ "RESOLVE_DEV"
+ ]
+ },
+ "RESOLVE_UTILITY_SCRIPTS_SOURCE_DIR": [
+ "{STUDIO_SOFT}/davinci_resolve/scripts/python"
+ ],
+ "RESOLVE_SCRIPT_API": {
+ "windows": "{PROGRAMDATA}/Blackmagic Design/DaVinci Resolve/Support/Developer/Scripting",
+ "darvin": "/Library/Application Support/Blackmagic Design/DaVinci Resolve/Developer/Scripting",
+ "linux": "/opt/resolve/Developer/Scripting"
+ },
+ "RESOLVE_SCRIPT_LIB": {
+ "windows": "C:/Program Files/Blackmagic Design/DaVinci Resolve/fusionscript.dll",
+ "darvin": "/Applications/DaVinci Resolve/DaVinci Resolve.app/Contents/Libraries/Fusion/fusionscript.so",
+ "linux": "/opt/resolve/libs/Fusion/fusionscript.so"
+ },
+ "RESOLVE_UTILITY_SCRIPTS_DIR": {
+ "windows": "{PROGRAMDATA}/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Comp",
+ "darvin": "/Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Comp",
+ "linux": "/opt/resolve/Fusion/Scripts/Comp"
+ },
+ "PYTHON36_RESOLVE": {
+ "windows": "{LOCALAPPDATA}/Programs/Python/Python36",
+ "darvin": "~/Library/Python/3.6/bin",
+ "linux": "/opt/Python/3.6/bin"
+ },
+ "PYTHONPATH": [
+ "{PYTHON36_RESOLVE}/Lib/site-packages",
+ "{VIRTUAL_ENV}/Lib/site-packages",
+ "{PYTHONPATH}",
+ "{RESOLVE_SCRIPT_API}/Modules",
+ "{PYTHONPATH}"
+ ],
+ "PATH": [
+ "{PYTHON36_RESOLVE}",
+ "{PYTHON36_RESOLVE}/Scripts",
+ "{PATH}"
+ ],
+ "PRE_PYTHON_SCRIPT": "{PYPE_MODULE_ROOT}/pype/resolve/preload_console.py",
+ "PYPE_LOG_NO_COLORS": "True",
+ "RESOLVE_DEV": "True"
+ },
+ "resolve_16": {
+ "enabled": true,
+ "resolve_executables": {
+ "windows": [
+ "C:/Program Files/Blackmagic Design/DaVinci Resolve/Resolve.exe"
+ ],
+ "darwin": [],
+ "linux": []
+ },
+ "environment": {
+ "__environment_keys__": {
+ "resolve_16": []
+ }
+ }
+ }
+ },
+ "houdini": {
+ "enabled": true,
+ "environment": {
+ "__environment_keys__": {
+ "houdini": [
+ "HOUDINI_PATH",
+ "HOUDINI_MENU_PATH"
+ ]
+ },
+ "HOUDINI_PATH": {
+ "darwin": "{PYPE_MODULE_ROOT}/setup/houdini:&",
+ "linux": "{PYPE_MODULE_ROOT}/setup/houdini:&",
+ "windows": "{PYPE_MODULE_ROOT}/setup/houdini;&"
+ },
+ "HOUDINI_MENU_PATH": {
+ "darwin": "{PYPE_MODULE_ROOT}/setup/houdini:&",
+ "linux": "{PYPE_MODULE_ROOT}/setup/houdini:&",
+ "windows": "{PYPE_MODULE_ROOT}/setup/houdini;&"
+ }
+ },
+ "houdini_18": {
+ "enabled": true,
+ "houdini_executables": {
+ "windows": [
+ "C:\\Program Files\\Side Effects Software\\Houdini 18.0.287\\bin\\houdini.exe"
+ ],
+ "darwin": [],
+ "linux": []
+ },
+ "environment": {
+ "__environment_keys__": {
+ "houdini_18": []
+ }
+ }
+ },
+ "houdini_17": {
+ "enabled": true,
+ "houdini_executables": {
+ "windows": [
+ "C:\\Program Files\\Side Effects Software\\Houdini 17.0.459\\bin\\houdini.exe"
+ ],
+ "darwin": [],
+ "linux": []
+ },
+ "environment": {
+ "__environment_keys__": {
+ "houdini_17": []
+ }
+ }
+ }
+ },
+ "blender": {
+ "enabled": true,
+ "environment": {
+ "__environment_keys__": {
+ "blender": [
+ "BLENDER_USER_SCRIPTS",
+ "PYTHONPATH",
+ "CREATE_NEW_CONSOLE"
+ ]
+ },
+ "BLENDER_USER_SCRIPTS": "{PYPE_SETUP_PATH}/repos/avalon-core/setup/blender",
+ "PYTHONPATH": [
+ "{PYPE_SETUP_PATH}/repos/avalon-core/setup/blender",
+ "{PYTHONPATH}"
+ ],
+ "CREATE_NEW_CONSOLE": "yes"
+ },
+ "blender_2.90": {
+ "enabled": true,
+ "blender_executables": {
+ "windows": [
+ "C:\\Program Files\\Blender Foundation\\Blender 2.90\\blender.exe"
+ ],
+ "darwin": [],
+ "linux": []
+ },
+ "environment": {
+ "__environment_keys__": {
+ "blender_2.90": []
+ }
+ }
+ },
+ "blender_2.83": {
+ "enabled": true,
+ "blender_executables": {
+ "windows": [
+ "C:\\Program Files\\Blender Foundation\\Blender 2.83\\blender.exe"
+ ],
+ "darwin": [],
+ "linux": []
+ },
+ "environment": {
+ "__environment_keys__": {
+ "blender_2.83": []
+ }
+ }
+ }
+ },
+ "harmony": {
+ "enabled": true,
+ "environment": {
+ "__environment_keys__": {
+ "harmony": [
+ "AVALON_HARMONY_WORKFILES_ON_LAUNCH",
+ "PYBLISH_GUI_ALWAYS_EXEC"
+ ]
+ },
+ "AVALON_HARMONY_WORKFILES_ON_LAUNCH": "1",
+ "PYBLISH_GUI_ALWAYS_EXEC": "1"
+ },
+ "harmony_20": {
+ "enabled": true,
+ "harmony_executables": {
+ "windows": [
+ "C:/Program Files (x86)/Toon Boom Animation/Toon Boom Harmony 20 Premium/win64/bin/HarmonyPremium.exe"
+ ],
+ "darwin": [],
+ "linux": []
+ },
+ "environment": {
+ "__environment_keys__": {
+ "harmony_20": []
+ }
+ }
+ },
+ "harmony_19": {
+ "enabled": true,
+ "harmony_executables": {
+ "windows": [
+ "C:/Program Files (x86)/Toon Boom Animation/Toon Boom Harmony 19 Premium/win64/bin/HarmonyPremium.exe"
+ ],
+ "darwin": [],
+ "linux": []
+ },
+ "environment": {
+ "__environment_keys__": {
+ "harmony_19": []
+ }
+ }
+ },
+ "harmony_18": {
+ "enabled": true,
+ "harmony_executables": {
+ "windows": [
+ "C:/Program Files (x86)/Toon Boom Animation/Toon Boom Harmony 17 Premium/win64/bin/HarmonyPremium.exe"
+ ],
+ "darwin": [],
+ "linux": []
+ },
+ "environment": {
+ "__environment_keys__": {
+ "harmony_18": []
+ }
+ }
+ },
+ "harmony_17": {
+ "enabled": true,
+ "harmony_executables": {
+ "windows": [
+ "C:/Program Files (x86)/Toon Boom Animation/Toon Boom Harmony 17 Premium/win64/bin/HarmonyPremium.exe"
+ ],
+ "darwin": [],
+ "linux": []
+ },
+ "environment": {
+ "__environment_keys__": {
+ "harmony_17": []
+ }
+ }
+ }
+ },
+ "photoshop": {
+ "enabled": true,
+ "environment": {
+ "__environment_keys__": {
+ "photoshop": [
+ "AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH",
+ "PYTHONPATH",
+ "PYPE_LOG_NO_COLORS",
+ "WEBSOCKET_URL",
+ "WORKFILES_SAVE_AS"
+ ]
+ },
+ "AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH": "1",
+ "PYTHONPATH": "{PYTHONPATH}",
+ "PYPE_LOG_NO_COLORS": "Yes",
+ "WEBSOCKET_URL": "ws://localhost:8099/ws/",
+ "WORKFILES_SAVE_AS": "Yes"
+ },
+ "photoshop_2020": {
+ "enabled": true,
+ "photoshop_executables": {
+ "windows": [
+ "C:\\Program Files\\Adobe\\Adobe Photoshop 2020\\Photoshop.exe"
+ ],
+ "darwin": [],
+ "linux": []
+ },
+ "environment": {
+ "__environment_keys__": {
+ "photoshop_2020": []
+ }
+ }
+ }
+ },
+ "celaction": {
+ "enabled": true,
+ "environment": {
+ "__environment_keys__": {
+ "celaction": [
+ "CELACTION_TEMPLATE"
+ ]
+ },
+ "CELACTION_TEMPLATE": "{PYPE_MODULE_ROOT}/pype/hosts/celaction/celaction_template_scene.scn"
+ },
+ "celation_Local": {
+ "enabled": true,
+ "celation_executables": "C:\\Program Files (x86)\\CelAction\\CelAction2D.exe",
+ "environment": {
+ "__environment_keys__": {
+ "celation_Local": []
+ }
+ }
+ },
+ "celation_Publish": {
+ "enabled": true,
+ "celation_executables": "%PYPE_PYTHON_EXE% \"%PYPE_MODULE_ROOT%\\pype\\hosts\\celaction\\cli.py\" %*",
+ "environment": {
+ "__environment_keys__": {
+ "celation_Publish": []
+ }
+ }
+ }
+ },
+ "unreal": {
+ "enabled": true,
+ "environment": {
+ "__environment_keys__": {
+ "unreal": []
+ }
+ },
+ "unreal_4.24": {
+ "enabled": true,
+ "unreal_executables": {
+ "windows": [
+ "%AVALON_CURRENT_UNREAL_ENGINE%\\Engine\\Binaries\\Win64\\UE4Editor.exe"
+ ],
+ "darwin": [],
+ "linux": []
+ },
+ "environment": {
+ "__environment_keys__": {
+ "unreal_4.24": []
+ }
+ }
+ }
+ },
+ "shell": {
+ "enabled": true,
+ "environment": {
+ "__environment_keys__": {
+ "shell": []
+ }
+ },
+ "python_Python 3.7": {
+ "enabled": true,
+ "python_executables": {
+ "windows": [
+ "C:\\Python37\\python.exe",
+ "C:\\Python36\\python.exe"
+ ],
+ "darwin": [],
+ "linux": []
+ },
+ "environment": {
+ "__environment_keys__": {
+ "python_Python 3.7": []
+ }
+ }
+ },
+ "python_Python 2.7": {
+ "enabled": true,
+ "python_executables": {
+ "windows": [
+ "C:\\Python27\\python.exe"
+ ],
+ "darwin": [],
+ "linux": []
+ },
+ "environment": {
+ "__environment_keys__": {
+ "python_Python 2.7": []
+ }
+ }
+ },
+ "terminal_Terminal": {
+ "enabled": true,
+ "terminal_executables": {
+ "windows": [
+ "start cmd"
+ ],
+ "darwin": [],
+ "linux": []
+ },
+ "environment": {
+ "__environment_keys__": {
+ "terminal_Terminal": []
+ }
+ }
+ }
+ },
+ "djvview": {
+ "enabled": true,
+ "environment": {
+ "__environment_keys__": {
+ "djvview": []
+ }
+ },
+ "djvview_1.1": {
+ "enabled": true,
+ "djvview_executables": {
+ "windows": [
+ "C:/Program Files/djv-1.1.0-Windows-64/bin/djv_view.exe",
+ "C:/Program Files/DJV/bin/djv_view.exe"
+ ],
+ "darwin": [
+ "Application/DJV.app/Contents/MacOS/DJV"
+ ],
+ "linux": [
+ "usr/local/djv/djv_view"
+ ]
+ },
+ "environment": {
+ "__environment_keys__": {
+ "djvview_1.1": []
+ }
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/pype/settings/defaults/system_settings/global/general.json b/pype/settings/defaults/system_settings/global/general.json
index bd501b06eb..23e8a3ff5d 100644
--- a/pype/settings/defaults/system_settings/global/general.json
+++ b/pype/settings/defaults/system_settings/global/general.json
@@ -1,4 +1,53 @@
{
- "studio_name": "",
- "studio_code": ""
+ "studio_name": "convert from \"PYPE_STUDIO_NAME\"",
+ "studio_code": "convert from \"PYPE_STUDIO_CODE\"",
+ "project_plugins": {
+ "windows": "convert from \"PYPE_PROJECT_PLUGINS\"",
+ "darwin": "",
+ "linux": ""
+ },
+ "studio_soft": {
+ "windows": "convert from \"STUDIO_SOFT\"",
+ "darwin": "",
+ "linux": ""
+ },
+ "environment": {
+ "__environment_keys__": {
+ "global": [
+ "PYPE_APP_ROOT",
+ "PYPE_MODULE_ROOT",
+ "FFMPEG_PATH",
+ "PATH",
+ "PYTHONPATH",
+ "PYPE_PROJECT_CONFIGS",
+ "PYPE_PYTHON_EXE",
+ "PYBLISH_GUI"
+ ]
+ },
+ "PYPE_APP_ROOT": "{PYPE_SETUP_PATH}/pypeapp",
+ "PYPE_MODULE_ROOT": "{PYPE_SETUP_PATH}/repos/pype",
+ "FFMPEG_PATH": {
+ "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"
+ },
+ "PATH": [
+ "{PYPE_CONFIG}/launchers",
+ "{PYPE_APP_ROOT}",
+ "{FFMPEG_PATH}",
+ "{PATH}"
+ ],
+ "PYTHONPATH": {
+ "windows": "{VIRTUAL_ENV}/Lib/site-packages;{PYPE_MODULE_ROOT}/pype/tools;{PYPE_MODULE_ROOT}/pype/vendor;{PYTHONPATH}",
+ "linux": "{VIRTUAL_ENV}/lib/python{PYTHON_VERSION}/site-packages:{PYPE_MODULE_ROOT}/pype/tools:{PYPE_MODULE_ROOT}/pype/vendor:{PYTHONPATH}",
+ "darwin": "{VIRTUAL_ENV}/lib/python{PYTHON_VERSION}/site-packages:{PYPE_MODULE_ROOT}/pype/tools:{PYPE_MODULE_ROOT}/pype/vendor:{PYTHONPATH}"
+ },
+ "PYPE_PROJECT_CONFIGS": "{PYPE_SETUP_PATH}/../studio-project-configs",
+ "PYPE_PYTHON_EXE": {
+ "windows": "{VIRTUAL_ENV}/Scripts/python.exe",
+ "linux": "{VIRTUAL_ENV}/Scripts/python",
+ "darwin": "{VIRTUAL_ENV}/bin/python"
+ },
+ "PYBLISH_GUI": "pyblish_pype"
+ }
}
\ No newline at end of file
diff --git a/pype/settings/defaults/system_settings/global/hosts.json b/pype/settings/defaults/system_settings/global/hosts.json
new file mode 100644
index 0000000000..35ee708df3
--- /dev/null
+++ b/pype/settings/defaults/system_settings/global/hosts.json
@@ -0,0 +1,34 @@
+{
+ "blender_2.80": true,
+ "blender_2.81": true,
+ "blender_2.82": true,
+ "blender_2.83": true,
+ "celaction_local": true,
+ "celaction_remote": true,
+ "harmony_17": true,
+ "maya_2017": true,
+ "maya_2018": true,
+ "maya_2019": true,
+ "maya_2020": true,
+ "nuke_10.0": true,
+ "nuke_11.2": true,
+ "nuke_11.3": true,
+ "nuke_12.0": true,
+ "nukex_10.0": true,
+ "nukex_11.2": true,
+ "nukex_11.3": true,
+ "nukex_12.0": true,
+ "nukestudio_10.0": true,
+ "nukestudio_11.2": true,
+ "nukestudio_11.3": true,
+ "nukestudio_12.0": true,
+ "houdini_16": true,
+ "houdini_16.5": true,
+ "houdini_17": true,
+ "houdini_18": true,
+ "premiere_2019": true,
+ "premiere_2020": true,
+ "resolve_16": true,
+ "storyboardpro_7": true,
+ "unreal_4.24": true
+}
\ No newline at end of file
diff --git a/pype/settings/defaults/system_settings/global/modules.json b/pype/settings/defaults/system_settings/global/modules.json
index 9bd46602cf..b0245f52bd 100644
--- a/pype/settings/defaults/system_settings/global/modules.json
+++ b/pype/settings/defaults/system_settings/global/modules.json
@@ -2,7 +2,23 @@
"Avalon": {
"AVALON_MONGO": "mongodb://localhost:2707",
"AVALON_DB_DATA": "{PYPE_SETUP_PATH}/../mongo_db_data",
- "AVALON_THUMBNAIL_ROOT": "{PYPE_SETUP_PATH}/../avalon_thumails"
+ "AVALON_THUMBNAIL_ROOT": "{PYPE_SETUP_PATH}/../avalon_thumails",
+ "environment": {
+ "__environment_keys__": {
+ "avalon": [
+ "AVALON_CONFIG",
+ "AVALON_PROJECTS",
+ "AVALON_SCHEMA",
+ "AVALON_LABEL",
+ "AVALON_TIMEOUT"
+ ]
+ },
+ "AVALON_CONFIG": "pype",
+ "AVALON_PROJECTS": "{PYPE_PROJECTS_PATH}",
+ "AVALON_SCHEMA": "{PYPE_MODULE_ROOT}/schema",
+ "AVALON_LABEL": "Pype",
+ "AVALON_TIMEOUT": "1000"
+ }
},
"Ftrack": {
"enabled": true,
@@ -34,6 +50,24 @@
"test": "Test"
},
"default": "-"
+ },
+ "environment": {
+ "__environment_keys__": {
+ "ftrack": [
+ "FTRACK_ACTIONS_PATH",
+ "FTRACK_EVENTS_PATH",
+ "PYBLISHPLUGINPATH"
+ ]
+ },
+ "FTRACK_ACTIONS_PATH": [
+ "{PYPE_MODULE_ROOT}/pype/modules/ftrack/actions"
+ ],
+ "FTRACK_EVENTS_PATH": [
+ "{PYPE_MODULE_ROOT}/pype/modules/ftrack/events"
+ ],
+ "PYBLISHPLUGINPATH": [
+ "{PYPE_MODULE_ROOT}/pype/plugins/ftrack/publish"
+ ]
}
},
"Rest Api": {
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/0_system_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/0_system_gui_schema.json
index c5f229fc2f..eb7d707f6a 100644
--- a/pype/tools/settings/settings/gui_schemas/system_schema/0_system_gui_schema.json
+++ b/pype/tools/settings/settings/gui_schemas/system_schema/0_system_gui_schema.json
@@ -7,7 +7,7 @@
"key": "global",
"children": [{
"type": "schema",
- "name": "1_intents_gui_schema"
+ "name": "1_general_gui_schema"
},{
"type": "schema",
"name": "1_modules_gui_schema"
diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_applications_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_applications_gui_schema.json
index 3427f98253..6b73fc3f8c 100644
--- a/pype/tools/settings/settings/gui_schemas/system_schema/1_applications_gui_schema.json
+++ b/pype/tools/settings/settings/gui_schemas/system_schema/1_applications_gui_schema.json
@@ -3,137 +3,82 @@
"type": "dict",
"label": "Applications",
"collapsable": true,
- "is_group": true,
"is_file": true,
- "children": [
+ "children": [{
+ "type": "schema",
+ "name": "system_maya_schema"
+ },
{
- "type": "boolean",
- "key": "blender_2.80",
- "label": "Blender 2.80"
- }, {
- "type": "boolean",
- "key": "blender_2.81",
- "label": "Blender 2.81"
- }, {
- "type": "boolean",
- "key": "blender_2.82",
- "label": "Blender 2.82"
- }, {
- "type": "boolean",
- "key": "blender_2.83",
- "label": "Blender 2.83"
- }, {
- "type": "boolean",
- "key": "celaction_local",
- "label": "Celaction Local"
- }, {
- "type": "boolean",
- "key": "celaction_remote",
- "label": "Celaction Remote"
- }, {
- "type": "boolean",
- "key": "harmony_17",
- "label": "Harmony 17"
- }, {
- "type": "boolean",
- "key": "maya_2017",
- "label": "Autodest Maya 2017"
- }, {
- "type": "boolean",
- "key": "maya_2018",
- "label": "Autodest Maya 2018"
- }, {
- "type": "boolean",
- "key": "maya_2019",
- "label": "Autodest Maya 2019"
- }, {
- "type": "boolean",
- "key": "maya_2020",
- "label": "Autodest Maya 2020"
- }, {
- "key": "nuke_10.0",
- "type": "boolean",
- "label": "Nuke 10.0"
- }, {
- "type": "boolean",
- "key": "nuke_11.2",
- "label": "Nuke 11.2"
- }, {
- "type": "boolean",
- "key": "nuke_11.3",
- "label": "Nuke 11.3"
- }, {
- "type": "boolean",
- "key": "nuke_12.0",
- "label": "Nuke 12.0"
- }, {
- "type": "boolean",
- "key": "nukex_10.0",
- "label": "NukeX 10.0"
- }, {
- "type": "boolean",
- "key": "nukex_11.2",
- "label": "NukeX 11.2"
- }, {
- "type": "boolean",
- "key": "nukex_11.3",
- "label": "NukeX 11.3"
- }, {
- "type": "boolean",
- "key": "nukex_12.0",
- "label": "NukeX 12.0"
- }, {
- "type": "boolean",
- "key": "nukestudio_10.0",
- "label": "NukeStudio 10.0"
- }, {
- "type": "boolean",
- "key": "nukestudio_11.2",
- "label": "NukeStudio 11.2"
- }, {
- "type": "boolean",
- "key": "nukestudio_11.3",
- "label": "NukeStudio 11.3"
- }, {
- "type": "boolean",
- "key": "nukestudio_12.0",
- "label": "NukeStudio 12.0"
- }, {
- "type": "boolean",
- "key": "houdini_16",
- "label": "Houdini 16"
- }, {
- "type": "boolean",
- "key": "houdini_16.5",
- "label": "Houdini 16.5"
- }, {
- "type": "boolean",
- "key": "houdini_17",
- "label": "Houdini 17"
- }, {
- "type": "boolean",
- "key": "houdini_18",
- "label": "Houdini 18"
- }, {
- "type": "boolean",
- "key": "premiere_2019",
- "label": "Premiere 2019"
- }, {
- "type": "boolean",
- "key": "premiere_2020",
- "label": "Premiere 2020"
- }, {
- "type": "boolean",
- "key": "resolve_16",
- "label": "BM DaVinci Resolve 16"
- }, {
- "type": "boolean",
- "key": "storyboardpro_7",
- "label": "Storyboard Pro 7"
- }, {
- "type": "boolean",
- "key": "unreal_4.24",
- "label": "Unreal Editor 4.24"
+ "type": "schema_template",
+ "name": "system_nuke_template",
+ "template_data": {
+ "nuke_type": "nuke",
+ "nuke_label": "Nuke"
+ }
+ },
+ {
+ "type": "schema_template",
+ "name": "system_nuke_template",
+ "template_data": {
+ "nuke_type": "nukex",
+ "nuke_label": "Nuke X"
+ }
+ },
+ {
+ "type": "schema_template",
+ "name": "system_nuke_template",
+ "template_data": {
+ "nuke_type": "nukestudio",
+ "nuke_label": "Nuke Studio"
+ }
+ },
+ {
+ "type": "schema_template",
+ "name": "system_nuke_template",
+ "template_data": {
+ "nuke_type": "hiero",
+ "nuke_label": "Hiero"
+ }
+ },
+ {
+ "type": "schema",
+ "name": "system_fusion_schema"
+ },
+ {
+ "type": "schema",
+ "name": "system_resolve_schema"
+ },
+ {
+ "type": "schema",
+ "name": "system_houdini_schema"
+ },
+ {
+ "type": "schema",
+ "name": "system_blender_schema"
+ },
+ {
+ "type": "schema",
+ "name": "system_harmony_schema"
+ },
+ {
+ "type": "schema",
+ "name": "system_photoshop_schema"
+ },
+ {
+ "type": "schema",
+ "name": "system_celaction_schema"
+ },
+ {
+ "type": "schema",
+ "name": "system_unreal_schema"
+ },
+ {
+ "type": "schema",
+ "name": "system_shell_schema"
+ },
+ {
+ "type": "schema",
+ "name": "system_djv_schema"
}
]
}
diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_general_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_general_gui_schema.json
new file mode 100644
index 0000000000..15252ab39d
--- /dev/null
+++ b/pype/tools/settings/settings/gui_schemas/system_schema/1_general_gui_schema.json
@@ -0,0 +1,39 @@
+{
+ "key": "general",
+ "type": "dict",
+ "label": "General",
+ "collapsable": true,
+ "is_file": true,
+ "children": [{
+ "key": "studio_name",
+ "type": "text",
+ "label": "Studio Name"
+ }, {
+ "key": "studio_code",
+ "type": "text",
+ "label": "Studio Short Code"
+ }, {
+ "type": "splitter"
+ }, {
+ "key": "project_plugins",
+ "type": "path-widget",
+ "label": "Additional Project Plugins Path",
+ "multiplatform": true,
+ "multipath": false
+ }, {
+ "key": "studio_soft",
+ "type": "path-widget",
+ "label": "Studio Software Location",
+ "multiplatform": true,
+ "multipath": false
+ }, {
+ "type": "splitter"
+ },
+ {
+ "key": "environment",
+ "label": "Environment",
+ "type": "raw-json",
+ "env_group_key": "global"
+ }
+ ]
+}
diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_intents_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_intents_gui_schema.json
deleted file mode 100644
index 7f71da26cd..0000000000
--- a/pype/tools/settings/settings/gui_schemas/system_schema/1_intents_gui_schema.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "key": "general",
- "type": "dict",
- "label": "General",
- "collapsable": true,
- "is_file": true,
- "children": [{
- "key": "studio_name",
- "type": "text",
- "label": "Studio Name"
- },{
- "key": "studio_code",
- "type": "text",
- "label": "Studio Short Code"
- }
-]}
diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_modules_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_modules_gui_schema.json
index d78c11838a..937eea4097 100644
--- a/pype/tools/settings/settings/gui_schemas/system_schema/1_modules_gui_schema.json
+++ b/pype/tools/settings/settings/gui_schemas/system_schema/1_modules_gui_schema.json
@@ -1,283 +1,301 @@
{
- "key": "modules",
- "type": "dict",
- "label": "Modules",
- "collapsable": true,
- "is_file": true,
- "children": [{
- "type": "dict",
- "key": "Avalon",
- "label": "Avalon",
- "collapsable": true,
- "children": [
- {
- "type": "text",
- "key": "AVALON_MONGO",
- "label": "Avalon Mongo URL"
- },
- {
- "type": "text",
- "key": "AVALON_DB_DATA",
- "label": "Avalon Mongo Data Location"
- },
- {
- "type": "text",
- "key": "AVALON_THUMBNAIL_ROOT",
- "label": "Thumbnail Storage Location"
- }
- ]
- },{
+ "key": "modules",
+ "type": "dict",
+ "label": "Modules",
+ "collapsable": true,
+ "is_file": true,
+ "children": [{
+ "type": "dict",
+ "key": "Avalon",
+ "label": "Avalon",
+ "collapsable": true,
+ "children": [{
+ "type": "text",
+ "key": "AVALON_MONGO",
+ "label": "Avalon Mongo URL"
+ },
+ {
+ "type": "text",
+ "key": "AVALON_DB_DATA",
+ "label": "Avalon Mongo Data Location"
+ },
+ {
+ "type": "text",
+ "key": "AVALON_THUMBNAIL_ROOT",
+ "label": "Thumbnail Storage Location"
+ },
+ {
+ "key": "environment",
+ "label": "Environment",
+ "type": "raw-json",
+ "env_group_key": "avalon"
+ }
+ ]
+ }, {
"type": "dict",
"key": "Ftrack",
"label": "Ftrack",
"collapsable": true,
"checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ },
+ {
+ "type": "text",
+ "key": "ftrack_server",
+ "label": "Server"
+ },
+ {
+ "type": "splitter"
+ },
+ {
+ "type": "label",
+ "label": "Additional Ftrack paths"
+ },
+ {
+ "type": "list",
+ "key": "ftrack_actions_path",
+ "label": "Action paths",
+ "object_type": "text"
+ },
+ {
+ "type": "list",
+ "key": "ftrack_events_path",
+ "label": "Event paths",
+ "object_type": "text"
+ },
+ {
+ "type": "splitter"
+ },
+ {
+ "type": "label",
+ "label": "Ftrack event server advanced settings"
+ },
+ {
+ "type": "text",
+ "key": "FTRACK_EVENTS_MONGO_DB",
+ "label": "Event Mongo DB"
+ },
+ {
+ "type": "text",
+ "key": "FTRACK_EVENTS_MONGO_COL",
+ "label": "Events Mongo Collection"
+ },
+ {
+ "type": "dict",
+ "key": "sync_to_avalon",
+ "label": "Sync to avalon",
+ "children": [{
+ "type": "list",
+ "key": "statuses_name_change",
+ "label": "Status name change",
+ "object_type": {
+ "type": "text",
+ "multiline": false
+ }
+ }]
+ },
+ {
+ "type": "dict-modifiable",
+ "key": "status_version_to_task",
+ "label": "Version to Task status mapping",
+ "object_type": "text"
+ },
+ {
+ "type": "dict-modifiable",
+ "key": "status_update",
+ "label": "Status Updates",
+ "object_type": {
+ "type": "list",
+ "object_type": "text"
+ }
+ },
+ {
+ "key": "intent",
+ "type": "dict-invisible",
+ "children": [{
+ "type": "dict-modifiable",
+ "object_type": "text",
+ "key": "items",
+ "label": "Intent Key/Label"
+ },
+ {
+ "key": "default",
+ "type": "text",
+ "label": "Default Intent"
+ }
+ ]
+ },
+ {
+ "type": "splitter"
+ },
+ {
+ "key": "environment",
+ "label": "Environment",
+ "type": "raw-json",
+ "env_group_key": "ftrack"
+ }
+ ]
+ }, {
+ "type": "dict",
+ "key": "Rest Api",
+ "label": "Rest Api",
+ "collapsable": true,
+ "children": [{
+ "type": "number",
+ "key": "default_port",
+ "label": "Default Port",
+ "minimum": 1,
+ "maximum": 65535
+ },
+ {
+ "type": "list",
+ "key": "exclude_ports",
+ "label": "Exclude ports",
+ "object_type": {
+ "type": "number",
+ "minimum": 1,
+ "maximum": 65535
+ }
+ }
+ ]
+ }, {
+ "type": "dict",
+ "key": "Timers Manager",
+ "label": "Timers Manager",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ },
+ {
+ "type": "number",
+ "decimal": 2,
+ "key": "full_time",
+ "label": "Max idle time"
+ }, {
+ "type": "number",
+ "decimal": 2,
+ "key": "message_time",
+ "label": "When dialog will show"
+ }
+ ]
+ }, {
+ "type": "dict",
+ "key": "Clockify",
+ "label": "Clockify",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ },
+ {
+ "type": "text",
+ "key": "workspace_name",
+ "label": "Workspace name"
+ }
+ ]
+ }, {
+ "type": "dict",
+ "key": "Deadline",
+ "label": "Deadline",
+ "collapsable": true,
+ "checkbox_key": "enabled",
"children": [{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
- },
- {
- "type": "text",
- "key": "ftrack_server",
- "label": "Server"
- },
- {
- "type": "label",
- "label": "Additional Ftrack paths"
- },
- {
- "type": "list",
- "key": "ftrack_actions_path",
- "label": "Action paths",
- "object_type": "text"
- },
- {
- "type": "list",
- "key": "ftrack_events_path",
- "label": "Event paths",
- "object_type": "text"
- },
- {
- "type": "label",
- "label": "Ftrack event server advanced settings"
- },
- {
- "type": "text",
- "key": "FTRACK_EVENTS_MONGO_DB",
- "label": "Event Mongo DB"
- },
- {
- "type": "text",
- "key": "FTRACK_EVENTS_MONGO_COL",
- "label": "Events Mongo Collection"
- },
- {
- "type": "dict",
- "key": "sync_to_avalon",
- "label": "Sync to avalon",
- "children": [{
- "type": "list",
- "key": "statuses_name_change",
- "label": "Status name change",
- "object_type": {
- "type": "text",
- "multiline": false
- }
- }]
- },
- {
- "type": "dict-modifiable",
- "key": "status_version_to_task",
- "label": "Version to Task status mapping",
- "object_type": "text"
- },
- {
- "type": "dict-modifiable",
- "key": "status_update",
- "label": "Status Updates",
- "object_type": {
- "type": "list",
- "object_type": "text"
- }
- },
- {
- "key": "intent",
- "type": "dict-invisible",
- "children": [
- {
- "type": "dict-modifiable",
- "object_type": "text",
- "key": "items",
- "label": "Intent Key/Label"
- },
- {
- "key": "default",
- "type": "text",
- "label": "Defautl Intent"
- }
- ]
- }
- ]
- }, {
- "type": "dict",
- "key": "Rest Api",
- "label": "Rest Api",
- "collapsable": true,
- "children": [{
- "type": "number",
- "key": "default_port",
- "label": "Default Port",
- "minimum": 1,
- "maximum": 65535
- },
- {
- "type": "list",
- "key": "exclude_ports",
- "label": "Exclude ports",
- "object_type": {
- "type": "number",
- "minimum": 1,
- "maximum": 65535
- }
- }
- ]
- }, {
- "type": "dict",
- "key": "Timers Manager",
- "label": "Timers Manager",
- "collapsable": true,
- "checkbox_key": "enabled",
- "children": [{
- "type": "boolean",
- "key": "enabled",
- "label": "Enabled"
- },
- {
- "type": "number",
- "decimal": 2,
- "key": "full_time",
- "label": "Max idle time"
}, {
- "type": "number",
- "decimal": 2,
- "key": "message_time",
- "label": "When dialog will show"
- }
- ]
- }, {
- "type": "dict",
- "key": "Clockify",
- "label": "Clockify",
- "collapsable": true,
- "checkbox_key": "enabled",
- "children": [{
- "type": "boolean",
- "key": "enabled",
- "label": "Enabled"
- },
- {
- "type": "text",
- "key": "workspace_name",
- "label": "Workspace name"
- }
- ]
- }, {
- "type": "dict",
- "key": "Deadline",
- "label": "Deadline",
- "collapsable": true,
- "checkbox_key": "enabled",
- "children": [{
- "type": "boolean",
- "key": "enabled",
- "label": "Enabled"
- },{
- "type": "text",
- "key": "DEADLINE_REST_URL",
- "label": "Deadline Resl URL"
+ "type": "text",
+ "key": "DEADLINE_REST_URL",
+ "label": "Deadline Resl URL"
}]
}, {
- "type": "dict",
- "key": "Muster",
- "label": "Muster",
- "collapsable": true,
- "checkbox_key": "enabled",
- "children": [{
- "type": "boolean",
- "key": "enabled",
- "label": "Enabled"
- },{
- "type": "text",
- "key": "MUSTER_REST_URL",
- "label": "Muster Resl URL"
- },{
- "type": "dict-modifiable",
- "object_type": {
- "type": "number",
- "minimum": 0,
- "maximum": 300
- },
- "is_group": true,
- "key": "templates_mapping",
- "label": "Templates mapping",
- "is_file": true
- }]
- }, {
- "type": "dict",
- "key": "Logging",
- "label": "Logging",
- "collapsable": true,
- "checkbox_key": "enabled",
- "children": [{
- "type": "boolean",
- "key": "enabled",
- "label": "Enabled"
+ "type": "dict",
+ "key": "Muster",
+ "label": "Muster",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ }, {
+ "type": "text",
+ "key": "MUSTER_REST_URL",
+ "label": "Muster Resl URL"
+ }, {
+ "type": "dict-modifiable",
+ "object_type": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 300
+ },
+ "is_group": true,
+ "key": "templates_mapping",
+ "label": "Templates mapping",
+ "is_file": true
}]
}, {
- "type": "dict",
- "key": "Adobe Communicator",
- "label": "Adobe Communicator",
- "collapsable": true,
- "checkbox_key": "enabled",
- "children": [{
- "type": "boolean",
- "key": "enabled",
- "label": "Enabled"
+ "type": "dict",
+ "key": "Logging",
+ "label": "Logging",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
}]
}, {
- "type": "dict",
- "key": "User setting",
- "label": "User setting",
- "collapsable": true,
- "checkbox_key": "enabled",
- "children": [{
- "type": "boolean",
- "key": "enabled",
- "label": "Enabled"
+ "type": "dict",
+ "key": "Adobe Communicator",
+ "label": "Adobe Communicator",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
}]
}, {
- "type": "dict",
- "key": "Standalone Publish",
- "label": "Standalone Publish",
- "collapsable": true,
- "checkbox_key": "enabled",
- "children": [{
- "type": "boolean",
- "key": "enabled",
- "label": "Enabled"
+ "type": "dict",
+ "key": "User setting",
+ "label": "User setting",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
}]
}, {
- "type": "dict",
- "key": "Idle Manager",
- "label": "Idle Manager",
- "collapsable": true,
- "checkbox_key": "enabled",
- "children": [{
- "type": "boolean",
- "key": "enabled",
- "label": "Enabled"
+ "type": "dict",
+ "key": "Standalone Publish",
+ "label": "Standalone Publish",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
}]
- }
- ]
+ }, {
+ "type": "dict",
+ "key": "Idle Manager",
+ "label": "Idle Manager",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ }]
+ }]
}
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/gui_schemas/system_schema/host_settings/system_blender_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_blender_schema.json
new file mode 100644
index 0000000000..8600d8de8a
--- /dev/null
+++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_blender_schema.json
@@ -0,0 +1,35 @@
+{
+ "type": "dict",
+ "key": "blender",
+ "label": "Blender",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ },
+ {
+ "key": "environment",
+ "label": "Environment",
+ "type": "raw-json",
+ "env_group_key": "blender"
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "2.90",
+ "host_name": "blender"
+ }
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "2.83",
+ "host_name": "blender"
+ }
+ }
+ ]
+}
diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_celaction_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_celaction_schema.json
new file mode 100644
index 0000000000..36addab00d
--- /dev/null
+++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_celaction_schema.json
@@ -0,0 +1,39 @@
+{
+ "type": "dict",
+ "key": "celaction",
+ "label": "CelAction2D",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ },
+ {
+ "key": "environment",
+ "label": "Environment",
+ "type": "raw-json",
+ "env_group_key": "celaction"
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "Local",
+ "host_name": "celation",
+ "multiplatform": false,
+ "multipath_executables": false
+ }
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "Publish",
+ "host_name": "celation",
+ "multiplatform": false,
+ "multipath_executables": false
+ }
+ }
+ ]
+}
diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_djv_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_djv_schema.json
new file mode 100644
index 0000000000..704b13443d
--- /dev/null
+++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_djv_schema.json
@@ -0,0 +1,27 @@
+{
+ "type": "dict",
+ "key": "djvview",
+ "label": "DJV View",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ },
+ {
+ "key": "environment",
+ "label": "Environment",
+ "type": "raw-json",
+ "env_group_key": "djvview"
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "1.1",
+ "host_name": "djvview"
+ }
+ }
+ ]
+}
diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_fusion_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_fusion_schema.json
new file mode 100644
index 0000000000..b3dc79ef97
--- /dev/null
+++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_fusion_schema.json
@@ -0,0 +1,35 @@
+{
+ "type": "dict",
+ "key": "fusion",
+ "label": "Blackmagic Fusion",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ },
+ {
+ "key": "environment",
+ "label": "Environment",
+ "type": "raw-json",
+ "env_group_key": "fusion"
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "16",
+ "host_name": "fusion"
+ }
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "9",
+ "host_name": "fusion"
+ }
+ }
+ ]
+}
diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_harmony_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_harmony_schema.json
new file mode 100644
index 0000000000..10cb929fc4
--- /dev/null
+++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_harmony_schema.json
@@ -0,0 +1,51 @@
+{
+ "type": "dict",
+ "key": "harmony",
+ "label": "Toon Boom Harmony",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ },
+ {
+ "key": "environment",
+ "label": "Environment",
+ "type": "raw-json",
+ "env_group_key": "harmony"
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "20",
+ "host_name": "harmony"
+ }
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "19",
+ "host_name": "harmony"
+ }
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "18",
+ "host_name": "harmony"
+ }
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "17",
+ "host_name": "harmony"
+ }
+ }
+ ]
+}
diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_host_template.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_host_template.json
new file mode 100644
index 0000000000..b39d6ac79d
--- /dev/null
+++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_host_template.json
@@ -0,0 +1,33 @@
+[{
+ "__default_values__": {
+ "multipath_executables": true,
+ "multiplatform": true
+ }
+ },
+ {
+ "type": "dict",
+ "key": "{host_name}_{host_version}",
+ "label": "{host_version}",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ },
+ {
+ "type": "path-widget",
+ "key": "{host_name}_executables",
+ "label": "Executables",
+ "multiplatform": "{multiplatform}",
+ "multipath": "{multipath_executables}"
+ },
+ {
+ "key": "environment",
+ "label": "Environment",
+ "type": "raw-json",
+ "env_group_key": "{host_name}_{host_version}"
+ }
+ ]
+ }
+]
diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_houdini_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_houdini_schema.json
new file mode 100644
index 0000000000..36c6d9fcb2
--- /dev/null
+++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_houdini_schema.json
@@ -0,0 +1,35 @@
+{
+ "type": "dict",
+ "key": "houdini",
+ "label": "SideFX Houdini",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ },
+ {
+ "key": "environment",
+ "label": "Environment",
+ "type": "raw-json",
+ "env_group_key": "houdini"
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "18",
+ "host_name": "houdini"
+ }
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "17",
+ "host_name": "houdini"
+ }
+ }
+ ]
+}
diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_maya_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_maya_schema.json
new file mode 100644
index 0000000000..710b62a9cc
--- /dev/null
+++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_maya_schema.json
@@ -0,0 +1,43 @@
+{
+ "type": "dict",
+ "key": "maya",
+ "label": "Autodesk Maya",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ },
+ {
+ "key": "environment",
+ "label": "Environment",
+ "type": "raw-json",
+ "env_group_key": "maya"
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "2020",
+ "host_name": "maya"
+ }
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "2019",
+ "host_name": "maya"
+ }
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "2018",
+ "host_name": "maya"
+ }
+ }
+ ]
+}
diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_nuke_template.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_nuke_template.json
new file mode 100644
index 0000000000..22b2e0c4df
--- /dev/null
+++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_nuke_template.json
@@ -0,0 +1,47 @@
+[{
+ "type": "dict",
+ "key": "{nuke_type}",
+ "label": "Foundry {nuke_label}",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ },
+ {
+ "key": "environment",
+ "label": "Environment",
+ "type": "raw-json",
+ "env_group_key": "{nuke_type}"
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "12.0",
+ "host_name": "{nuke_type}",
+ "multipath_executables": true
+ }
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "11.3",
+ "host_name": "{nuke_type}",
+ "multipath_executables": true
+ }
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "11.2",
+ "host_name": "{nuke_type}",
+ "multipath_executables": true
+ }
+ }
+ ]
+}
+]
diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_photoshop_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_photoshop_schema.json
new file mode 100644
index 0000000000..c1062045e7
--- /dev/null
+++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_photoshop_schema.json
@@ -0,0 +1,27 @@
+{
+ "type": "dict",
+ "key": "photoshop",
+ "label": "Adobe Photoshop",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ },
+ {
+ "key": "environment",
+ "label": "Environment",
+ "type": "raw-json",
+ "env_group_key": "photoshop"
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "2020",
+ "host_name": "photoshop"
+ }
+ }
+ ]
+}
diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_resolve_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_resolve_schema.json
new file mode 100644
index 0000000000..364be8208d
--- /dev/null
+++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_resolve_schema.json
@@ -0,0 +1,27 @@
+{
+ "type": "dict",
+ "key": "resolve",
+ "label": "Blackmagic DaVinci Resolve",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ },
+ {
+ "key": "environment",
+ "label": "Environment",
+ "type": "raw-json",
+ "env_group_key": "resolve"
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "16",
+ "host_name": "resolve"
+ }
+ }
+ ]
+}
diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_shell_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_shell_schema.json
new file mode 100644
index 0000000000..22955abc46
--- /dev/null
+++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_shell_schema.json
@@ -0,0 +1,43 @@
+{
+ "type": "dict",
+ "key": "shell",
+ "label": "Shell",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ },
+ {
+ "key": "environment",
+ "label": "Environment",
+ "type": "raw-json",
+ "env_group_key": "shell"
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "Python 3.7",
+ "host_name": "python"
+ }
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "Python 2.7",
+ "host_name": "python"
+ }
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "Terminal",
+ "host_name": "terminal"
+ }
+ }
+ ]
+}
diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_unreal_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_unreal_schema.json
new file mode 100644
index 0000000000..e0408f9a36
--- /dev/null
+++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_unreal_schema.json
@@ -0,0 +1,27 @@
+{
+ "type": "dict",
+ "key": "unreal",
+ "label": "Unreal Editor",
+ "collapsable": true,
+ "checkbox_key": "enabled",
+ "children": [{
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ },
+ {
+ "key": "environment",
+ "label": "Environment",
+ "type": "raw-json",
+ "env_group_key": "unreal"
+ },
+ {
+ "type": "schema_template",
+ "name": "system_host_template",
+ "template_data": {
+ "host_version": "4.24",
+ "host_name": "unreal"
+ }
+ }
+ ]
+}
diff --git a/pype/tools/settings/settings/widgets/base.py b/pype/tools/settings/settings/widgets/base.py
index 243a8448e5..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,10 +131,14 @@ 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()
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)
@@ -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 4124c32ba8..87f2fcd48e 100644
--- a/pype/tools/settings/settings/widgets/item_types.py
+++ b/pype/tools/settings/settings/widgets/item_types.py
@@ -6,7 +6,8 @@ from .widgets import (
NumberSpinBox,
PathInput,
GridLabelWidget,
- ComboBox
+ ComboBox,
+ NiceCheckbox
)
from .multiselection_combobox import MultiSelectionComboBox
from .lib import NOT_SET, METADATA_KEY, TypeToKlass, CHILD_OFFSET
@@ -14,6 +15,24 @@ from pype.api import Logger
from avalon.vendor import qtawesome
+class InvalidValueType(Exception):
+ msg_template = "{}"
+
+ def __init__(self, valid_types, invalid_type, key):
+ msg = ""
+ if key:
+ msg += "Key \"{}\". ".format(key)
+
+ joined_types = ", ".join(
+ [str(valid_type) for valid_type in valid_types]
+ )
+ msg += "Got invalid type \"{}\". Expected: {}".format(
+ invalid_type, joined_types
+ )
+ self.msg = msg
+ super(InvalidValueType, self).__init__(msg)
+
+
class SettingObject:
"""Partially abstract class for Setting's item type workflow."""
# `is_input_type` attribute says if has implemented item type methods
@@ -24,11 +43,44 @@ 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
# Item will expand to full width in grid layout
expand_in_grid = False
+ # types that are valid for `set_value` method
+ valid_value_types = tuple()
+
+ def validate_value(self, value):
+ if not self.valid_value_types:
+ return
+
+ for valid_type in self.valid_value_types:
+ if type(value) is valid_type:
+ return
+
+ 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.
@@ -49,6 +101,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
@@ -84,6 +139,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:
@@ -140,6 +209,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.
@@ -202,7 +282,7 @@ class SettingObject:
def is_modified(self):
"""Has object any changes that require saving."""
if self.any_parent_as_widget:
- return self._is_modified
+ return self._is_modified or self.defaults_not_set
if self._is_modified or self.defaults_not_set:
return True
@@ -269,6 +349,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
@@ -566,7 +653,7 @@ class InputObject(SettingObject):
self._is_modified = False
value = NOT_SET
- if self._as_widget:
+ if self.as_widget:
value = parent_values
elif parent_values is not NOT_SET:
value = parent_values.get(self.key, NOT_SET)
@@ -591,7 +678,12 @@ class InputObject(SettingObject):
self.default_value = value
self._has_studio_override = False
self._had_studio_override = False
- self.set_value(value)
+ try:
+ self.set_value(value)
+ except InvalidValueType as exc:
+ self.default_value = NOT_SET
+ self.defaults_not_set = True
+ self.log.warning(exc.msg)
def update_studio_values(self, parent_values):
self._state = None
@@ -607,12 +699,17 @@ class InputObject(SettingObject):
if value is not NOT_SET:
self._has_studio_override = True
self._had_studio_override = True
- self.set_value(value)
else:
self._has_studio_override = False
self._had_studio_override = False
- self.set_value(self.default_value)
+ value = self.default_value
+
+ try:
+ self.set_value(value)
+ except InvalidValueType as exc:
+ self.studio_value = NOT_SET
+ self.log.warning(exc.msg)
def apply_overrides(self, parent_values):
self._is_modified = False
@@ -639,7 +736,11 @@ class InputObject(SettingObject):
self._was_overriden = True
value = override_value
- self.set_value(value)
+ try:
+ self.set_value(value)
+ except InvalidValueType as exc:
+ self.override_value = NOT_SET
+ self.log.warning(exc.msg)
def _on_value_change(self, item=None):
if self.ignore_value_changes:
@@ -689,7 +790,7 @@ class InputObject(SettingObject):
False,
self._is_invalid,
False,
- self._is_modified
+ self.is_modified
)
else:
state = self.style_state(
@@ -750,6 +851,7 @@ class InputObject(SettingObject):
self._is_overriden = False
return
+ self._state = None
self._is_modified = False
self._is_overriden = self._was_overriden
@@ -785,6 +887,7 @@ class InputObject(SettingObject):
class BooleanWidget(QtWidgets.QWidget, InputObject):
default_input_value = True
value_changed = QtCore.Signal(object)
+ valid_value_types = (bool, )
def __init__(
self, input_data, parent,
@@ -809,7 +912,11 @@ class BooleanWidget(QtWidgets.QWidget, InputObject):
layout.addWidget(label_widget, 0)
self.label_widget = label_widget
- self.input_field = QtWidgets.QCheckBox(self)
+ checkbox_height = self.style().pixelMetric(
+ QtWidgets.QStyle.PM_IndicatorHeight
+ )
+ self.input_field = NiceCheckbox(height=checkbox_height, parent=self)
+
spacer = QtWidgets.QWidget(self)
spacer.setAttribute(QtCore.Qt.WA_TranslucentBackground)
@@ -823,6 +930,7 @@ class BooleanWidget(QtWidgets.QWidget, InputObject):
def set_value(self, value):
# Ignore value change because if `self.isChecked()` has same
# value as `value` the `_on_value_change` is not triggered
+ self.validate_value(value)
self.input_field.setChecked(value)
def item_value(self):
@@ -833,6 +941,7 @@ class NumberWidget(QtWidgets.QWidget, InputObject):
default_input_value = 0
value_changed = QtCore.Signal(object)
input_modifiers = ("minimum", "maximum", "decimal")
+ valid_value_types = (int, float)
def __init__(
self, input_data, parent,
@@ -870,6 +979,7 @@ class NumberWidget(QtWidgets.QWidget, InputObject):
self.input_field.valueChanged.connect(self._on_value_change)
def set_value(self, value):
+ self.validate_value(value)
self.input_field.setValue(value)
def item_value(self):
@@ -879,6 +989,7 @@ class NumberWidget(QtWidgets.QWidget, InputObject):
class TextWidget(QtWidgets.QWidget, InputObject):
default_input_value = ""
value_changed = QtCore.Signal(object)
+ valid_value_types = (str, )
def __init__(
self, input_data, parent,
@@ -924,6 +1035,7 @@ class TextWidget(QtWidgets.QWidget, InputObject):
self.input_field.textChanged.connect(self._on_value_change)
def set_value(self, value):
+ self.validate_value(value)
if self.multiline:
self.input_field.setPlainText(value)
else:
@@ -939,6 +1051,7 @@ class TextWidget(QtWidgets.QWidget, InputObject):
class PathInputWidget(QtWidgets.QWidget, InputObject):
default_input_value = ""
value_changed = QtCore.Signal(object)
+ valid_value_types = (str, )
def __init__(
self, input_data, parent,
@@ -969,6 +1082,7 @@ class PathInputWidget(QtWidgets.QWidget, InputObject):
self.input_field.textChanged.connect(self._on_value_change)
def set_value(self, value):
+ self.validate_value(value)
self.input_field.setText(value)
def focusOutEvent(self, event):
@@ -1108,6 +1222,7 @@ class RawJsonInput(QtWidgets.QPlainTextEdit):
def set_value(self, value):
if value is NOT_SET:
value = ""
+
elif not isinstance(value, str):
try:
value = json.dumps(value, indent=4)
@@ -1133,6 +1248,8 @@ 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,
@@ -1173,6 +1290,7 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject):
return super(RawJsonWidget, self).update_studio_values(parent_values)
def set_value(self, value):
+ self.validate_value(value)
self.input_field.set_value(value)
def _on_value_change(self, *args, **kwargs):
@@ -1182,8 +1300,24 @@ 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
+ return output
+
+ def config_value(self):
+ value = self.item_value()
+ value[METADATA_KEY] = {
+ "environments": {
+ self.env_group_key: list(value.keys())
+ }
+ }
+ return {self.key: value}
class ListItem(QtWidgets.QWidget, SettingObject):
_btn_size = 20
@@ -1338,6 +1472,12 @@ class ListItem(QtWidgets.QWidget, SettingObject):
return self.value_input.item_value()
return NOT_SET
+ @property
+ def is_modified(self):
+ if self._is_empty:
+ return False
+ return self.value_input.is_modified
+
@property
def child_has_studio_override(self):
return self.value_input.child_has_studio_override
@@ -1369,6 +1509,7 @@ class ListItem(QtWidgets.QWidget, SettingObject):
class ListWidget(QtWidgets.QWidget, InputObject):
default_input_value = []
value_changed = QtCore.Signal(object)
+ valid_value_types = (list, )
def __init__(
self, input_data, parent,
@@ -1433,6 +1574,8 @@ class ListWidget(QtWidgets.QWidget, InputObject):
self.hierarchical_style_update()
def set_value(self, value):
+ self.validate_value(value)
+
previous_inputs = tuple(self.input_fields)
for item_value in value:
self.add_row(value=item_value)
@@ -1579,6 +1722,17 @@ class ListWidget(QtWidgets.QWidget, InputObject):
input_field.hierarchical_style_update()
self.update_style()
+ @property
+ def is_modified(self):
+ is_modified = super(ListWidget, self).is_modified
+ if is_modified:
+ return is_modified
+
+ for input_field in self.input_fields:
+ if input_field.is_modified:
+ return True
+ return False
+
def update_style(self):
if not self.label_widget:
return
@@ -1603,6 +1757,7 @@ class ListWidget(QtWidgets.QWidget, InputObject):
class ListStrictWidget(QtWidgets.QWidget, InputObject):
value_changed = QtCore.Signal(object)
_default_input_value = None
+ valid_value_types = (list, )
def __init__(
self, input_data, parent,
@@ -1719,6 +1874,8 @@ class ListStrictWidget(QtWidgets.QWidget, InputObject):
return self._default_input_value
def set_value(self, value):
+ self.validate_value(value)
+
if self._is_overriden:
method_name = "apply_overrides"
elif not self._has_studio_override:
@@ -1895,6 +2052,8 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject):
@property
def is_modified(self):
+ if self._is_empty:
+ return False
return self.is_value_modified() or self.is_key_modified()
def hierarchical_style_update(self):
@@ -1941,6 +2100,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject):
# TODO this is actually input field (do not care if is group or not)
value_changed = QtCore.Signal(object)
expand_in_grid = True
+ valid_value_types = (dict, )
def __init__(
self, input_data, parent,
@@ -2031,6 +2191,8 @@ class ModifiableDict(QtWidgets.QWidget, InputObject):
return len(self.input_fields)
def set_value(self, value):
+ self.validate_value(value)
+
previous_inputs = tuple(self.input_fields)
for item_key, item_value in value.items():
self.add_row(key=item_key, value=item_value)
@@ -2079,6 +2241,17 @@ class ModifiableDict(QtWidgets.QWidget, InputObject):
self.value_changed.emit(self)
+ @property
+ def is_modified(self):
+ is_modified = super(ModifiableDict, self).is_modified
+ if is_modified:
+ return is_modified
+
+ for input_field in self.input_fields:
+ if input_field.is_modified:
+ return True
+ return False
+
def hierarchical_style_update(self):
for input_field in self.input_fields:
input_field.hierarchical_style_update()
@@ -2193,6 +2366,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject):
class DictWidget(QtWidgets.QWidget, SettingObject):
value_changed = QtCore.Signal(object)
expand_in_grid = True
+ valid_value_types = (dict, type(NOT_SET))
def __init__(
self, input_data, parent,
@@ -2349,13 +2523,22 @@ class DictWidget(QtWidgets.QWidget, SettingObject):
self._has_studio_override = True
def discard_changes(self):
- self._is_overriden = self._was_overriden
self._is_modified = False
+ self._is_overriden = self._was_overriden
+ self._has_studio_override = self._had_studio_override
for input_field in self.input_fields:
input_field.discard_changes()
self._is_modified = self.child_modified
+ if not self.is_overidable and self.as_widget:
+ if self.has_studio_override:
+ self._is_modified = self.studio_value != self.item_value()
+ else:
+ self._is_modified = self.default_value != self.item_value()
+
+ self._state = None
+ self._is_overriden = self._was_overriden
def set_as_overriden(self):
if self.is_overriden:
@@ -2379,6 +2562,12 @@ class DictWidget(QtWidgets.QWidget, SettingObject):
elif parent_values is not NOT_SET:
value = parent_values.get(self.key, NOT_SET)
+ try:
+ self.validate_value(value)
+ except InvalidValueType as exc:
+ value = NOT_SET
+ self.log.warning(exc.msg)
+
for item in self.input_fields:
item.update_default_values(value)
@@ -2399,6 +2588,14 @@ class DictWidget(QtWidgets.QWidget, SettingObject):
self._had_studio_override = bool(self._has_studio_override)
+ try:
+ self.validate_value(value)
+ except InvalidValueType as exc:
+ value = NOT_SET
+ self._has_studio_override = False
+ self._had_studio_override = False
+ self.log.warning(exc.msg)
+
for item in self.input_fields:
item.update_studio_values(value)
@@ -2420,6 +2617,12 @@ class DictWidget(QtWidgets.QWidget, SettingObject):
self._is_overriden = self.key in groups
+ try:
+ self.validate_value(override_values)
+ except InvalidValueType as exc:
+ override_values = NOT_SET
+ self.log.warning(exc.msg)
+
for item in self.input_fields:
item.apply_overrides(override_values)
@@ -2543,6 +2746,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)
@@ -2550,34 +2780,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):
@@ -2585,6 +2793,7 @@ class DictInvisible(QtWidgets.QWidget, SettingObject):
value_changed = QtCore.Signal(object)
allow_actions = False
expand_in_grid = True
+ valid_value_types = (dict, type(NOT_SET))
def __init__(
self, input_data, parent,
@@ -2731,11 +2940,20 @@ class DictInvisible(QtWidgets.QWidget, SettingObject):
def discard_changes(self):
self._is_modified = False
self._is_overriden = self._was_overriden
+ self._has_studio_override = self._had_studio_override
for input_field in self.input_fields:
input_field.discard_changes()
self._is_modified = self.child_modified
+ if not self.is_overidable and self.as_widget:
+ if self.has_studio_override:
+ self._is_modified = self.studio_value != self.item_value()
+ else:
+ self._is_modified = self.default_value != self.item_value()
+
+ self._state = None
+ self._is_overriden = self._was_overriden
def set_as_overriden(self):
if self.is_overriden:
@@ -2750,11 +2968,17 @@ class DictInvisible(QtWidgets.QWidget, SettingObject):
def update_default_values(self, parent_values):
value = NOT_SET
- if self._as_widget:
+ if self.as_widget:
value = parent_values
elif parent_values is not NOT_SET:
value = parent_values.get(self.key, NOT_SET)
+ try:
+ self.validate_value(value)
+ except InvalidValueType as exc:
+ value = NOT_SET
+ self.log.warning(exc.msg)
+
for item in self.input_fields:
item.update_default_values(value)
@@ -2763,6 +2987,12 @@ class DictInvisible(QtWidgets.QWidget, SettingObject):
if parent_values is not NOT_SET:
value = parent_values.get(self.key, NOT_SET)
+ try:
+ self.validate_value(value)
+ except InvalidValueType as exc:
+ value = NOT_SET
+ self.log.warning(exc.msg)
+
for item in self.input_fields:
item.update_studio_values(value)
@@ -2781,6 +3011,12 @@ class DictInvisible(QtWidgets.QWidget, SettingObject):
self._is_overriden = self.key in groups
+ try:
+ self.validate_value(override_values)
+ except InvalidValueType as exc:
+ override_values = NOT_SET
+ self.log.warning(exc.msg)
+
for item in self.input_fields:
item.apply_overrides(override_values)
@@ -2792,6 +3028,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)
@@ -2799,34 +3062,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):
@@ -3114,20 +3355,20 @@ class PathWidget(QtWidgets.QWidget, SettingObject):
self._has_studio_override = True
def discard_changes(self):
+ self._is_modified = False
self._is_overriden = self._was_overriden
self._has_studio_override = self._had_studio_override
self.input_field.discard_changes()
- if not self.is_overidable:
+ self._is_modified = self.child_modified
+ if not self.is_overidable and self.as_widget:
if self.has_studio_override:
self._is_modified = self.studio_value != self.item_value()
else:
self._is_modified = self.default_value != self.item_value()
- self._is_overriden = False
- return
- self._is_modified = False
+ self._state = None
self._is_overriden = self._was_overriden
def set_as_overriden(self):
@@ -3243,11 +3484,20 @@ class DictFormWidget(QtWidgets.QWidget, SettingObject):
def discard_changes(self):
self._is_modified = False
self._is_overriden = self._was_overriden
+ self._has_studio_override = self._had_studio_override
- for item in self.input_fields:
- item.discard_changes()
+ for input_field in self.input_fields:
+ input_field.discard_changes()
self._is_modified = self.child_modified
+ if not self.is_overidable and self.as_widget:
+ if self.has_studio_override:
+ self._is_modified = self.studio_value != self.item_value()
+ else:
+ self._is_modified = self.default_value != self.item_value()
+
+ self._state = None
+ self._is_overriden = self._was_overriden
def remove_overrides(self):
self._is_overriden = False
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/settings/settings/widgets/widgets.py b/pype/tools/settings/settings/widgets/widgets.py
index b0bcf059a5..b64b1aa8ac 100644
--- a/pype/tools/settings/settings/widgets/widgets.py
+++ b/pype/tools/settings/settings/widgets/widgets.py
@@ -288,3 +288,191 @@ class GridLabelWidget(QtWidgets.QWidget):
if self.input_field:
return self.input_field.show_actions_menu(event)
return super(GridLabelWidget, self).mouseReleaseEvent(event)
+
+
+class NiceCheckboxMoveWidget(QtWidgets.QFrame):
+ def __init__(self, height, border_width, parent):
+ super(NiceCheckboxMoveWidget, self).__init__(parent=parent)
+
+ self.checkstate = False
+
+ self.half_size = int(height / 2)
+ self.full_size = self.half_size * 2
+ self.border_width = border_width
+ self.setFixedHeight(self.full_size)
+ self.setFixedWidth(self.full_size)
+
+ self.setStyleSheet((
+ "background: #444444;border-style: none;"
+ "border-radius: {};border-width:{}px;"
+ ).format(self.half_size, self.border_width))
+
+ def update_position(self):
+ parent_rect = self.parent().rect()
+ if self.checkstate is True:
+ pos_x = (
+ parent_rect.x()
+ + parent_rect.width()
+ - self.full_size
+ - self.border_width
+ )
+ else:
+ pos_x = parent_rect.x() + self.border_width
+
+ pos_y = parent_rect.y() + int(
+ parent_rect.height() / 2 - self.half_size
+ )
+ self.setGeometry(pos_x, pos_y, self.width(), self.height())
+
+ def state_offset(self):
+ diff_x = (
+ self.parent().rect().width()
+ - self.full_size
+ - (2 * self.border_width)
+ )
+ return QtCore.QPoint(diff_x, 0)
+
+ def change_position(self, checkstate):
+ self.checkstate = checkstate
+
+ self.update_position()
+
+ def resizeEvent(self, event):
+ super().resizeEvent(event)
+ self.update_position()
+
+
+class NiceCheckbox(QtWidgets.QFrame):
+ stateChanged = QtCore.Signal(int)
+ checked_bg_color = QtGui.QColor(69, 128, 86)
+ unchecked_bg_color = QtGui.QColor(170, 80, 80)
+
+ def set_bg_color(self, color):
+ self._bg_color = color
+ self.setStyleSheet(self._stylesheet_template.format(
+ color.red(), color.green(), color.blue()
+ ))
+
+ def bg_color(self):
+ return self._bg_color
+
+ bgcolor = QtCore.Property(QtGui.QColor, bg_color, set_bg_color)
+
+ def __init__(self, checked=True, height=30, *args, **kwargs):
+ super(NiceCheckbox, self).__init__(*args, **kwargs)
+
+ self._checkstate = checked
+ if checked:
+ bg_color = self.checked_bg_color
+ else:
+ bg_color = self.unchecked_bg_color
+
+ self.half_height = int(height / 2)
+ height = self.half_height * 2
+ tenth_height = int(height / 10)
+
+ self.setFixedHeight(height)
+ self.setFixedWidth((height - tenth_height) * 2)
+
+ move_item_size = height - (2 * tenth_height)
+
+ self.move_item = NiceCheckboxMoveWidget(
+ move_item_size, tenth_height, self
+ )
+ self.move_item.change_position(self._checkstate)
+
+ self._stylesheet_template = (
+ "border-radius: {}px;"
+ "border-width: {}px;"
+ "background: #333333;"
+ "border-style: solid;"
+ "border-color: #555555;"
+ ).format(self.half_height, tenth_height)
+ self._stylesheet_template += "background: rgb({},{},{});"
+
+ self.set_bg_color(bg_color)
+
+ def resizeEvent(self, event):
+ super(NiceCheckbox, self).resizeEvent(event)
+ self.move_item.update_position()
+
+ def show(self, *args, **kwargs):
+ super(NiceCheckbox, self).show(*args, **kwargs)
+ self.move_item.update_position()
+
+ def checkState(self):
+ if self._checkstate:
+ return QtCore.Qt.Checked
+ else:
+ return QtCore.Qt.Unchecked
+
+ def _on_checkstate_change(self):
+ self.stateChanged.emit(self.checkState())
+
+ move_start_value = self.move_item.pos()
+ offset = self.move_item.state_offset()
+ if self._checkstate is True:
+ move_end_value = move_start_value + offset
+ else:
+ move_end_value = move_start_value - offset
+ move_animation = QtCore.QPropertyAnimation(
+ self.move_item, b"pos", self
+ )
+ move_animation.setDuration(150)
+ move_animation.setEasingCurve(QtCore.QEasingCurve.OutQuad)
+ move_animation.setStartValue(move_start_value)
+ move_animation.setEndValue(move_end_value)
+
+ color_animation = QtCore.QPropertyAnimation(
+ self, b"bgcolor"
+ )
+ color_animation.setDuration(150)
+ if self._checkstate is True:
+ color_animation.setStartValue(self.unchecked_bg_color)
+ color_animation.setEndValue(self.checked_bg_color)
+ else:
+ color_animation.setStartValue(self.checked_bg_color)
+ color_animation.setEndValue(self.unchecked_bg_color)
+
+ anim_group = QtCore.QParallelAnimationGroup(self)
+ anim_group.addAnimation(move_animation)
+ anim_group.addAnimation(color_animation)
+
+ def _finished():
+ self.move_item.change_position(self._checkstate)
+
+ anim_group.finished.connect(_finished)
+ anim_group.start()
+
+ def isChecked(self):
+ return self._checkstate
+
+ def setChecked(self, checked):
+ if checked == self._checkstate:
+ return
+ self._checkstate = checked
+ self._on_checkstate_change()
+
+ def setCheckState(self, state=None):
+ if state is None:
+ checkstate = not self._checkstate
+ elif state == QtCore.Qt.Checked:
+ checkstate = True
+ elif state == QtCore.Qt.Unchecked:
+ checkstate = False
+ else:
+ return
+
+ if checkstate == self._checkstate:
+ return
+
+ self._checkstate = checkstate
+
+ self._on_checkstate_change()
+
+ def mouseReleaseEvent(self, event):
+ if event.button() == QtCore.Qt.LeftButton:
+ self.setCheckState()
+ event.accept()
+ return
+ return super(NiceCheckbox, self).mouseReleaseEvent(event)