diff --git a/pype/__init__.py b/pype/__init__.py
index 505db4c57f..9ca0380bf3 100644
--- a/pype/__init__.py
+++ b/pype/__init__.py
@@ -3,7 +3,7 @@ import os
from pyblish import api as pyblish
from avalon import api as avalon
from .lib import filter_pyblish_plugins
-from pypeapp import config, Roots
+from pypeapp import config, Anatomy
import logging
@@ -100,8 +100,9 @@ def install():
avalon.register_plugin_path(avalon.InventoryAction, path)
if project_name:
- root_obj = Roots(project_name)
- avalon.register_root(root_obj.roots)
+ anatomy = Anatomy(project_name)
+ anatomy.set_root_environments()
+ avalon.register_root(anatomy.roots)
# apply monkey patched discover to original one
avalon.discover = patched_discover
diff --git a/pype/avalon_apps/avalon_app.py b/pype/avalon_apps/avalon_app.py
index 35ab4c1eb7..d3190a9d53 100644
--- a/pype/avalon_apps/avalon_app.py
+++ b/pype/avalon_apps/avalon_app.py
@@ -45,10 +45,9 @@ class AvalonApps:
def show_launcher(self):
# if app_launcher don't exist create it/otherwise only show main window
if self.app_launcher is None:
- root = os.path.realpath(os.environ["AVALON_PROJECTS"])
io.install()
APP_PATH = launcher_lib.resource("qml", "main.qml")
- self.app_launcher = launcher_widget.Launcher(root, APP_PATH)
+ self.app_launcher = launcher_widget.Launcher(APP_PATH)
self.app_launcher.window.show()
def show_library_loader(self):
diff --git a/pype/ftrack/actions/action_delete_old_versions.py b/pype/ftrack/actions/action_delete_old_versions.py
index c13845f58c..30f786e93f 100644
--- a/pype/ftrack/actions/action_delete_old_versions.py
+++ b/pype/ftrack/actions/action_delete_old_versions.py
@@ -42,36 +42,8 @@ class DeleteOldVersions(BaseAction):
return False
def interface(self, session, entities, event):
+ # TODO Add roots existence validation
items = []
- root = os.environ.get("AVALON_PROJECTS")
- if not root:
- msg = "Root path to projects is not set."
- items.append({
- "type": "label",
- "value": "ERROR: {}".format(msg)
- })
- self.show_interface(
- items=items, title=self.inteface_title, event=event
- )
- return {
- "success": False,
- "message": msg
- }
-
- if not os.path.exists(root):
- msg = "Root path does not exists \"{}\".".format(str(root))
- items.append({
- "type": "label",
- "value": "ERROR: {}".format(msg)
- })
- self.show_interface(
- items=items, title=self.inteface_title, event=event
- )
- return {
- "success": False,
- "message": msg
- }
-
values = event["data"].get("values")
if values:
versions_count = int(values["last_versions_count"])
diff --git a/pype/ftrack/actions/action_delivery.py b/pype/ftrack/actions/action_delivery.py
index 9d686929de..23da81d383 100644
--- a/pype/ftrack/actions/action_delivery.py
+++ b/pype/ftrack/actions/action_delivery.py
@@ -340,7 +340,7 @@ class Delivery(BaseAction):
repre_path = self.path_from_represenation(repre, anatomy)
# TODO add backup solution where root of path from component
- # is repalced with AVALON_PROJECTS root
+ # is repalced with root
if not frame:
self.process_single_file(
repre_path, anatomy, anatomy_name, anatomy_data
diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py
index 163fcd27b7..a706753755 100644
--- a/pype/nuke/lib.py
+++ b/pype/nuke/lib.py
@@ -177,9 +177,16 @@ def format_anatomy(data):
log.debug("__ anatomy.templates: {}".format(anatomy.templates))
try:
- padding = int(anatomy.templates['render']['padding'])
+ # TODO: bck compatibility with old anatomy template
+ padding = int(
+ anatomy.templates["render"].get(
+ "frame_padding",
+ anatomy.templates["render"].get("padding")
+ )
+ )
except KeyError as e:
msg = ("`padding` key is not in `render` "
+ "or `frame_padding` on is not available in "
"Anatomy template. Please, add it there and restart "
"the pipeline (padding: \"4\"): `{}`").format(e)
@@ -973,7 +980,9 @@ class WorkfileSettings(object):
self.set_colorspace()
def set_favorites(self):
- projects_root = os.getenv("AVALON_PROJECTS")
+ anatomy = get_anatomy()
+ work_template = anatomy.templates["work"]["path"]
+ projects_root = anatomy.root_value_for_template(work_template)
work_dir = os.getenv("AVALON_WORKDIR")
asset = os.getenv("AVALON_ASSET")
project = os.getenv("AVALON_PROJECT")
diff --git a/pype/nukestudio/lib.py b/pype/nukestudio/lib.py
index 774a9d45bf..3a8e35f100 100644
--- a/pype/nukestudio/lib.py
+++ b/pype/nukestudio/lib.py
@@ -6,7 +6,7 @@ import pyblish.api
import avalon.api as avalon
from avalon.vendor.Qt import (QtWidgets, QtGui)
import pype.api as pype
-from pypeapp import Logger
+from pypeapp import Logger, Anatomy
log = Logger().get_logger(__name__, "nukestudio")
@@ -30,12 +30,17 @@ def set_workfiles():
# show workfile gui
workfiles.show(workdir)
+
def sync_avalon_data_to_workfile():
# import session to get project dir
- S = avalon.Session
- active_project_root = os.path.normpath(
- os.path.join(S['AVALON_PROJECTS'], S['AVALON_PROJECT'])
- )
+ project_name = avalon.Session["AVALON_PROJECT"]
+
+ anatomy = Anatomy(project_name)
+ work_template = anatomy.templates["work"]["path"]
+ work_root = anatomy.root_value_for_template(work_template)
+ active_project_root = (
+ os.path.join(work_root, project_name)
+ ).replace("\\", "/")
# getting project
project = hiero.core.projects()[-1]
@@ -350,17 +355,19 @@ def CreateNukeWorkfile(nodes=None,
# create root node and save all metadata
root_node = hiero.core.nuke.RootNode()
- root_path = os.environ["AVALON_PROJECTS"]
+ anatomy = Anatomy(os.environ["AVALON_PROJECT"])
+ work_template = anatomy.templates["work"]["path"]
+ root_path = anatomy.root_value_for_template(work_template)
nuke_script.addNode(root_node)
# here to call pype.nuke.lib.BuildWorkfile
script_builder = nklib.BuildWorkfile(
- root_node=root_node,
- root_path=root_path,
- nodes=nuke_script.getNodes(),
- **kwargs
- )
+ root_node=root_node,
+ root_path=root_path,
+ nodes=nuke_script.getNodes(),
+ **kwargs
+ )
class ClipLoader:
diff --git a/pype/plugins/adobecommunicator/publish/collect_context.py b/pype/plugins/adobecommunicator/publish/collect_context.py
index 139dd86480..6d05825844 100644
--- a/pype/plugins/adobecommunicator/publish/collect_context.py
+++ b/pype/plugins/adobecommunicator/publish/collect_context.py
@@ -39,10 +39,8 @@ class CollectContextDataFromAport(pyblish.api.ContextPlugin):
# get avalon session data and convert \ to /
_S = avalon.session
- projects = Path(_S["AVALON_PROJECTS"]).resolve()
asset = _S["AVALON_ASSET"]
workdir = Path(_S["AVALON_WORKDIR"]).resolve()
- _S["AVALON_PROJECTS"] = str(projects)
_S["AVALON_WORKDIR"] = str(workdir)
context.data["avalonSession"] = _S
diff --git a/pype/plugins/global/publish/collect_deadline_user.py b/pype/plugins/global/publish/collect_deadline_user.py
deleted file mode 100644
index 125f9d0d26..0000000000
--- a/pype/plugins/global/publish/collect_deadline_user.py
+++ /dev/null
@@ -1,64 +0,0 @@
-"""
-Requires:
- environment -> DEADLINE_PATH
-
-Provides:
- context -> deadlineUser (str)
-"""
-
-import os
-import subprocess
-
-import pyblish.api
-from pype.plugin import contextplugin_should_run
-
-CREATE_NO_WINDOW = 0x08000000
-
-
-def deadline_command(cmd):
- # Find Deadline
- path = os.environ.get("DEADLINE_PATH", None)
- assert path is not None, "Variable 'DEADLINE_PATH' must be set"
-
- executable = os.path.join(path, "deadlinecommand")
- if os.name == "nt":
- executable += ".exe"
- assert os.path.exists(
- executable), "Deadline executable not found at %s" % executable
- assert cmd, "Must have a command"
-
- query = (executable, cmd)
-
- process = subprocess.Popen(query, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- universal_newlines=True,
- creationflags=CREATE_NO_WINDOW)
- out, err = process.communicate()
-
- return out
-
-
-class CollectDeadlineUser(pyblish.api.ContextPlugin):
- """Retrieve the local active Deadline user"""
-
- order = pyblish.api.CollectorOrder + 0.499
- label = "Deadline User"
- hosts = ['maya', 'fusion']
- families = ["renderlayer", "saver.deadline"]
-
- def process(self, context):
- """Inject the current working file"""
-
- # Workaround bug pyblish-base#250
- if not contextplugin_should_run(self, context):
- return
-
- user = deadline_command("GetCurrentUserName").strip()
-
- if not user:
- self.log.warning("No Deadline user found. "
- "Do you have Deadline installed?")
- return
-
- self.log.info("Found Deadline user: {}".format(user))
- context.data['deadlineUser'] = user
diff --git a/pype/plugins/global/publish/integrate_master_version.py b/pype/plugins/global/publish/integrate_master_version.py
index e6e4247dd8..d82c3be075 100644
--- a/pype/plugins/global/publish/integrate_master_version.py
+++ b/pype/plugins/global/publish/integrate_master_version.py
@@ -356,8 +356,11 @@ class IntegrateMasterVersion(pyblish.api.InstancePlugin):
_anatomy_filled = anatomy.format(anatomy_data)
_template_filled = _anatomy_filled["master"]["path"]
head, tail = _template_filled.split(frame_splitter)
- padding = (
- anatomy.templates["render"]["padding"]
+ padding = int(
+ anatomy.templates["render"].get(
+ "frame_padding",
+ anatomy.templates["render"].get("padding")
+ )
)
dst_col = clique.Collection(
diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py
index b054d8c309..0cd46d8891 100644
--- a/pype/plugins/global/publish/integrate_new.py
+++ b/pype/plugins/global/publish/integrate_new.py
@@ -343,10 +343,11 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
index_frame_start = None
if repre.get("frameStart"):
- # TODO: bck compatibility `templates["render"]["padding"]`
- frame_start_padding = anatomy.templates.get(
- "frame_padding",
- anatomy.templates["render"].get("padding")
+ frame_start_padding = int(
+ anatomy.templates["render"].get(
+ "frame_padding",
+ anatomy.templates["render"].get("padding")
+ )
)
index_frame_start = int(repre.get("frameStart"))
@@ -656,7 +657,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
source = context.data["currentFile"]
anatomy = instance.context.data["anatomy"]
success, rootless_path = (
- anatomy.roots_obj.find_root_template_from_path(source)
+ anatomy.find_root_template_from_path(source)
)
if success:
source = rootless_path
diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py
index 9741a7135a..2f4e0a5ae1 100644
--- a/pype/plugins/global/publish/submit_publish_job.py
+++ b/pype/plugins/global/publish/submit_publish_job.py
@@ -195,7 +195,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
output_dir = instance.data["outputDir"]
# Convert output dir to `{root}/rest/of/path/...` with Anatomy
success, rootless_path = (
- self.anatomy.roots_obj.find_root_template_from_path(output_dir)
+ self.anatomy.find_root_template_from_path(output_dir)
)
if not success:
# `rootless_path` is not set to `output_dir` if none of roots match
@@ -262,7 +262,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
# self.log.info(json.dumps(payload, indent=4, sort_keys=True))
url = "{}/api/jobs".format(self.DEADLINE_REST_URL)
- response = requests.post(url, json=payload)
+ response = requests.post(url, json=payload, timeout=10)
if not response.ok:
raise Exception(response.text)
@@ -379,7 +379,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
staging = os.path.dirname(list(cols[0])[0])
success, rootless_staging_dir = (
- self.anatomy.roots_obj.find_root_template_from_path(staging)
+ self.anatomy.find_root_template_from_path(staging)
)
if success:
staging = rootless_staging_dir
@@ -471,7 +471,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
staging = os.path.dirname(list(collection)[0])
success, rootless_staging_dir = (
- self.anatomy.roots_obj.find_root_template_from_path(staging)
+ self.anatomy.find_root_template_from_path(staging)
)
if success:
staging = rootless_staging_dir
@@ -506,7 +506,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
staging = os.path.dirname(remainder)
success, rootless_staging_dir = (
- self.anatomy.roots_obj.find_root_template_from_path(staging)
+ self.anatomy.find_root_template_from_path(staging)
)
if success:
staging = rootless_staging_dir
@@ -619,7 +619,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
source = context.data["currentFile"]
success, rootless_path = (
- self.anatomy.roots_obj.find_root_template_from_path(source)
+ self.anatomy.find_root_template_from_path(source)
)
if success:
source = rootless_path
@@ -684,7 +684,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
staging_dir = repre.get("stagingDir")
if staging_dir:
success, rootless_staging_dir = (
- self.anatomy.roots_obj.find_root_template_from_path(
+ self.anatomy.find_root_template_from_path(
staging_dir
)
)
diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py
index b5e4cfe98c..c65f13c653 100644
--- a/pype/plugins/maya/publish/submit_maya_deadline.py
+++ b/pype/plugins/maya/publish/submit_maya_deadline.py
@@ -352,6 +352,8 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin):
"""
if 'verify' not in kwargs:
kwargs['verify'] = False if os.getenv("PYPE_DONT_VERIFY_SSL", True) else True # noqa
+ # add 10sec timeout before bailing out
+ kwargs['timeout'] = 10
return requests.post(*args, **kwargs)
def _requests_get(self, *args, **kwargs):
@@ -366,4 +368,6 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin):
"""
if 'verify' not in kwargs:
kwargs['verify'] = False if os.getenv("PYPE_DONT_VERIFY_SSL", True) else True # noqa
+ # add 10sec timeout before bailing out
+ kwargs['timeout'] = 10
return requests.get(*args, **kwargs)
diff --git a/pype/plugins/maya/publish/validate_ass_relative_paths.py b/pype/plugins/maya/publish/validate_ass_relative_paths.py
index b0fd12a550..b64e23e92c 100644
--- a/pype/plugins/maya/publish/validate_ass_relative_paths.py
+++ b/pype/plugins/maya/publish/validate_ass_relative_paths.py
@@ -37,50 +37,71 @@ class ValidateAssRelativePaths(pyblish.api.InstancePlugin):
scene_dir, scene_basename = os.path.split(cmds.file(q=True, loc=True))
scene_name, _ = os.path.splitext(scene_basename)
- project_root = "{}{}{}".format(
- os.environ.get("AVALON_PROJECTS"),
- os.path.sep,
- os.environ.get("AVALON_PROJECT")
- )
assert self.maya_is_true(relative_texture) is not True, \
("Texture path is set to be absolute")
assert self.maya_is_true(relative_procedural) is not True, \
("Procedural path is set to be absolute")
- texture_search_path = texture_search_path.replace("\\", "/")
- procedural_search_path = procedural_search_path.replace("\\", "/")
- project_root = project_root.replace("\\", "/")
+ anatomy = instance.context.data["anatomy"]
- assert project_root in texture_search_path, \
- ("Project root is not in texture_search_path")
- assert project_root in procedural_search_path, \
- ("Project root is not in procedural_search_path")
+ # Use project root variables for multiplatform support, see:
+ # https://docs.arnoldrenderer.com/display/A5AFMUG/Search+Path
+ # ':' as path separator is supported by Arnold for all platforms.
+ keys = anatomy.root_environments().keys()
+ paths = []
+ for k in keys:
+ paths.append("[{}]".format(k))
+
+ self.log.info("discovered roots: {}".format(":".join(paths)))
+
+ assert ":".join(paths) in texture_search_path, (
+ "Project roots are not in texture_search_path"
+ )
+
+ assert ":".join(paths) in procedural_search_path, (
+ "Project roots are not in procedural_search_path"
+ )
@classmethod
def repair(cls, instance):
- texture_search_path = cmds.getAttr(
- "defaultArnoldRenderOptions.tspath"
+ texture_path = cmds.getAttr("defaultArnoldRenderOptions.tspath")
+ procedural_path = cmds.getAttr("defaultArnoldRenderOptions.pspath")
+
+ # Use project root variables for multiplatform support, see:
+ # https://docs.arnoldrenderer.com/display/A5AFMUG/Search+Path
+ # ':' as path separator is supported by Arnold for all platforms.
+ anatomy = instance.context.data["anatomy"]
+ keys = anatomy.root_environments().keys()
+ paths = []
+ for k in keys:
+ paths.append("[{}]".format(k))
+
+ cmds.setAttr(
+ "defaultArnoldRenderOptions.tspath",
+ ":".join([p for p in paths + [texture_path] if p]),
+ type="string"
)
- procedural_search_path = cmds.getAttr(
- "defaultArnoldRenderOptions.pspath"
+ cmds.setAttr(
+ "defaultArnoldRenderOptions.absolute_texture_paths",
+ False
)
- project_root = "{}{}{}".format(
- os.environ.get("AVALON_PROJECTS"),
- os.path.sep,
- os.environ.get("AVALON_PROJECT"),
- ).replace("\\", "/")
+ cmds.setAttr(
+ "defaultArnoldRenderOptions.pspath",
+ ":".join([p for p in paths + [procedural_path] if p]),
+ type="string"
+ )
+ cmds.setAttr(
+ "defaultArnoldRenderOptions.absolute_procedural_paths",
+ False
+ )
- cmds.setAttr("defaultArnoldRenderOptions.tspath",
- project_root + os.pathsep + texture_search_path,
- type="string")
- cmds.setAttr("defaultArnoldRenderOptions.pspath",
- project_root + os.pathsep + procedural_search_path,
- type="string")
- cmds.setAttr("defaultArnoldRenderOptions.absolute_procedural_paths",
- False)
- cmds.setAttr("defaultArnoldRenderOptions.absolute_texture_paths",
- False)
+ @staticmethod
+ def find_absolute_path(relative_path, all_root_paths):
+ for root_path in all_root_paths:
+ possible_path = os.path.join(root_path, relative_path)
+ if os.path.exists(possible_path):
+ return possible_path
def maya_is_true(self, attr_val):
"""
diff --git a/pype/plugins/nuke/publish/submit_nuke_deadline.py b/pype/plugins/nuke/publish/submit_nuke_deadline.py
index 4b68056e09..6a1654f77e 100644
--- a/pype/plugins/nuke/publish/submit_nuke_deadline.py
+++ b/pype/plugins/nuke/publish/submit_nuke_deadline.py
@@ -251,7 +251,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin):
self.expected_files(instance, render_path)
self.log.debug("__ expectedFiles: `{}`".format(
instance.data["expectedFiles"]))
- response = requests.post(self.deadline_url, json=payload)
+ response = requests.post(self.deadline_url, json=payload, timeout=10)
if not response.ok:
raise Exception(response.text)
diff --git a/pype/plugins/nukestudio/publish/collect_project_root.py b/pype/plugins/nukestudio/publish/collect_project_root.py
deleted file mode 100644
index 1b21a6b641..0000000000
--- a/pype/plugins/nukestudio/publish/collect_project_root.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import pyblish.api
-import avalon.api as avalon
-import os
-
-class CollectActiveProjectRoot(pyblish.api.ContextPlugin):
- """Inject the active project into context"""
-
- label = "Collect Project Root"
- order = pyblish.api.CollectorOrder - 0.1
-
- def process(self, context):
- S = avalon.Session
- context.data["projectroot"] = os.path.normpath(
- os.path.join(S['AVALON_PROJECTS'], S['AVALON_PROJECT'])
- )
diff --git a/pype/services/adobe_communicator/lib/publish.py b/pype/services/adobe_communicator/lib/publish.py
index 2e7d993a60..a6fe991025 100644
--- a/pype/services/adobe_communicator/lib/publish.py
+++ b/pype/services/adobe_communicator/lib/publish.py
@@ -18,7 +18,7 @@ def main(env):
# Register Host (and it's pyblish plugins)
host_name = env["AVALON_APP"]
# TODO not sure if use "pype." or "avalon." for host import
- host_import_str = f"avalon.{host_name}"
+ host_import_str = f"pype.{host_name}"
try:
host_module = importlib.import_module(host_import_str)
diff --git a/res/app_icons/harmony.png b/res/app_icons/harmony.png
new file mode 100644
index 0000000000..f0f6c82c6e
Binary files /dev/null and b/res/app_icons/harmony.png differ
diff --git a/schema/session-2.0.json b/schema/session-2.0.json
index 006a9e2dbf..d37f2ac822 100644
--- a/schema/session-2.0.json
+++ b/schema/session-2.0.json
@@ -9,7 +9,6 @@
"additionalProperties": true,
"required": [
- "AVALON_PROJECTS",
"AVALON_PROJECT",
"AVALON_ASSET",
"AVALON_CONFIG"