diff --git a/pype/ftrack/actions/ftrack_action_handler.py b/pype/ftrack/actions/ftrack_action_handler.py index b3fbd28274..ba67912c9f 100644 --- a/pype/ftrack/actions/ftrack_action_handler.py +++ b/pype/ftrack/actions/ftrack_action_handler.py @@ -71,6 +71,7 @@ class AppAction(object): ), self._launch ) + self.log.info("Application '{}' - Registered successfully".format(self.label)) def _discover(self, event): args = self._translate_event( diff --git a/pype/nuke/__init__.py b/pype/nuke/__init__.py index 371fe2a786..83235edc29 100644 --- a/pype/nuke/__init__.py +++ b/pype/nuke/__init__.py @@ -113,8 +113,7 @@ def install(): # Disable all families except for the ones we explicitly want to see family_states = [ - "render", - "still" + "write", "lifeGroup", "backdrop", "imagesequence", diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index 79c292b2ba..af0284bfae 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -241,7 +241,7 @@ def get_avalon_knob_data(node): import toml try: data = toml.loads(node['avalon'].value()) - except: + except Exception: return None return data diff --git a/pype/plugins/ftrack/integrate_ftrack_instances.py b/pype/plugins/ftrack/integrate_ftrack_instances.py index 9a0a36a413..177ced5ddb 100644 --- a/pype/plugins/ftrack/integrate_ftrack_instances.py +++ b/pype/plugins/ftrack/integrate_ftrack_instances.py @@ -20,7 +20,8 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): 'rig': 'rig', 'setdress': 'setdress', 'pointcache': 'cache', - 'review': 'mov'} + 'review': 'mov', + 'write': 'img'} def process(self, instance): diff --git a/pype/plugins/nuke/load/load_sequence.py b/pype/plugins/nuke/load/load_sequence.py index ee5e93aad5..1cd3688aaf 100644 --- a/pype/plugins/nuke/load/load_sequence.py +++ b/pype/plugins/nuke/load/load_sequence.py @@ -1,14 +1,77 @@ -import nuke import os import contextlib from avalon import api import avalon.io as io + +import nuke + from pype.api import Logger log = Logger.getLogger(__name__, "nuke") +@contextlib.contextmanager +def preserve_trim(node): + """Preserve the relative trim of the Loader tool. + + This tries to preserve the loader's trim (trim in and trim out) after + the context by reapplying the "amount" it trims on the clip's length at + start and end. + + """ + # working script frame range + script_start = nuke.root()["first_frame"].value() + + start_at_frame = None + offset_frame = None + if node['frame_mode'].value() == "start at": + start_at_frame = node['frame'].value() + if node['frame_mode'].value() is "offset": + offset_frame = node['frame'].value() + + try: + yield + finally: + if start_at_frame: + node['frame_mode'].setValue("start at") + node['frame'].setValue(str(script_start)) + log.info("start frame of reader was set to" + "{}".format(script_start)) + + if offset_frame: + node['frame_mode'].setValue("offset") + node['frame'].setValue(str((script_start + offset_frame))) + log.info("start frame of reader was set to" + "{}".format(script_start)) + + +def loader_shift(node, frame, relative=True): + """Shift global in time by i preserving duration + + This moves the loader by i frames preserving global duration. When relative + is False it will shift the global in to the start frame. + + Args: + loader (tool): The fusion loader tool. + frame (int): The amount of frames to move. + relative (bool): When True the shift is relative, else the shift will + change the global in to frame. + + Returns: + int: The resulting relative frame change (how much it moved) + + """ + # working script frame range + script_start = nuke.root()["first_frame"].value() + + if relative: + node['frame_mode'].setValue("start at") + node['frame'].setValue(str(script_start)) + + return int(script_start) + + class LoadSequence(api.Loader): """Load image sequence into Nuke""" @@ -21,122 +84,132 @@ class LoadSequence(api.Loader): color = "orange" def load(self, context, name, namespace, data): + from avalon.nuke import ( + containerise, + ls_img_sequence, + viewer_update_and_undo_stop + ) + for k, v in context.items(): + log.info("key: `{}`, value: {}\n".format(k, v)) - log.info("context: {}\n".format(context["representation"])) - log.info("name: {}\n".format(name)) - log.info("namespace: {}\n".format(namespace)) - log.info("data: {}\n".format(data)) - return - # # Fallback to asset name when namespace is None - # if namespace is None: - # namespace = context['asset']['name'] - # - # # Use the first file for now - # # TODO: fix path fname - # file = ls_img_sequence(os.path.dirname(self.fname), one=True) - # - # # Create the Loader with the filename path set - # with viewer_update_and_undo_stop(): - # # TODO: it might be universal read to img/geo/camera - # r = nuke.createNode( - # "Read", - # "name {}".format(self.name)) # TODO: does self.name exist? - # r["file"].setValue(file['path']) - # if len(file['frames']) is 1: - # first = file['frames'][0][0] - # last = file['frames'][0][1] - # r["originfirst"].setValue(first) - # r["first"].setValue(first) - # r["originlast"].setValue(last) - # r["last"].setValue(last) - # else: - # first = file['frames'][0][0] - # last = file['frames'][:-1][1] - # r["originfirst"].setValue(first) - # r["first"].setValue(first) - # r["originlast"].setValue(last) - # r["last"].setValue(last) - # log.warning("Missing frames in image sequence") - # - # # Set global in point to start frame (if in version.data) - # start = context["version"]["data"].get("startFrame", None) - # if start is not None: - # loader_shift(r, start, relative=False) - # - # containerise(r, - # name=name, - # namespace=namespace, - # context=context, - # loader=self.__class__.__name__) - # - # def switch(self, container, representation): - # self.update(container, representation) - # - # def update(self, container, representation): - # """Update the Loader's path - # - # Fusion automatically tries to reset some variables when changing - # the loader's path to a new file. These automatic changes are to its - # inputs: - # - # """ - # - # from avalon.nuke import ( - # viewer_update_and_undo_stop, - # ls_img_sequence, - # update_container - # ) - # log.info("this i can see") - # node = container["_tool"] - # # TODO: prepare also for other readers img/geo/camera - # assert node.Class() == "Reader", "Must be Reader" - # - # root = api.get_representation_path(representation) - # file = ls_img_sequence(os.path.dirname(root), one=True) - # - # # Get start frame from version data - # version = io.find_one({"type": "version", - # "_id": representation["parent"]}) - # start = version["data"].get("startFrame") - # if start is None: - # log.warning("Missing start frame for updated version" - # "assuming starts at frame 0 for: " - # "{} ({})".format(node['name'].value(), representation)) - # start = 0 - # - # with viewer_update_and_undo_stop(): - # - # # Update the loader's path whilst preserving some values - # with preserve_trim(node): - # with preserve_inputs(node, - # knobs=["file", - # "first", - # "last", - # "originfirst", - # "originlast", - # "frame_mode", - # "frame"]): - # node["file"] = file["path"] - # - # # Set the global in to the start frame of the sequence - # global_in_changed = loader_shift(node, start, relative=False) - # if global_in_changed: - # # Log this change to the user - # log.debug("Changed '{}' global in:" - # " {:d}".format(node['name'].value(), start)) - # - # # Update the imprinted representation - # update_container( - # node, - # {"representation": str(representation["_id"])} - # ) - # - # def remove(self, container): - # - # from avalon.nuke import viewer_update_and_undo_stop - # - # node = container["_tool"] - # assert node.Class() == "Reader", "Must be Reader" - # - # with viewer_update_and_undo_stop(): - # nuke.delete(node) + # Fallback to asset name when namespace is None + if namespace is None: + namespace = context['asset']['name'] + + # Use the first file for now + # TODO: fix path fname + file = ls_img_sequence(os.path.dirname(self.fname), one=True) + log.info("file: {}\n".format(file)) + + read_name = "Read_" + context["representation"]["context"]["subset"] + # Create the Loader with the filename path set + with viewer_update_and_undo_stop(): + # TODO: it might be universal read to img/geo/camera + r = nuke.createNode( + "Read", + "name {}".format(read_name)) + r["file"].setValue(file['path']) + if len(file['frames']) is 1: + first = file['frames'][0][0] + last = file['frames'][0][1] + r["origfirst"].setValue(first) + r["first"].setValue(first) + r["origlast"].setValue(last) + r["last"].setValue(last) + else: + first = file['frames'][0][0] + last = file['frames'][:-1][1] + r["origfirst"].setValue(first) + r["first"].setValue(first) + r["origlast"].setValue(last) + r["last"].setValue(last) + log.warning("Missing frames in image sequence") + + # Set colorspace defined in version data + colorspace = context["version"]["data"].get("colorspace", None) + if colorspace is not None: + r["colorspace"].setValue(str(colorspace)) + + # Set global in point to start frame (if in version.data) + start = context["version"]["data"].get("startFrame", None) + if start is not None: + loader_shift(r, start, relative=True) + + # add additional metadata from the version to imprint to Avalon knob + add_keys = ["startFrame", "endFrame", "handles", + "source", "colorspace", "author", "fps"] + + data_imprint = {} + for k in add_keys: + data_imprint.update({k: context["version"]['data'][k]}) + + containerise(r, + name=name, + namespace=namespace, + context=context, + loader=self.__class__.__name__, + data=data_imprint) + + def switch(self, container, representation): + self.update(container, representation) + + def update(self, container, representation): + """Update the Loader's path + + Fusion automatically tries to reset some variables when changing + the loader's path to a new file. These automatic changes are to its + inputs: + + """ + + from avalon.nuke import ( + viewer_update_and_undo_stop, + ls_img_sequence, + update_container + ) + log.info("this i can see") + node = container["_tool"] + # TODO: prepare also for other readers img/geo/camera + assert node.Class() == "Reader", "Must be Reader" + + root = api.get_representation_path(representation) + file = ls_img_sequence(os.path.dirname(root), one=True) + + # Get start frame from version data + version = io.find_one({"type": "version", + "_id": representation["parent"]}) + start = version["data"].get("startFrame") + if start is None: + log.warning("Missing start frame for updated version" + "assuming starts at frame 0 for: " + "{} ({})".format(node['name'].value(), representation)) + start = 0 + + with viewer_update_and_undo_stop(): + + # Update the loader's path whilst preserving some values + with preserve_trim(node): + node["file"] = file["path"] + + # Set the global in to the start frame of the sequence + global_in_changed = loader_shift(node, start, relative=False) + if global_in_changed: + # Log this change to the user + log.debug("Changed '{}' global in:" + " {:d}".format(node['name'].value(), start)) + + # Update the imprinted representation + update_container( + node, + {"representation": str(representation["_id"])} + ) + + def remove(self, container): + + from avalon.nuke import viewer_update_and_undo_stop + + node = container["_tool"] + assert node.Class() == "Reader", "Must be Reader" + + with viewer_update_and_undo_stop(): + nuke.delete(node) diff --git a/pype/plugins/nuke/publish/collect_instances.py b/pype/plugins/nuke/publish/collect_instances.py index f1fa1276c2..5d64c60252 100644 --- a/pype/plugins/nuke/publish/collect_instances.py +++ b/pype/plugins/nuke/publish/collect_instances.py @@ -24,24 +24,29 @@ class CollectNukeInstances(pyblish.api.ContextPlugin): except Exception: continue + try: + publish = node.knob("publish").value() + except Exception: + continue + # get data from avalon knob avalon_knob_data = get_avalon_knob_data(node) if not avalon_knob_data: continue - subset = avalon_knob_data["subset"] + + subset = avalon_knob_data.get("subset", None) or node["name"].value() # Create instance instance = context.create_instance(subset) instance.add(node) instance.data.update({ + "subset": subset, "asset": os.environ["AVALON_ASSET"], "label": node.name(), "name": node.name(), - "subset": subset, - "families": [avalon_knob_data["families"]], - "family": avalon_knob_data["family"], - "publish": node.knob("publish").value() + "avalonKnob": avalon_knob_data, + "publish": publish }) self.log.info("collected instance: {}".format(instance.data)) instances.append(instance) diff --git a/pype/plugins/nuke/publish/collect_writes.py b/pype/plugins/nuke/publish/collect_writes.py index db966fd84d..1f1d79fefe 100644 --- a/pype/plugins/nuke/publish/collect_writes.py +++ b/pype/plugins/nuke/publish/collect_writes.py @@ -3,6 +3,8 @@ import os import nuke import pyblish.api import logging +from avalon import io, api + log = logging.getLogger(__name__) @@ -15,6 +17,9 @@ class CollectNukeWrites(pyblish.api.ContextPlugin): hosts = ["nuke", "nukeassist"] def process(self, context): + asset_data = io.find_one({"type": "asset", + "name": api.Session["AVALON_ASSET"]}) + self.log.debug("asset_data: {}".format(asset_data["data"])) for instance in context.data["instances"]: self.log.debug("checking instance: {}".format(instance)) node = instance[0] @@ -63,9 +68,9 @@ class CollectNukeWrites(pyblish.api.ContextPlugin): else: # dealing with local/farm rendering if node["render_farm"].value(): - families = "{}.farm".format(instance.data["families"][0]) + families = "{}.farm".format(instance.data["avalonKnob"]["families"][0]) else: - families = "{}.local".format(instance.data["families"][0]) + families = "{}.local".format(instance.data["avalonKnob"]["families"][0]) self.log.debug("checking for error: {}".format(label)) instance.data.update({ @@ -73,12 +78,16 @@ class CollectNukeWrites(pyblish.api.ContextPlugin): "outputDir": output_dir, "ext": ext, "label": label, + "family": instance.data["avalonKnob"]["family"], "families": [families], - "firstFrame": first_frame, - "lastFrame": last_frame, + "startFrame": first_frame, + "endFrame": last_frame, "outputType": output_type, "stagingDir": output_dir, - + "colorspace": node["colorspace"].value(), + "handles": int(asset_data["data"].get("handles", 0)), + "step": 1, + "fps": int(nuke.root()['fps'].value()) }) self.log.debug("instance.data: {}".format(instance.data)) diff --git a/pype/plugins/nuke/publish/integrate_rendered_frames.py b/pype/plugins/nuke/publish/integrate_rendered_frames.py index f482a48cda..8b7df93d1b 100644 --- a/pype/plugins/nuke/publish/integrate_rendered_frames.py +++ b/pype/plugins/nuke/publish/integrate_rendered_frames.py @@ -1,6 +1,7 @@ import os import logging import shutil +import clique import errno import pyblish.api @@ -110,9 +111,11 @@ class IntegrateFrames(pyblish.api.InstancePlugin): locations=[LOCATION], data=version_data) + self.log.debug("version: {}".format(version)) self.log.debug("Creating version ...") - version_id = io.insert_one(version).inserted_id + version_id = io.insert_one(version).inserted_id + self.log.debug("version_id: {}".format(version_id)) # Write to disk # _ # | | @@ -130,11 +133,11 @@ class IntegrateFrames(pyblish.api.InstancePlugin): # "asset": ASSET, # "subset": subset["name"], # "version": version["name"]} - hierarchy = io.find_one({"type":'asset', "name":ASSET})['data']['parents'] + hierarchy = io.find_one({"type": 'asset', "name": ASSET})['data']['parents'] if hierarchy: # hierarchy = os.path.sep.join(hierarchy) hierarchy = os.path.join(*hierarchy) - + self.log.debug("hierarchy: {}".format(hierarchy)) template_data = {"root": root, "project": {"name": PROJECT, "code": "prjX"}, @@ -145,7 +148,7 @@ class IntegrateFrames(pyblish.api.InstancePlugin): "VERSION": version["name"], "hierarchy": hierarchy} - template_publish = project["config"]["template"]["publish"] + # template_publish = project["config"]["template"]["publish"] anatomy = instance.context.data['anatomy'] # Find the representations to transfer amongst the files @@ -153,7 +156,6 @@ class IntegrateFrames(pyblish.api.InstancePlugin): representations = [] for files in instance.data["files"]: - # Collection # _______ # |______|\ @@ -206,7 +208,6 @@ class IntegrateFrames(pyblish.api.InstancePlugin): anatomy_filled = anatomy.format(template_data) dst = anatomy_filled.publish.path - # if instance.data.get('transfer', True): # dst = src # instance.data["transfers"].append([src, dst]) @@ -222,17 +223,17 @@ class IntegrateFrames(pyblish.api.InstancePlugin): # Imprint shortcut to context # for performance reasons. "context": { - "root": root, - "project": PROJECT, - "projectcode": "prjX", - 'task': api.Session["AVALON_TASK"], - "silo": asset['silo'], - "asset": ASSET, - "family": instance.data['family'], - "subset": subset["name"], - "version": version["name"], - "hierarchy": hierarchy, - "representation": ext[1:] + "root": root, + "project": PROJECT, + "projectcode": "prjX", + 'task': api.Session["AVALON_TASK"], + "silo": asset['silo'], + "asset": ASSET, + "family": instance.data['family'], + "subset": subset["name"], + "version": version["name"], + "hierarchy": hierarchy, + "representation": ext[1:] } } representations.append(representation) @@ -353,9 +354,11 @@ class IntegrateFrames(pyblish.api.InstancePlugin): "comment": context.data.get("comment")} # Include optional data if present in - optionals = ["startFrame", "endFrame", "step", "handles"] + optionals = ["startFrame", "endFrame", "step", + "handles", "colorspace", "fps", "outputDir"] + for key in optionals: if key in instance.data: - version_data[key] = instance.data[key] + version_data[key] = instance.data.get(key, None) return version_data diff --git a/pype/plugins/nuke/publish/render_local.py b/pype/plugins/nuke/publish/render_local.py index 55adedb9e5..eee67d1e40 100644 --- a/pype/plugins/nuke/publish/render_local.py +++ b/pype/plugins/nuke/publish/render_local.py @@ -29,8 +29,8 @@ class NukeRenderLocal(pyblish.api.InstancePlugin): self.log.debug("instance collected: {}".format(instance.data)) - first_frame = instance.data.get("firstFrame", None) - last_frame = instance.data.get("lastFrame", None) + first_frame = instance.data.get("startFrame", None) + last_frame = instance.data.get("endFrame", None) node_subset_name = instance.data.get("name", None) self.log.info("Starting render") diff --git a/pype/plugins/nuke/publish/validate_collection.py b/pype/plugins/nuke/publish/validate_collection.py index 4088272bc4..e8137d006c 100644 --- a/pype/plugins/nuke/publish/validate_collection.py +++ b/pype/plugins/nuke/publish/validate_collection.py @@ -35,8 +35,8 @@ class ValidateCollection(pyblish.api.InstancePlugin): collections, remainder = clique.assemble(*instance.data['files']) self.log.info('collections: {}'.format(str(collections))) - frame_length = instance.data["lastFrame"] \ - - instance.data["firstFrame"] + 1 + frame_length = instance.data["endFrame"] \ + - instance.data["startFrame"] + 1 if frame_length is not 1: assert len(collections) == 1, self.log.info(