diff --git a/openpype/modules/default_modules/royal_render/plugins/collect_sequences_from_job.py b/openpype/modules/default_modules/royal_render/plugins/collect_sequences_from_job.py deleted file mode 100644 index ac811696fa..0000000000 --- a/openpype/modules/default_modules/royal_render/plugins/collect_sequences_from_job.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -"""Collect sequences from Royal Render Job.""" -import rr # noqa -import rrGlobal # noqa \ No newline at end of file diff --git a/openpype/modules/default_modules/royal_render/plugins/collect_default_rr_path.py b/openpype/modules/default_modules/royal_render/plugins/publish/collect_default_rr_path.py similarity index 100% rename from openpype/modules/default_modules/royal_render/plugins/collect_default_rr_path.py rename to openpype/modules/default_modules/royal_render/plugins/publish/collect_default_rr_path.py diff --git a/openpype/modules/default_modules/royal_render/plugins/collect_rr_path_from_instance.py b/openpype/modules/default_modules/royal_render/plugins/publish/collect_rr_path_from_instance.py similarity index 96% rename from openpype/modules/default_modules/royal_render/plugins/collect_rr_path_from_instance.py rename to openpype/modules/default_modules/royal_render/plugins/publish/collect_rr_path_from_instance.py index 939b7c6e00..fb27a76d11 100644 --- a/openpype/modules/default_modules/royal_render/plugins/collect_rr_path_from_instance.py +++ b/openpype/modules/default_modules/royal_render/plugins/publish/collect_rr_path_from_instance.py @@ -6,7 +6,7 @@ class CollectRRPathFromInstance(pyblish.api.InstancePlugin): """Collect RR Path from instance.""" order = pyblish.api.CollectorOrder - label = "Deadline Webservice from the Instance" + label = "Royal Render Path from the Instance" families = ["rendering"] def process(self, instance): diff --git a/openpype/modules/default_modules/royal_render/plugins/publish/collect_sequences_from_job.py b/openpype/modules/default_modules/royal_render/plugins/publish/collect_sequences_from_job.py new file mode 100644 index 0000000000..2b0e35b3b8 --- /dev/null +++ b/openpype/modules/default_modules/royal_render/plugins/publish/collect_sequences_from_job.py @@ -0,0 +1,222 @@ +# -*- coding: utf-8 -*- +"""Collect sequences from Royal Render Job.""" +import os +import re +import copy +import json +from pprint import pformat + +import pyblish.api +from avalon import api + + +def collect(root, + regex=None, + exclude_regex=None, + frame_start=None, + frame_end=None): + """Collect sequence collections in root""" + + from avalon.vendor import clique + + files = [] + for filename in os.listdir(root): + + # Must have extension + ext = os.path.splitext(filename)[1] + if not ext: + continue + + # Only files + if not os.path.isfile(os.path.join(root, filename)): + continue + + # Include and exclude regex + if regex and not re.search(regex, filename): + continue + if exclude_regex and re.search(exclude_regex, filename): + continue + + files.append(filename) + + # Match collections + # Support filenames like: projectX_shot01_0010.tiff with this regex + pattern = r"(?P(?P0*)\d+)\.\D+\d?$" + collections, remainder = clique.assemble(files, + patterns=[pattern], + minimum_items=1) + + # Ignore any remainders + if remainder: + print("Skipping remainder {}".format(remainder)) + + # Exclude any frames outside start and end frame. + for collection in collections: + for index in list(collection.indexes): + if frame_start is not None and index < frame_start: + collection.indexes.discard(index) + continue + if frame_end is not None and index > frame_end: + collection.indexes.discard(index) + continue + + # Keep only collections that have at least a single frame + collections = [c for c in collections if c.indexes] + + return collections + + +class CollectSequencesFromJob(pyblish.api.ContextPlugin): + """Gather file sequences from working directory + + When "FILESEQUENCE" environment variable is set these paths (folders or + .json files) are parsed for image sequences. Otherwise the current + working directory is searched for file sequences. + + The json configuration may have the optional keys: + asset (str): The asset to publish to. If not provided fall back to + api.Session["AVALON_ASSET"] + subset (str): The subset to publish to. If not provided the sequence's + head (up to frame number) will be used. + frame_start (int): The start frame for the sequence + frame_end (int): The end frame for the sequence + root (str): The path to collect from (can be relative to the .json) + regex (str): A regex for the sequence filename + exclude_regex (str): A regex for filename to exclude from collection + metadata (dict): Custom metadata for instance.data["metadata"] + + """ + + order = pyblish.api.CollectorOrder + targets = ["rr_control"] + label = "Collect Rendered Frames" + + def process(self, context): + if os.environ.get("OPENPYPE_PUBLISH_DATA"): + self.log.debug(os.environ.get("OPENPYPE_PUBLISH_DATA")) + paths = os.environ["OPENPYPE_PUBLISH_DATA"].split(os.pathsep) + self.log.info("Collecting paths: {}".format(paths)) + else: + cwd = context.get("workspaceDir", os.getcwd()) + paths = [cwd] + + for path in paths: + + self.log.info("Loading: {}".format(path)) + + if path.endswith(".json"): + # Search using .json configuration + with open(path, "r") as f: + try: + data = json.load(f) + except Exception as exc: + self.log.error("Error loading json: " + "{} - Exception: {}".format(path, exc)) + raise + + cwd = os.path.dirname(path) + root_override = data.get("root") + if root_override: + if os.path.isabs(root_override): + root = root_override + else: + root = os.path.join(cwd, root_override) + else: + root = cwd + + metadata = data.get("metadata") + if metadata: + session = metadata.get("session") + if session: + self.log.info("setting session using metadata") + api.Session.update(session) + os.environ.update(session) + + else: + # Search in directory + data = {} + root = path + + self.log.info("Collecting: {}".format(root)) + regex = data.get("regex") + if regex: + self.log.info("Using regex: {}".format(regex)) + + collections = collect(root=root, + regex=regex, + exclude_regex=data.get("exclude_regex"), + frame_start=data.get("frameStart"), + frame_end=data.get("frameEnd")) + + self.log.info("Found collections: {}".format(collections)) + + if data.get("subset") and len(collections) > 1: + self.log.error("Forced subset can only work with a single " + "found sequence") + raise RuntimeError("Invalid sequence") + + fps = data.get("fps", 25) + + # Get family from the data + families = data.get("families", ["render"]) + if "render" not in families: + families.append("render") + if "ftrack" not in families: + families.append("ftrack") + if "review" not in families: + families.append("review") + + for collection in collections: + instance = context.create_instance(str(collection)) + self.log.info("Collection: %s" % list(collection)) + + # Ensure each instance gets a unique reference to the data + data = copy.deepcopy(data) + + # If no subset provided, get it from collection's head + subset = data.get("subset", collection.head.rstrip("_. ")) + + # If no start or end frame provided, get it from collection + indices = list(collection.indexes) + start = data.get("frameStart", indices[0]) + end = data.get("frameEnd", indices[-1]) + + # root = os.path.normpath(root) + # self.log.info("Source: {}}".format(data.get("source", ""))) + + ext = list(collection)[0].split('.')[-1] + + instance.data.update({ + "name": str(collection), + "family": families[0], # backwards compatibility / pyblish + "families": list(families), + "subset": subset, + "asset": data.get("asset", api.Session["AVALON_ASSET"]), + "stagingDir": root, + "frameStart": start, + "frameEnd": end, + "fps": fps, + "source": data.get('source', '') + }) + instance.append(collection) + instance.context.data['fps'] = fps + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + 'name': ext, + 'ext': '{}'.format(ext), + 'files': list(collection), + "stagingDir": root, + "anatomy_template": "render", + "fps": fps, + "tags": ['review'] + } + instance.data["representations"].append(representation) + + if data.get('user'): + context.data["user"] = data['user'] + + self.log.debug("Collected instance:\n" + "{}".format(pformat(instance.data))) diff --git a/openpype/modules/default_modules/royal_render/rr_root/plugins/control_job/perjob/m50__openpype_publish_render.py b/openpype/modules/default_modules/royal_render/rr_root/plugins/control_job/perjob/m50__openpype_publish_render.py index b993bd9e68..7e18695a7b 100644 --- a/openpype/modules/default_modules/royal_render/rr_root/plugins/control_job/perjob/m50__openpype_publish_render.py +++ b/openpype/modules/default_modules/royal_render/rr_root/plugins/control_job/perjob/m50__openpype_publish_render.py @@ -148,8 +148,12 @@ class OpenPypeContextSelector: print(">>> setting environment:") for k, v in env.items(): print(" {}: {}".format(k, v)) + args = [os.path.join(self.openpype_root, self.openpype_executable), - 'publish', '-t', "rr_control", "--gui", self.job.imageDir] + 'publish', '-t', "rr_control", "--gui", + os.path.join(self.job.imageDir, + os.path.dirname(self.job.imageFileName)) + ] print(">>> running {}".format(" ".join(args))) out = None diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index 53c842b7c4..8cc4b819ff 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -83,7 +83,10 @@ class PypeCommands: # Register target and host import pyblish.api import pyblish.util - from pprint import pprint + + log = Logger.get_logger() + + install() manager = ModulesManager() @@ -103,10 +106,6 @@ class PypeCommands: ) os.environ.update(env) - log = Logger.get_logger() - - install() - pyblish.api.register_host("shell") if targets: @@ -120,9 +119,6 @@ class PypeCommands: log.info("Running publish ...") - # Error exit as soon as any error occurs. - error_format = "Failed {plugin.__name__}: {error} -- {error.traceback}" - plugins = pyblish.api.discover() print("Using plugins:") for plugin in plugins: @@ -132,6 +128,10 @@ class PypeCommands: with qt_app_context(): show_publish() else: + # Error exit as soon as any error occurs. + error_format = ("Failed {plugin.__name__}: " + "{error} -- {error.traceback}") + for result in pyblish.util.publish_iter(): if result["error"]: log.error(error_format.format(**result)) @@ -139,7 +139,6 @@ class PypeCommands: sys.exit(1) log.info("Publish finished.") - # uninstall() @staticmethod def remotepublishfromapp(project, batch_dir, host, user, targets=None):