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"