diff --git a/pype/__init__.py b/pype/__init__.py index 5a65e01776..44721c3eaf 100644 --- a/pype/__init__.py +++ b/pype/__init__.py @@ -2,7 +2,6 @@ import os from pyblish import api as pyblish from avalon import api as avalon -from Qt import QtWidgets import logging log = logging.getLogger(__name__) @@ -25,20 +24,6 @@ def install(): pyblish.register_plugin_path(PUBLISH_PATH) avalon.register_plugin_path(avalon.Loader, LOAD_PATH) - # pyblish-qml settings. - try: - __import__("pyblish_qml") - except ImportError as e: - log.error("Could not load pyblish-qml: %s " % e) - else: - from pyblish_qml import settings - app = QtWidgets.QApplication.instance() - screen_resolution = app.desktop().screenGeometry() - width, height = screen_resolution.width(), screen_resolution.height() - settings.WindowSize = (width / 3, height * 0.75) - settings.WindowPosition = (0, 0) - - def uninstall(): log.info("Deregistering global plug-ins..") pyblish.deregister_plugin_path(PUBLISH_PATH) diff --git a/pype/api.py b/pype/api.py index 1ab7a91955..2227236fd3 100644 --- a/pype/api.py +++ b/pype/api.py @@ -5,7 +5,8 @@ from .plugin import ( ValidatePipelineOrder, ValidateContentsOrder, ValidateSceneOrder, - ValidateMeshOrder + ValidateMeshOrder, + ValidationException ) # temporary fix, might @@ -62,6 +63,8 @@ __all__ = [ "Logger", + "ValidationException", + # contectual templates # get data to preloaded templates "load_data_from_templates", diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index 9bc390eaa5..4d96e6b772 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -24,6 +24,7 @@ for path in sys.path: log.info("_ removing from sys.path: `{}`".format(path)) sys.path.remove(path) + def onScriptLoad(): if nuke.env['LINUX']: nuke.tcl('load ffmpegReader') @@ -37,12 +38,12 @@ def checkInventoryVersions(): """ Actiual version idetifier of Loaded containers - Any time this function is run it will check all nodes and filter only Loader nodes for its version. It will get all versions from database - and check if the node is having actual version. If not then it will color it to red. - + Any time this function is run it will check all nodes and filter only + Loader nodes for its version. It will get all versions from database + and check if the node is having actual version. If not then it will color + it to red. """ - # get all Loader nodes by avalon attribute metadata for each in nuke.allNodes(): if each.Class() == 'Read': @@ -195,12 +196,17 @@ def create_write_node(name, data): except Exception as e: log.error("problem with resolving anatomy tepmlate: {}".format(e)) - fpath = str(anatomy_filled["render"]["path"]).replace("\\", "/") + # build file path to workfiles + fpath = str(anatomy_filled["work"]["folder"]).replace("\\", "/") + fpath = '{work}/renders/v{version}/{subset}.{frame}.{ext}'.format( + work=fpath, version=data["version"], subset=data["subset"], + frame=data["frame"], + ext=data["nuke_dataflow_writes"]["file_type"]) # create directory if not os.path.isdir(os.path.dirname(fpath)): log.info("path does not exist") - os.makedirs(os.path.dirname(fpath), 0766) + os.makedirs(os.path.dirname(fpath), 0o766) _data = OrderedDict({ "file": fpath diff --git a/pype/plugin.py b/pype/plugin.py index cfcd814c92..c77b9927e1 100644 --- a/pype/plugin.py +++ b/pype/plugin.py @@ -69,3 +69,7 @@ def contextplugin_should_run(plugin, context): return True return False + + +class ValidationException(Exception): + pass diff --git a/pype/plugins/ftrack/publish/integrate_ftrack_instances.py b/pype/plugins/ftrack/publish/integrate_ftrack_instances.py index 3c8ab3d1d4..d351289dfe 100644 --- a/pype/plugins/ftrack/publish/integrate_ftrack_instances.py +++ b/pype/plugins/ftrack/publish/integrate_ftrack_instances.py @@ -22,9 +22,9 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): 'rig': 'rig', 'setdress': 'setdress', 'pointcache': 'cache', - 'write': 'img', 'render': 'render', 'nukescript': 'comp', + 'write': 'render', 'review': 'mov', 'plate': 'img' } diff --git a/pype/plugins/global/publish/collect_assumed_destination.py b/pype/plugins/global/publish/collect_assumed_destination.py deleted file mode 100644 index e91093f4db..0000000000 --- a/pype/plugins/global/publish/collect_assumed_destination.py +++ /dev/null @@ -1,148 +0,0 @@ -import os -import pyblish.api - -from avalon import io, api - - -class CollectAssumedDestination(pyblish.api.ContextPlugin): - """Generate the assumed destination path where the file will be stored""" - - label = "Collect Assumed Destination" - order = pyblish.api.CollectorOrder + 0.498 - exclude_families = ["plate"] - - def process(self, context): - - for instance in context: - if [ef for ef in self.exclude_families - if ef in instance.data["family"]]: - self.log.info("Ignoring instance: {}".format(instance)) - return - self.process_item(instance) - - def process_item(self, instance): - - self.create_destination_template(instance) - - template_data = instance.data["assumedTemplateData"] - - anatomy = instance.context.data['anatomy'] - # self.log.info(anatomy.anatomy()) - self.log.info(anatomy.templates) - # template = anatomy.publish.path - anatomy_filled = anatomy.format(template_data) - self.log.info(anatomy_filled) - mock_template = anatomy_filled["publish"]["path"] - - # For now assume resources end up in a "resources" folder in the - # published folder - mock_destination = os.path.join(os.path.dirname(mock_template), - "resources") - - # Clean the path - mock_destination = os.path.abspath(os.path.normpath(mock_destination)) - - # Define resource destination and transfers - resources = instance.data.get("resources", list()) - transfers = instance.data.get("transfers", list()) - for resource in resources: - - # Add destination to the resource - source_filename = os.path.basename(resource["source"]) - destination = os.path.join(mock_destination, source_filename) - - # Force forward slashes to fix issue with software unable - # to work correctly with backslashes in specific scenarios - # (e.g. escape characters in PLN-151 V-Ray UDIM) - destination = destination.replace("\\", "/") - - resource['destination'] = destination - - # Collect transfers for the individual files of the resource - # e.g. all individual files of a cache or UDIM textures. - files = resource['files'] - for fsrc in files: - fname = os.path.basename(fsrc) - fdest = os.path.join(mock_destination, fname) - transfers.append([fsrc, fdest]) - - instance.data["resources"] = resources - instance.data["transfers"] = transfers - - def create_destination_template(self, instance): - """Create a filepath based on the current data available - - Example template: - {root}/{project}/{silo}/{asset}/publish/{subset}/v{version:0>3}/ - {subset}.{representation} - Args: - instance: the instance to publish - - Returns: - file path (str) - """ - if [ef for ef in self.exclude_families - if instance.data["family"] in ef]: - return - - # get all the stuff from the database - subset_name = instance.data["subset"] - asset_name = instance.data["asset"] - project_name = api.Session["AVALON_PROJECT"] - - # FIXME: io is not initialized at this point for shell host - io.install() - project = io.find_one({"type": "project", - "name": project_name}, - projection={"config": True, "data": True}) - - template = project["config"]["template"]["publish"] - anatomy = instance.context.data['anatomy'] - - asset = io.find_one({"type": "asset", - "name": asset_name, - "parent": project["_id"]}) - - assert asset, ("No asset found by the name '{}' " - "in project '{}'".format(asset_name, project_name)) - silo = asset['silo'] - - subset = io.find_one({"type": "subset", - "name": subset_name, - "parent": asset["_id"]}) - - # assume there is no version yet, we start at `1` - version = None - version_number = 1 - if subset is not None: - version = io.find_one({"type": "version", - "parent": subset["_id"]}, - sort=[("name", -1)]) - - # if there is a subset there ought to be version - if version is not None: - version_number += int(version["name"]) - - hierarchy = asset['data']['parents'] - if hierarchy: - # hierarchy = os.path.sep.join(hierarchy) - hierarchy = os.path.join(*hierarchy) - - template_data = {"root": api.Session["AVALON_PROJECTS"], - "project": {"name": project_name, - "code": project['data']['code']}, - "silo": silo, - "family": instance.data['family'], - "asset": asset_name, - "subset": subset_name, - "version": version_number, - "hierarchy": hierarchy, - "representation": "TEMP"} - - instance.data["template"] = template - instance.data["assumedTemplateData"] = template_data - - # We take the parent folder of representation 'filepath' - instance.data["assumedDestination"] = os.path.dirname( - (anatomy.format(template_data))["publish"]["path"] - ) diff --git a/pype/plugins/global/publish/collect_filesequences.py b/pype/plugins/global/publish/collect_filesequences.py index 5267da04f4..ad128c099b 100644 --- a/pype/plugins/global/publish/collect_filesequences.py +++ b/pype/plugins/global/publish/collect_filesequences.py @@ -204,7 +204,8 @@ class CollectFileSequences(pyblish.api.ContextPlugin): 'ext': '{}'.format(ext), 'files': list(collection), "stagingDir": root, - "anatomy_template": "render" + "anatomy_template": "render", + "frameRate": fps } instance.data["representations"].append(representation) diff --git a/pype/plugins/global/publish/collect_presets.py b/pype/plugins/global/publish/collect_presets.py index 312b6b008a..7e0d3e2f4b 100644 --- a/pype/plugins/global/publish/collect_presets.py +++ b/pype/plugins/global/publish/collect_presets.py @@ -27,5 +27,5 @@ class CollectPresets(api.ContextPlugin): context.data["presets"] = presets - self.log.info(context.data["presets"]) + # self.log.info(context.data["presets"]) return diff --git a/pype/plugins/global/publish/extract_quicktime.py b/pype/plugins/global/publish/extract_quicktime.py index 9fbed51fe7..ccbbb24634 100644 --- a/pype/plugins/global/publish/extract_quicktime.py +++ b/pype/plugins/global/publish/extract_quicktime.py @@ -70,6 +70,9 @@ class ExtractQuicktimeEXR(pyblish.api.InstancePlugin): sub_proc = subprocess.Popen(subprocess_mov) sub_proc.wait() + if not os.path.isfile(full_output_path): + raise("Quicktime wasn't created succesfully") + if "representations" not in instance.data: instance.data["representations"] = [] diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 359bb9afe7..f96fb240c9 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -60,7 +60,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "nukescript", "render", "rendersetup", - "write", "rig", "plate", "look" diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 1b795157d7..992553cc7e 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -1,6 +1,8 @@ import os import json import re +from pprint import pprint +import logging from avalon import api, io from avalon.vendor import requests, clique @@ -215,7 +217,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if not job: # No deadline job. Try Muster: musterSubmissionJob - job = instance.data.get("musterSubmissionJob") + job = data.pop("musterSubmissionJob") submission_type = "muster" if not job: raise RuntimeError("Can't continue without valid Deadline " @@ -249,7 +251,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # This assumes the output files start with subset name and ends with # a file extension. The "ext" key includes the dot with the extension. if "ext" in instance.data: - ext = re.escape(instance.data["ext"]) + ext = r"\." + re.escape(instance.data["ext"]) else: ext = r"\.\D+" @@ -362,7 +364,18 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): metadata["metadata"]["instance"]["endFrame"] = updated_end metadata_filename = "{}_metadata.json".format(subset) + metadata_path = os.path.join(output_dir, metadata_filename) + # convert log messages if they are `LogRecord` to their + # string format to allow serializing as JSON later on. + rendered_logs = [] + for log in metadata["metadata"]["instance"].get("_log", []): + if isinstance(log, logging.LogRecord): + rendered_logs.append(log.getMessage()) + else: + rendered_logs.append(log) + + metadata["metadata"]["instance"]["_log"] = rendered_logs with open(metadata_path, "w") as f: json.dump(metadata, f, indent=4, sort_keys=True) diff --git a/pype/plugins/nuke/create/create_write.py b/pype/plugins/nuke/create/create_write.py index b3c9117641..347a4a1fd2 100644 --- a/pype/plugins/nuke/create/create_write.py +++ b/pype/plugins/nuke/create/create_write.py @@ -19,7 +19,7 @@ def subset_to_families(subset, family, families): return "{}.{}".format(family, new_subset) -class CrateWriteRender(avalon.nuke.Creator): +class CreateWriteRender(avalon.nuke.Creator): # change this to template preset preset = "render" @@ -31,7 +31,7 @@ class CrateWriteRender(avalon.nuke.Creator): icon = "sign-out" def __init__(self, *args, **kwargs): - super(CrateWriteRender, self).__init__(*args, **kwargs) + super(CreateWriteRender, self).__init__(*args, **kwargs) data = OrderedDict() @@ -62,7 +62,7 @@ class CrateWriteRender(avalon.nuke.Creator): return -class CrateWritePrerender(avalon.nuke.Creator): +class CreateWritePrerender(avalon.nuke.Creator): # change this to template preset preset = "prerender" @@ -74,7 +74,7 @@ class CrateWritePrerender(avalon.nuke.Creator): icon = "sign-out" def __init__(self, *args, **kwargs): - super(CrateWritePrerender, self).__init__(*args, **kwargs) + super(CreateWritePrerender, self).__init__(*args, **kwargs) data = OrderedDict() @@ -89,8 +89,6 @@ class CrateWritePrerender(avalon.nuke.Creator): self.name = self.data["subset"] instance = nuke.toNode(self.data["subset"]) - - family = self.family node = 'write' if not instance: @@ -103,51 +101,53 @@ class CrateWritePrerender(avalon.nuke.Creator): create_write_node(self.data["subset"], write_data) return -# -# -# class CrateWriteStill(avalon.nuke.Creator): -# # change this to template preset -# preset = "still" -# -# name = "WriteStill" -# label = "Create Write Still" -# hosts = ["nuke"] -# family = "{}_write".format(preset) -# families = preset -# icon = "image" -# -# def __init__(self, *args, **kwargs): -# super(CrateWriteStill, self).__init__(*args, **kwargs) -# -# data = OrderedDict() -# -# data["family"] = self.family.split("_")[-1] -# data["families"] = self.families -# -# {data.update({k: v}) for k, v in self.data.items() -# if k not in data.keys()} -# self.data = data -# -# def process(self): -# self.name = self.data["subset"] -# -# node_name = self.data["subset"].replace( -# "_", "_f{}_".format(nuke.frame())) -# instance = nuke.toNode(self.data["subset"]) -# self.data["subset"] = node_name -# -# family = self.family -# node = 'write' -# -# if not instance: -# write_data = { -# "frame_range": [nuke.frame(), nuke.frame()], -# "class": node, -# "preset": self.preset, -# "avalon": self.data -# } -# -# nuke.createNode("FrameHold", "first_frame {}".format(nuke.frame())) -# create_write_node(node_name, write_data) -# -# return + + +""" +class CrateWriteStill(avalon.nuke.Creator): + # change this to template preset + preset = "still" + + name = "WriteStill" + label = "Create Write Still" + hosts = ["nuke"] + family = "{}_write".format(preset) + families = preset + icon = "image" + + def __init__(self, *args, **kwargs): + super(CrateWriteStill, self).__init__(*args, **kwargs) + + data = OrderedDict() + + data["family"] = self.family.split("_")[-1] + data["families"] = self.families + + {data.update({k: v}) for k, v in self.data.items() + if k not in data.keys()} + self.data = data + + def process(self): + self.name = self.data["subset"] + + node_name = self.data["subset"].replace( + "_", "_f{}_".format(nuke.frame())) + instance = nuke.toNode(self.data["subset"]) + self.data["subset"] = node_name + + family = self.family + node = 'write' + + if not instance: + write_data = { + "frame_range": [nuke.frame(), nuke.frame()], + "class": node, + "preset": self.preset, + "avalon": self.data + } + + nuke.createNode("FrameHold", "first_frame {}".format(nuke.frame())) + create_write_node(node_name, write_data) + + return +""" diff --git a/pype/plugins/nuke/publish/collect_asset_info.py b/pype/plugins/nuke/publish/collect_asset_info.py new file mode 100644 index 0000000000..ae49c6e86f --- /dev/null +++ b/pype/plugins/nuke/publish/collect_asset_info.py @@ -0,0 +1,21 @@ +import nuke +from avalon import api, io +import pyblish.api + + +class CollectAssetInfo(pyblish.api.ContextPlugin): + """Collect framerate.""" + + order = pyblish.api.CollectorOrder + label = "Collect Asset Info" + hosts = [ + "nuke", + "nukeassist" + ] + + def process(self, context): + asset_data = io.find_one({"type": "asset", + "name": api.Session["AVALON_ASSET"]}) + self.log.info("asset_data: {}".format(asset_data)) + + context.data['handles'] = int(asset_data["data"].get("handles", 0)) diff --git a/pype/plugins/nuke/publish/collect_families.py b/pype/plugins/nuke/publish/collect_families.py deleted file mode 100644 index d7515f91ca..0000000000 --- a/pype/plugins/nuke/publish/collect_families.py +++ /dev/null @@ -1,46 +0,0 @@ -import pyblish.api - - -@pyblish.api.log -class CollectInstanceFamilies(pyblish.api.InstancePlugin): - """Collect families for all instances""" - - order = pyblish.api.CollectorOrder + 0.2 - label = "Collect Families" - hosts = ["nuke", "nukeassist"] - families = ['write'] - - def process(self, instance): - - node = instance[0] - - self.log.info('processing {}'.format(node)) - - families = [] - if instance.data.get('families'): - families += instance.data['families'] - - # set for ftrack to accept - # instance.data["families"] = ["ftrack"] - - if node["render"].value(): - # dealing with local/farm rendering - if node["render_farm"].value(): - families.append("render.farm") - else: - families.append("render.local") - else: - families.append("render.frames") - # to ignore staging dir op in integrate - instance.data['transfer'] = False - - families.append('ftrack') - - instance.data["families"] = families - - # Sort/grouped by family (preserving local index) - instance.context[:] = sorted(instance.context, key=self.sort_by_family) - - def sort_by_family(self, instance): - """Sort by family""" - return instance.data.get("families", instance.data.get("family")) diff --git a/pype/plugins/nuke/publish/collect_framerate.py b/pype/plugins/nuke/publish/collect_framerate.py index e12933fbe4..980ec22872 100644 --- a/pype/plugins/nuke/publish/collect_framerate.py +++ b/pype/plugins/nuke/publish/collect_framerate.py @@ -15,3 +15,4 @@ class CollectFramerate(pyblish.api.ContextPlugin): def process(self, context): context.data["framerate"] = nuke.root()["fps"].getValue() + context.data["fps"] = nuke.root()["fps"].getValue() diff --git a/pype/plugins/nuke/publish/collect_instances.py b/pype/plugins/nuke/publish/collect_instances.py index e9db556a9f..7f119f9a1e 100644 --- a/pype/plugins/nuke/publish/collect_instances.py +++ b/pype/plugins/nuke/publish/collect_instances.py @@ -19,7 +19,7 @@ class CollectNukeInstances(pyblish.api.ContextPlugin): "name": api.Session["AVALON_ASSET"]}) # add handles into context - context.data['handles'] = int(asset_data["data"].get("handles", 0)) + context.data['handles'] = context.data['handles'] self.log.debug("asset_data: {}".format(asset_data["data"])) instances = [] @@ -40,12 +40,23 @@ class CollectNukeInstances(pyblish.api.ContextPlugin): if avalon_knob_data["id"] != "pyblish.avalon.instance": continue - subset = avalon_knob_data.get("subset", None) or node["name"].value() + subset = avalon_knob_data.get( + "subset", None) or node["name"].value() # Create instance instance = context.create_instance(subset) instance.add(node) + family = avalon_knob_data["families"] + if node["render"].value(): + self.log.info("flagged for render") + family = "render.local" + # dealing with local/farm rendering + if node["render_farm"].value(): + self.log.info("adding render farm family") + family = "render.farm" + instance.data['transfer'] = False + instance.data.update({ "subset": subset, "asset": os.environ["AVALON_ASSET"], @@ -53,14 +64,14 @@ class CollectNukeInstances(pyblish.api.ContextPlugin): "name": node.name(), "subset": subset, "family": avalon_knob_data["family"], + "families": [family], "avalonKnob": avalon_knob_data, "publish": node.knob('publish').value(), "step": 1, "fps": int(nuke.root()['fps'].value()) }) - # if node.Class() == "Write": - # instance.data["families"] = [avalon_knob_data["families"]] + self.log.info("collected instance: {}".format(instance.data)) instances.append(instance) diff --git a/pype/plugins/nuke/publish/collect_review.py b/pype/plugins/nuke/publish/collect_review.py index f75c675b8f..0ab5424434 100644 --- a/pype/plugins/nuke/publish/collect_review.py +++ b/pype/plugins/nuke/publish/collect_review.py @@ -9,21 +9,9 @@ class CollectReview(pyblish.api.InstancePlugin): family = "review" label = "Collect Review" hosts = ["nuke"] - families = ["write"] - - family_targets = [".local", ".frames"] + families = ["render", "render.local"] def process(self, instance): - pass - families = [(f, search) for f in instance.data["families"] - for search in self.family_targets - if search in f][0] - - if families: - root_families = families[0].replace(families[1], "") - # instance.data["families"].append(".".join([ - # root_families, - # self.family - # ])) - instance.data["families"].append("render.review") + if instance.data["families"]: + instance.data["families"].append("review") self.log.info("Review collected: `{}`".format(instance)) diff --git a/pype/plugins/nuke/publish/collect_script.py b/pype/plugins/nuke/publish/collect_script.py index f0c917b449..d2585e4421 100644 --- a/pype/plugins/nuke/publish/collect_script.py +++ b/pype/plugins/nuke/publish/collect_script.py @@ -16,10 +16,6 @@ class CollectScript(pyblish.api.ContextPlugin): hosts = ['nuke'] def process(self, context): - asset_data = io.find_one({"type": "asset", - "name": api.Session["AVALON_ASSET"]}) - self.log.info("asset_data: {}".format(asset_data["data"])) - root = nuke.root() add_avalon_tab_knob(root) add_publish_knob(root) @@ -57,7 +53,7 @@ class CollectScript(pyblish.api.ContextPlugin): "publish": root.knob('publish').value(), "family": family, "representation": "nk", - "handles": int(asset_data["data"].get("handles", 0)), + "handles": context.data['handles'], "step": 1, "fps": int(root['fps'].value()), }) diff --git a/pype/plugins/nuke/publish/collect_writes.py b/pype/plugins/nuke/publish/collect_writes.py index 68bc2fd5d4..2dae39a1fc 100644 --- a/pype/plugins/nuke/publish/collect_writes.py +++ b/pype/plugins/nuke/publish/collect_writes.py @@ -4,65 +4,66 @@ import pyblish.api import pype.api as pype - @pyblish.api.log -class CollectNukeWrites(pyblish.api.ContextPlugin): +class CollectNukeWrites(pyblish.api.InstancePlugin): """Collect all write nodes.""" order = pyblish.api.CollectorOrder + 0.1 label = "Collect Writes" hosts = ["nuke", "nukeassist"] + families = ["render.local", "render", "render.farm"] - def process(self, context): - for instance in context.data["instances"]: + def process(self, instance): - if not instance.data["publish"]: - continue + # if not instance.data["publish"]: + # continue - node = instance[0] + node = instance[0] - if node.Class() != "Write": - continue + if node.Class() != "Write": + return - self.log.debug("checking instance: {}".format(instance)) + self.log.debug("checking instance: {}".format(instance)) - # Determine defined file type - ext = node["file_type"].value() + # Determine defined file type + ext = node["file_type"].value() - # Determine output type - output_type = "img" - if ext == "mov": - output_type = "mov" + # Determine output type + output_type = "img" + if ext == "mov": + output_type = "mov" - # Get frame range - handles = instance.context.data.get('handles', 0) - first_frame = int(nuke.root()["first_frame"].getValue()) - last_frame = int(nuke.root()["last_frame"].getValue()) + # Get frame range + handles = instance.context.data.get('handles', 0) + first_frame = int(nuke.root()["first_frame"].getValue()) + last_frame = int(nuke.root()["last_frame"].getValue()) - if node["use_limit"].getValue(): - handles = 0 - first_frame = int(node["first"].getValue()) - last_frame = int(node["last"].getValue()) + if node["use_limit"].getValue(): + handles = 0 + first_frame = int(node["first"].getValue()) + last_frame = int(node["last"].getValue()) - # get path - path = nuke.filename(node) - output_dir = os.path.dirname(path) - self.log.debug('output dir: {}'.format(output_dir)) + # get path + path = nuke.filename(node) + output_dir = os.path.dirname(path) + self.log.debug('output dir: {}'.format(output_dir)) - # get version - version = pype.get_version_from_path(path) - instance.data['version'] = version - self.log.debug('Write Version: %s' % instance.data('version')) + # get version + version = pype.get_version_from_path(nuke.root().name()) + instance.data['version'] = version + self.log.debug('Write Version: %s' % instance.data('version')) - # create label - name = node.name() - # Include start and end render frame in label - label = "{0} ({1}-{2})".format( - name, - int(first_frame), - int(last_frame) - ) + # create label + name = node.name() + # Include start and end render frame in label + label = "{0} ({1}-{2})".format( + name, + int(first_frame), + int(last_frame) + ) + if 'render' in instance.data['families']: + instance.data['families'].append('ftrack') if "representations" not in instance.data: instance.data["representations"] = list() try: @@ -80,23 +81,20 @@ class CollectNukeWrites(pyblish.api.ContextPlugin): except Exception: self.log.debug("couldn't collect frames: {}".format(label)) + if 'render.local' in instance.data['families']: + instance.data['families'].append('ftrack') + + instance.data.update({ + "path": path, + "outputDir": output_dir, + "ext": ext, + "label": label, + "handles": handles, + "startFrame": first_frame, + "endFrame": last_frame, + "outputType": output_type, + "colorspace": node["colorspace"].value(), + }) - # except Exception: - # self.log.debug("couldn't collect frames: {}".format(label)) - - instance.data.update({ - "path": path, - "outputDir": output_dir, - "ext": ext, - "label": label, - "handles": handles, - "startFrame": first_frame, - "endFrame": last_frame, - "outputType": output_type, - "colorspace": node["colorspace"].value(), - }) - - self.log.debug("instance.data: {}".format(instance.data)) - - self.log.debug("context: {}".format(context)) + self.log.debug("instance.data: {}".format(instance.data)) diff --git a/pype/plugins/nuke/publish/extract_frames.py b/pype/plugins/nuke/publish/extract_frames.py index bdbcb75cea..b75f893802 100644 --- a/pype/plugins/nuke/publish/extract_frames.py +++ b/pype/plugins/nuke/publish/extract_frames.py @@ -8,14 +8,15 @@ class ExtractFramesToIntegrate(pyblish.api.InstancePlugin): order = pyblish.api.ExtractorOrder label = "Extract rendered frames" hosts = ["nuke"] - families = ["render.frames", "prerender.frames", "still.frames"] + families = ["render"] - def process(self, instance): + def process(self, instance\ + return - staging_dir = instance.data.get('stagingDir', None) - output_dir = instance.data.get('outputDir', None) - - if not staging_dir: - staging_dir = output_dir - instance.data['stagingDir'] = staging_dir - instance.data['transfer'] = False + # staging_dir = instance.data.get('stagingDir', None) + # output_dir = instance.data.get('outputDir', None) + # + # if not staging_dir: + # staging_dir = output_dir + # instance.data['stagingDir'] = staging_dir + # # instance.data['transfer'] = False diff --git a/pype/plugins/nuke/_publish_unused/extract_post_json.py b/pype/plugins/nuke/publish/extract_post_json.py similarity index 75% rename from pype/plugins/nuke/_publish_unused/extract_post_json.py rename to pype/plugins/nuke/publish/extract_post_json.py index 6954abff3d..fe42781d52 100644 --- a/pype/plugins/nuke/_publish_unused/extract_post_json.py +++ b/pype/plugins/nuke/publish/extract_post_json.py @@ -4,6 +4,7 @@ import datetime import time import clique +from pprint import pformat import pyblish.api @@ -23,35 +24,18 @@ class ExtractJSON(pyblish.api.ContextPlugin): os.makedirs(workspace) context_data = context.data.copy() - out_data = dict(self.serialize(context_data)) + unwrapped_instance = [] + for i in context_data["instances"]: + unwrapped_instance.append(i.data) - instances_data = [] - for instance in context: - - data = {} - for key, value in instance.data.items(): - if isinstance(value, clique.Collection): - value = value.format() - - try: - json.dumps(value) - data[key] = value - except KeyError: - msg = "\"{0}\"".format(value) - msg += " in instance.data[\"{0}\"]".format(key) - msg += " could not be serialized." - self.log.debug(msg) - - instances_data.append(data) - - out_data["instances"] = instances_data + context_data["instances"] = unwrapped_instance timestamp = datetime.datetime.fromtimestamp( time.time()).strftime("%Y%m%d-%H%M%S") filename = timestamp + "_instances.json" with open(os.path.join(workspace, filename), "w") as outfile: - outfile.write(json.dumps(out_data, indent=4, sort_keys=True)) + outfile.write(pformat(context_data, depth=20)) def serialize(self, data): """ diff --git a/pype/plugins/nuke/publish/extract_render_local.py b/pype/plugins/nuke/publish/extract_render_local.py index 18a1aa1f54..f424bf1200 100644 --- a/pype/plugins/nuke/publish/extract_render_local.py +++ b/pype/plugins/nuke/publish/extract_render_local.py @@ -69,6 +69,9 @@ class NukeRenderLocal(pype.api.Extractor): temp_dir )) + instance.data['family'] = 'render' + instance.data['families'].append('render') + collections, remainder = clique.assemble(collected_frames) self.log.info('collections: {}'.format(str(collections))) diff --git a/pype/plugins/nuke/publish/extract_review.py b/pype/plugins/nuke/publish/extract_review.py index 2eabbd4e87..bdbd3d17a6 100644 --- a/pype/plugins/nuke/publish/extract_review.py +++ b/pype/plugins/nuke/publish/extract_review.py @@ -16,7 +16,7 @@ class ExtractDataForReview(pype.api.Extractor): label = "Extract Review" optional = True - families = ["render.review"] + families = ["review"] hosts = ["nuke"] def process(self, instance): @@ -79,7 +79,7 @@ class ExtractDataForReview(pype.api.Extractor): instance.data["representations"] = [] representation = { - 'name': 'mov', + 'name': 'review', 'ext': 'mov', 'files': file_name, "stagingDir": stagingDir, @@ -100,7 +100,7 @@ class ExtractDataForReview(pype.api.Extractor): import nuke temporary_nodes = [] - stagingDir = instance.data["stagingDir"].replace("\\", "/") + stagingDir = instance.data['representations'][0]["stagingDir"].replace("\\", "/") self.log.debug("StagingDir `{0}`...".format(stagingDir)) collection = instance.data.get("collection", None) diff --git a/pype/plugins/nuke/publish/submit_nuke_deadline.py b/pype/plugins/nuke/publish/submit_nuke_deadline.py new file mode 100644 index 0000000000..3d854f66e9 --- /dev/null +++ b/pype/plugins/nuke/publish/submit_nuke_deadline.py @@ -0,0 +1,208 @@ +import os +import json +import getpass + +import nuke + +from avalon import api +from avalon.vendor import requests +import re + +import pyblish.api + + +class NukeSubmitDeadline(pyblish.api.InstancePlugin): + """Submit write to Deadline + + Renders are submitted to a Deadline Web Service as + supplied via the environment variable DEADLINE_REST_URL + + """ + + label = "Submit to Deadline" + order = pyblish.api.IntegratorOrder + 0.1 + hosts = ["nuke", "nukestudio"] + families = ["render.farm"] + optional = True + + def process(self, instance): + + # root = nuke.root() + # node_subset_name = instance.data.get("name", None) + node = instance[0] + + DEADLINE_REST_URL = os.environ.get("DEADLINE_REST_URL", + "http://localhost:8082") + assert DEADLINE_REST_URL, "Requires DEADLINE_REST_URL" + + context = instance.context + workspace = os.path.dirname(context.data["currentFile"]) + filepath = None + + # get path + path = nuke.filename(node) + output_dir = instance.data['outputDir'] + + filepath = context.data["currentFile"] + + self.log.debug(filepath) + + filename = os.path.basename(filepath) + comment = context.data.get("comment", "") + dirname = os.path.join(workspace, "renders") + deadline_user = context.data.get("deadlineUser", getpass.getuser()) + jobname = "%s - %s" % (filename, instance.name) + ver = re.search(r"\d+\.\d+", context.data.get("hostVersion")) + + try: + # Ensure render folder exists + os.makedirs(dirname) + except OSError: + pass + + # Documentation for keys available at: + # https://docs.thinkboxsoftware.com + # /products/deadline/8.0/1_User%20Manual/manual + # /manual-submission.html#job-info-file-options + payload = { + "JobInfo": { + # Top-level group name + "BatchName": filename, + + # Job name, as seen in Monitor + "Name": jobname, + + # Arbitrary username, for visualisation in Monitor + "UserName": deadline_user, + + "Plugin": "Nuke", + "Frames": "{start}-{end}".format( + start=int(instance.data["startFrame"]), + end=int(instance.data["endFrame"]) + ), + + "Comment": comment, + + # Optional, enable double-click to preview rendered + # frames from Deadline Monitor + # "OutputFilename0": output_filename_0.replace("\\", "/"), + }, + "PluginInfo": { + # Input + "SceneFile": filepath, + + # Output directory and filename + "OutputFilePath": dirname.replace("\\", "/"), + # "OutputFilePrefix": render_variables["filename_prefix"], + + # Mandatory for Deadline + "Version": ver.group(), + + # Resolve relative references + "ProjectPath": workspace, + }, + + # Mandatory for Deadline, may be empty + "AuxFiles": [] + } + + # Include critical environment variables with submission + keys = [ + # This will trigger `userSetup.py` on the slave + # such that proper initialisation happens the same + # way as it does on a local machine. + # TODO(marcus): This won't work if the slaves don't + # have accesss to these paths, such as if slaves are + # running Linux and the submitter is on Windows. + "PYTHONPATH", + "PATH", + "AVALON_SCHEMA", + "FTRACK_API_KEY", + "FTRACK_API_USER", + "FTRACK_SERVER", + "PYBLISHPLUGINPATH", + "NUKE_PATH", + "TOOL_ENV" + ] + environment = dict({key: os.environ[key] for key in keys + if key in os.environ}, **api.Session) + # self.log.debug("enviro: {}".format(pprint(environment))) + for path in os.environ: + if path.lower().startswith('pype_'): + environment[path] = os.environ[path] + + environment["PATH"] = os.environ["PATH"] + # self.log.debug("enviro: {}".format(environment['PYPE_SCRIPTS'])) + clean_environment = {} + for key in environment: + clean_path = "" + self.log.debug("key: {}".format(key)) + to_process = environment[key] + if key == "PYPE_STUDIO_CORE_MOUNT": + clean_path = environment[key] + elif "://" in environment[key]: + clean_path = environment[key] + elif os.pathsep not in to_process: + try: + path = environment[key] + path.decode('UTF-8', 'strict') + clean_path = os.path.normpath(path) + except UnicodeDecodeError: + print('path contains non UTF characters') + else: + for path in environment[key].split(os.pathsep): + try: + path.decode('UTF-8', 'strict') + clean_path += os.path.normpath(path) + os.pathsep + except UnicodeDecodeError: + print('path contains non UTF characters') + + if key == "PYTHONPATH": + clean_path = clean_path.replace('python2', 'python3') + clean_path = clean_path.replace( + os.path.normpath( + environment['PYPE_STUDIO_CORE_MOUNT']), # noqa + os.path.normpath( + environment['PYPE_STUDIO_CORE_PATH'])) # noqa + clean_environment[key] = clean_path + + environment = clean_environment + + payload["JobInfo"].update({ + "EnvironmentKeyValue%d" % index: "{key}={value}".format( + key=key, + value=environment[key] + ) for index, key in enumerate(environment) + }) + + plugin = payload["JobInfo"]["Plugin"] + self.log.info("using render plugin : {}".format(plugin)) + + self.preflight_check(instance) + + self.log.info("Submitting..") + self.log.info(json.dumps(payload, indent=4, sort_keys=True)) + + # E.g. http://192.168.0.1:8082/api/jobs + url = "{}/api/jobs".format(DEADLINE_REST_URL) + response = requests.post(url, json=payload) + if not response.ok: + raise Exception(response.text) + + # Store output dir for unified publisher (filesequence) + instance.data["deadlineSubmissionJob"] = response.json() + instance.data["publishJobState"] = "Active" + + def preflight_check(self, instance): + """Ensure the startFrame, endFrame and byFrameStep are integers""" + + for key in ("startFrame", "endFrame"): + value = instance.data[key] + + if int(value) == value: + continue + + self.log.warning( + "%f=%d was rounded off to nearest integer" + % (value, int(value)) + ) diff --git a/pype/plugins/nuke/publish/validate_collection.py b/pype/plugins/nuke/publish/validate_rendered_frames.py similarity index 52% rename from pype/plugins/nuke/publish/validate_collection.py rename to pype/plugins/nuke/publish/validate_rendered_frames.py index 1d0e1b260e..e2c9460597 100644 --- a/pype/plugins/nuke/publish/validate_collection.py +++ b/pype/plugins/nuke/publish/validate_rendered_frames.py @@ -1,5 +1,6 @@ import os import pyblish.api +from pype.api import ValidationException import clique @@ -20,24 +21,29 @@ class RepairCollectionAction(pyblish.api.Action): self.log.info("Rendering toggled ON") -class ValidatePrerenderedFrames(pyblish.api.InstancePlugin): +class ValidateRenderedFrames(pyblish.api.InstancePlugin): """ Validates file output. """ order = pyblish.api.ValidatorOrder + 0.1 - families = ["render.frames", "still.frames", "prerender.frames"] + families = ["render"] - label = "Validate prerendered frame" - hosts = ["nuke"] + label = "Validate rendered frame" + hosts = ["nuke", "nukestudio"] actions = [RepairCollectionAction] def process(self, instance): for repre in instance.data.get('representations'): - assert repre.get('files'), "no frames were collected, you need to render them" + if not repre.get('files'): + msg = ("no frames were collected, " + "you need to render them") + self.log.error(msg) + raise ValidationException(msg) collections, remainder = clique.assemble(repre["files"]) self.log.info('collections: {}'.format(str(collections))) + self.log.info('remainder: {}'.format(str(remainder))) collection = collections[0] @@ -45,10 +51,20 @@ class ValidatePrerenderedFrames(pyblish.api.InstancePlugin): - instance.data["startFrame"] + 1 if frame_length != 1: - assert len(collections) == 1, "There are multiple collections in the folder" - assert collection.is_contiguous(), "Some frames appear to be missing" + if len(collections) != 1: + msg = "There are multiple collections in the folder" + self.log.error(msg) + raise ValidationException(msg) - assert remainder is not None, "There are some extra files in folder" + if not collection.is_contiguous(): + msg = "Some frames appear to be missing" + self.log.error(msg) + raise ValidationException(msg) + + # if len(remainder) != 0: + # msg = "There are some extra files in folder" + # self.log.error(msg) + # raise ValidationException(msg) self.log.info('frame_length: {}'.format(frame_length)) self.log.info('len(collection.indexes): {}'.format( @@ -56,7 +72,7 @@ class ValidatePrerenderedFrames(pyblish.api.InstancePlugin): assert len( collection.indexes - ) is frame_length, "{} missing frames. Use " - "repair to render all frames".format(__name__) + ) is frame_length, ("{} missing frames. Use " + "repair to render all frames").format(__name__) instance.data['collection'] = collection diff --git a/setup/nuke/nuke_path/menu.py b/setup/nuke/nuke_path/menu.py index 4982513b78..61922d55ac 100644 --- a/setup/nuke/nuke_path/menu.py +++ b/setup/nuke/nuke_path/menu.py @@ -11,7 +11,7 @@ from pypeapp import Logger log = Logger().get_logger(__name__, "nuke") -nuke.addOnScriptSave(writes_version_sync) +# nuke.addOnScriptSave(writes_version_sync) nuke.addOnScriptSave(onScriptLoad) nuke.addOnScriptSave(checkInventoryVersions)