diff --git a/pype/ftrack/tray/ftrack_module.py b/pype/ftrack/tray/ftrack_module.py index adcce9c2b1..ce2754c25d 100644 --- a/pype/ftrack/tray/ftrack_module.py +++ b/pype/ftrack/tray/ftrack_module.py @@ -88,9 +88,11 @@ class FtrackModule: def set_action_server(self): try: self.action_server.run_server() - except Exception: - msg = 'Ftrack Action server crashed! Please try to start again.' - log.error(msg) + except Exception as exc: + log.error( + "Ftrack Action server crashed! Please try to start again.", + exc_info=True + ) # TODO show message to user self.bool_action_server = False self.set_menu_visibility() diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index c416cf3fc7..e5d8007d70 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -63,6 +63,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "rig", "plate", "look", + "lut", "audio" ] exclude_families = ["clip"] diff --git a/pype/plugins/nuke/load/load_luts.py b/pype/plugins/nuke/load/load_luts.py new file mode 100644 index 0000000000..d1c6c71057 --- /dev/null +++ b/pype/plugins/nuke/load/load_luts.py @@ -0,0 +1,317 @@ +from avalon import api, style, io +import nuke +import json +from collections import OrderedDict + + +class LoadLuts(api.Loader): + """Loading colorspace soft effect exported from nukestudio""" + + representations = ["lutJson"] + families = ["lut"] + + label = "Load Luts - nodes" + order = 0 + icon = "cc" + color = style.colors.light + + def load(self, context, name, namespace, data): + """ + Loading function to get the soft effects to particular read node + + Arguments: + context (dict): context of version + name (str): name of the version + namespace (str): asset name + data (dict): compulsory attribute > not used + + Returns: + nuke node: containerised nuke node object + """ + # import dependencies + from avalon.nuke import containerise + + # get main variables + version = context['version'] + version_data = version.get("data", {}) + vname = version.get("name", None) + first = version_data.get("frameStart", None) + last = version_data.get("frameEnd", None) + workfile_first_frame = int(nuke.root()["first_frame"].getValue()) + namespace = namespace or context['asset']['name'] + colorspace = version_data.get("colorspace", None) + object_name = "{}_{}".format(name, namespace) + + # prepare data for imprinting + # add additional metadata from the version to imprint to Avalon knob + add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd", + "source", "author", "fps"] + + data_imprint = {"frameStart": first, + "frameEnd": last, + "version": vname, + "colorspaceInput": colorspace, + "objectName": object_name} + + for k in add_keys: + data_imprint.update({k: version_data[k]}) + + # getting file path + file = self.fname.replace("\\", "/") + + # getting data from json file with unicode conversion + with open(file, "r") as f: + json_f = {self.byteify(key): self.byteify(value) + for key, value in json.load(f).iteritems()} + + # get correct order of nodes by positions on track and subtrack + nodes_order = self.reorder_nodes(json_f["effects"]) + + # adding nodes to node graph + # just in case we are in group lets jump out of it + nuke.endGroup() + + GN = nuke.createNode("Group") + + GN["name"].setValue(object_name) + + # adding content to the group node + with GN: + pre_node = nuke.createNode("Input") + pre_node["name"].setValue("rgb") + + for ef_name, ef_val in nodes_order.items(): + node = nuke.createNode(ef_val["class"]) + for k, v in ef_val["node"].items(): + if isinstance(v, list) and len(v) > 4: + node[k].setAnimated() + for i, value in enumerate(v): + if isinstance(value, list): + for ci, cv in enumerate(value): + node[k].setValueAt( + cv, + (workfile_first_frame + i), + ci) + else: + node[k].setValueAt( + value, + (workfile_first_frame + i)) + else: + node[k].setValue(v) + node.setInput(0, pre_node) + pre_node = node + + output = nuke.createNode("Output") + output.setInput(0, pre_node) + + # try to find parent read node + self.connect_read_node(GN, namespace, json_f["assignTo"]) + + GN["tile_color"].setValue(int("0x3469ffff", 16)) + + self.log.info("Loaded lut setup: `{}`".format(GN["name"].value())) + + return containerise( + node=GN, + name=name, + namespace=namespace, + context=context, + loader=self.__class__.__name__, + data=data_imprint) + + def update(self, container, representation): + """Update the Loader's path + + Nuke 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 ( + update_container + ) + # get main variables + # Get version from io + version = io.find_one({ + "type": "version", + "_id": representation["parent"] + }) + # get corresponding node + GN = nuke.toNode(container['objectName']) + + file = api.get_representation_path(representation).replace("\\", "/") + name = container['name'] + version_data = version.get("data", {}) + vname = version.get("name", None) + first = version_data.get("frameStart", None) + last = version_data.get("frameEnd", None) + workfile_first_frame = int(nuke.root()["first_frame"].getValue()) + namespace = container['namespace'] + colorspace = version_data.get("colorspace", None) + object_name = "{}_{}".format(name, namespace) + + add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd", + "source", "author", "fps"] + + data_imprint = {"representation": str(representation["_id"]), + "frameStart": first, + "frameEnd": last, + "version": vname, + "colorspaceInput": colorspace, + "objectName": object_name} + + for k in add_keys: + data_imprint.update({k: version_data[k]}) + + # Update the imprinted representation + update_container( + GN, + data_imprint + ) + + # getting data from json file with unicode conversion + with open(file, "r") as f: + json_f = {self.byteify(key): self.byteify(value) + for key, value in json.load(f).iteritems()} + + # get correct order of nodes by positions on track and subtrack + nodes_order = self.reorder_nodes(json_f["effects"]) + + # adding nodes to node graph + # just in case we are in group lets jump out of it + nuke.endGroup() + + # adding content to the group node + with GN: + # first remove all nodes + [nuke.delete(n) for n in nuke.allNodes()] + + # create input node + pre_node = nuke.createNode("Input") + pre_node["name"].setValue("rgb") + + for ef_name, ef_val in nodes_order.items(): + node = nuke.createNode(ef_val["class"]) + for k, v in ef_val["node"].items(): + if isinstance(v, list) and len(v) > 3: + node[k].setAnimated() + for i, value in enumerate(v): + if isinstance(value, list): + for ci, cv in enumerate(value): + node[k].setValueAt( + cv, + (workfile_first_frame + i), + ci) + else: + node[k].setValueAt( + value, + (workfile_first_frame + i)) + else: + node[k].setValue(v) + node.setInput(0, pre_node) + pre_node = node + + # create output node + output = nuke.createNode("Output") + output.setInput(0, pre_node) + + # try to find parent read node + self.connect_read_node(GN, namespace, json_f["assignTo"]) + + # get all versions in list + versions = io.find({ + "type": "version", + "parent": version["parent"] + }).distinct('name') + + max_version = max(versions) + + # change color of node + if version.get("name") not in [max_version]: + GN["tile_color"].setValue(int("0xd84f20ff", 16)) + else: + GN["tile_color"].setValue(int("0x3469ffff", 16)) + + self.log.info("udated to version: {}".format(version.get("name"))) + + def connect_read_node(self, group_node, asset, subset): + """ + Finds read node and selects it + + Arguments: + asset (str): asset name + + Returns: + nuke node: node is selected + None: if nothing found + """ + search_name = "{0}_{1}".format(asset, subset) + node = [n for n in nuke.allNodes() if search_name in n["name"].value()] + if len(node) > 0: + rn = node[0] + else: + None + + # Parent read node has been found + # solving connections + if rn: + dep_nodes = rn.dependent() + + if len(dep_nodes) > 0: + for dn in dep_nodes: + dn.setInput(0, group_node) + + group_node.setInput(0, rn) + group_node.autoplace() + + def reorder_nodes(self, data): + new_order = OrderedDict() + trackNums = [v["trackIndex"] for k, v in data.items()] + subTrackNums = [v["subTrackIndex"] for k, v in data.items()] + + for trackIndex in range( + min(trackNums), max(trackNums) + 1): + for subTrackIndex in range( + min(subTrackNums), max(subTrackNums) + 1): + item = self.get_item(data, trackIndex, subTrackIndex) + if item is not {}: + new_order.update(item) + return new_order + + def get_item(self, data, trackIndex, subTrackIndex): + return {key: val for key, val in data.items() + if subTrackIndex == val["subTrackIndex"] + if trackIndex == val["trackIndex"]} + + def byteify(self, input): + """ + Converts unicode strings to strings + It goes trought all dictionary + + Arguments: + input (dict/str): input + + Returns: + dict: with fixed values and keys + + """ + + if isinstance(input, dict): + return {self.byteify(key): self.byteify(value) + for key, value in input.iteritems()} + elif isinstance(input, list): + return [self.byteify(element) for element in input] + elif isinstance(input, unicode): + return input.encode('utf-8') + else: + return input + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + from avalon.nuke import viewer_update_and_undo_stop + node = nuke.toNode(container['objectName']) + with viewer_update_and_undo_stop(): + nuke.delete(node) diff --git a/pype/plugins/nuke/load/load_sequence.py b/pype/plugins/nuke/load/load_sequence.py index 471dfcf555..2946857e09 100644 --- a/pype/plugins/nuke/load/load_sequence.py +++ b/pype/plugins/nuke/load/load_sequence.py @@ -96,6 +96,8 @@ class LoadSequence(api.Loader): self.first_frame = int(nuke.root()["first_frame"].getValue()) self.handle_start = version_data.get("handleStart", 0) + self.handle_start = version_data.get("handleStart", 0) + self.handle_end = version_data.get("handleEnd", 0) first = version_data.get("frameStart", None) last = version_data.get("frameEnd", None) @@ -104,6 +106,9 @@ class LoadSequence(api.Loader): if namespace is None: namespace = context['asset']['name'] + first -= self.handle_start + last += self.handle_end + file = self.fname.replace("\\", "/") log.info("file: {}\n".format(self.fname)) @@ -231,6 +236,7 @@ class LoadSequence(api.Loader): self.first_frame = int(nuke.root()["first_frame"].getValue()) self.handle_start = version_data.get("handleStart", 0) + self.handle_end = version_data.get("handleEnd", 0) first = version_data.get("frameStart", None) last = version_data.get("frameEnd", None) @@ -241,6 +247,9 @@ class LoadSequence(api.Loader): "{} ({})".format(node['name'].value(), representation)) first = 0 + first -= self.handle_start + last += self.handle_end + # Update the loader's path whilst preserving some values with preserve_trim(node): node["file"].setValue(file["path"]) diff --git a/pype/plugins/nukestudio/publish/collect_metadata.py b/pype/plugins/nukestudio/_unused/collect_metadata.py similarity index 100% rename from pype/plugins/nukestudio/publish/collect_metadata.py rename to pype/plugins/nukestudio/_unused/collect_metadata.py diff --git a/pype/plugins/nukestudio/publish/collect_clips.py b/pype/plugins/nukestudio/publish/collect_clips.py index da71e2ab50..7a400909fd 100644 --- a/pype/plugins/nukestudio/publish/collect_clips.py +++ b/pype/plugins/nukestudio/publish/collect_clips.py @@ -20,83 +20,114 @@ class CollectClips(api.ContextPlugin): projectdata = context.data["projectData"] version = context.data.get("version", "001") - instances_data = [] - for item in context.data.get("selection", []): - # Skip audio track items - # Try/Except is to handle items types, like EffectTrackItem - try: - media_type = "core.Hiero.Python.TrackItem.MediaType.kVideo" - if str(item.mediaType()) != media_type: + sequence = context.data.get("activeSequence") + selection = context.data.get("selection") + + track_effects = dict() + + # collect all trackItems as instances + for track_index, video_track in enumerate(sequence.videoTracks()): + items = video_track.items() + sub_items = video_track.subTrackItems() + + for item in items: + # compare with selection or if disabled + if item not in selection or not item.isEnabled(): continue - except: + + # Skip audio track items + # Try/Except is to handle items types, like EffectTrackItem + try: + media_type = "core.Hiero.Python.TrackItem.MediaType.kVideo" + if str(item.mediaType()) != media_type: + continue + except Exception: + continue + + asset = item.name() + track = item.parent() + source = item.source().mediaSource() + source_path = source.firstpath() + effects = [f for f in item.linkedItems() if f.isEnabled()] + + # If source is *.nk its a comp effect and we need to fetch the + # write node output. This should be improved by parsing the script + # rather than opening it. + if source_path.endswith(".nk"): + nuke.scriptOpen(source_path) + # There should noly be one. + write_node = nuke.allNodes(filter="Write")[0] + path = nuke.filename(write_node) + + if "%" in path: + # Get start frame from Nuke script and use the item source + # in/out, because you can have multiple shots covered with + # one nuke script. + start_frame = int(nuke.root()["first_frame"].getValue()) + if write_node["use_limit"].getValue(): + start_frame = int(write_node["first"].getValue()) + + path = path % (start_frame + item.sourceIn()) + + source_path = path + self.log.debug( + "Fetched source path \"{}\" from \"{}\" in " + "\"{}\".".format( + source_path, write_node.name(), source.firstpath() + ) + ) + + try: + head, padding, ext = os.path.basename(source_path).split(".") + source_first_frame = int(padding) + except Exception: + source_first_frame = 0 + + data = {"name": "{0}_{1}".format(track.name(), item.name()), + "item": item, + "source": source, + "sourcePath": source_path, + "track": track.name(), + "trackIndex": track_index, + "sourceFirst": source_first_frame, + "effects": effects, + "sourceIn": int(item.sourceIn()), + "sourceOut": int(item.sourceOut()), + "clipIn": int(item.timelineIn()), + "clipOut": int(item.timelineOut()), + "asset": asset, + "family": "clip", + "families": [], + "handles": 0, + "handleStart": projectdata.get("handles", 0), + "handleEnd": projectdata.get("handles", 0), + "version": int(version)} + + instance = context.create_instance(**data) + + self.log.info("Created instance: {}".format(instance)) + self.log.debug(">> effects: {}".format(instance.data["effects"])) + + context.data["assetsShared"][asset] = dict() + + # from now we are collecting only subtrackitems on + # track with no video items + if len(items) > 0: continue - track = item.parent() - source = item.source().mediaSource() - source_path = source.firstpath() + # create list in track key + # get all subTrackItems and add it to context + track_effects[track_index] = list() - # If source is *.nk its a comp effect and we need to fetch the - # write node output. This should be improved by parsing the script - # rather than opening it. - if source_path.endswith(".nk"): - nuke.scriptOpen(source_path) - # There should noly be one. - write_node = nuke.allNodes(filter="Write")[0] - path = nuke.filename(write_node) + # collect all subtrack items + for sitem in sub_items: + # unwrap from tuple >> it is always tuple with one item + sitem = sitem[0] + # checking if not enabled + if not sitem.isEnabled(): + continue - if "%" in path: - # Get start frame from Nuke script and use the item source - # in/out, because you can have multiple shots covered with - # one nuke script. - start_frame = int(nuke.root()["first_frame"].getValue()) - if write_node["use_limit"].getValue(): - start_frame = int(write_node["first"].getValue()) + track_effects[track_index].append(sitem) - path = path % (start_frame + item.sourceIn()) - - source_path = path - self.log.debug( - "Fetched source path \"{}\" from \"{}\" in " - "\"{}\".".format( - source_path, write_node.name(), source.firstpath() - ) - ) - - try: - head, padding, ext = os.path.basename(source_path).split(".") - source_first_frame = int(padding) - except: - source_first_frame = 0 - - instances_data.append( - { - "name": "{0}_{1}".format(track.name(), item.name()), - "item": item, - "source": source, - "sourcePath": source_path, - "track": track.name(), - "sourceFirst": source_first_frame, - "sourceIn": int(item.sourceIn()), - "sourceOut": int(item.sourceOut()), - "clipIn": int(item.timelineIn()), - "clipOut": int(item.timelineOut()) - } - ) - - for data in instances_data: - data.update( - { - "asset": data["item"].name(), - "family": "clip", - "families": [], - "handles": 0, - "handleStart": projectdata.get("handles", 0), - "handleEnd": projectdata.get("handles", 0), - "version": int(version) - } - ) - instance = context.create_instance(**data) - self.log.debug( - "Created instance with data: {}".format(instance.data) - ) - context.data["assetsShared"][data["asset"]] = dict() + context.data["trackEffects"] = track_effects + self.log.debug(">> sub_track_items: `{}`".format(track_effects)) diff --git a/pype/plugins/nukestudio/publish/collect_effects.py b/pype/plugins/nukestudio/publish/collect_effects.py new file mode 100644 index 0000000000..f90623e1b9 --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect_effects.py @@ -0,0 +1,95 @@ +import pyblish.api +import re + + +class CollectVideoTracksLuts(pyblish.api.InstancePlugin): + """Collect video tracks effects into context.""" + + order = pyblish.api.CollectorOrder + 0.1015 + label = "Collect Soft Lut Effects" + families = ["clip"] + + def process(self, instance): + + self.log.debug("Finding soft effect for subset: `{}`".format(instance.data.get("subset"))) + + # taking active sequence + subset = instance.data["subset"] + track_effects = instance.context.data.get("trackEffects", {}) + track_index = instance.data["trackIndex"] + effects = instance.data["effects"] + + # creating context attribute + self.effects = {"assignTo": subset, "effects": dict()} + + for sitem in effects: + self.add_effect(instance, track_index, sitem) + + for t_index, sitems in track_effects.items(): + for sitem in sitems: + if not t_index > track_index: + continue + self.log.debug(">> sitem: `{}`".format(sitem)) + self.add_effect(instance, t_index, sitem) + + instance.data["effectTrackItems"] = self.effects + + if len(instance.data.get("effectTrackItems", {}).keys()) > 0: + instance.data["families"] += ["lut"] + self.log.debug("effects.keys: {}".format(instance.data.get("effectTrackItems", {}).keys())) + self.log.debug("effects: {}".format(instance.data.get("effectTrackItems", {}))) + + def add_effect(self, instance, track_index, item): + track = item.parentTrack().name() + # node serialization + node = item.node() + node_serialized = self.node_serialisation(instance, node) + + # collect timelineIn/Out + effect_t_in = int(item.timelineIn()) + effect_t_out = int(item.timelineOut()) + + node_name = item.name() + node_class = re.sub(r"\d+", "", node_name) + + self.effects["effects"].update({node_name: { + "class": node_class, + "timelineIn": effect_t_in, + "timelineOut": effect_t_out, + "subTrackIndex": item.subTrackIndex(), + "trackIndex": track_index, + "track": track, + "node": node_serialized + }}) + + def node_serialisation(self, instance, node): + node_serialized = {} + timeline_in_h = instance.data["clipInH"] + timeline_out_h = instance.data["clipOutH"] + + # adding ignoring knob keys + _ignoring_keys = ['invert_mask', 'help', 'mask', + 'xpos', 'ypos', 'layer', 'process_mask', 'channel', + 'channels', 'maskChannelMask', 'maskChannelInput', + 'note_font', 'note_font_size', 'unpremult', + 'postage_stamp_frame', 'maskChannel', 'export_cc', + 'select_cccid', 'mix', 'version'] + + # loop trough all knobs and collect not ignored + # and any with any value + for knob in node.knobs().keys(): + # skip nodes in ignore keys + if knob in _ignoring_keys: + continue + + # get animation if node is animated + if node[knob].isAnimated(): + # grab animation including handles + knob_anim = [node[knob].getValueAt(i) + for i in range(timeline_in_h, timeline_out_h + 1)] + + node_serialized[knob] = knob_anim + else: + node_serialized[knob] = node[knob].value() + + return node_serialized diff --git a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py index 536abf5ba4..5f29837d80 100644 --- a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py +++ b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py @@ -38,6 +38,10 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): tags = instance.data.get("tags", None) clip = instance.data["item"] asset = instance.data.get("asset") + sequence = context.data['activeSequence'] + width = int(sequence.format().width()) + height = int(sequence.format().height()) + pixel_aspect = sequence.format().pixelAspect() # build data for inner nukestudio project property data = { @@ -157,6 +161,9 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): "asset": asset, "hierarchy": hierarchy, "parents": parents, + "width": width, + "height": height, + "pixelAspect": pixel_aspect, "tasks": instance.data["tasks"] }) @@ -191,7 +198,7 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): def process(self, context): instances = context[:] - sequence = context.data['activeSequence'] + # create hierarchyContext attr if context has none temp_context = {} @@ -216,6 +223,9 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): instance.data["parents"] = s_asset_data["parents"] instance.data["hierarchy"] = s_asset_data["hierarchy"] instance.data["tasks"] = s_asset_data["tasks"] + instance.data["width"] = s_asset_data["width"] + instance.data["height"] = s_asset_data["height"] + instance.data["pixelAspect"] = s_asset_data["pixelAspect"] # adding frame start if any on instance start_frame = s_asset_data.get("startingFrame") @@ -265,16 +275,10 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): # adding SourceResolution if Tag was present if instance.data.get("main"): - width = int(sequence.format().width()) - height = int(sequence.format().height()) - pixel_aspect = sequence.format().pixelAspect() - self.log.info("Sequence Width,Height,PixelAspect are: `{0},{1},{2}`".format( - width, height, pixel_aspect)) - in_info['custom_attributes'].update({ - "resolutionWidth": width, - "resolutionHeight": height, - "pixelAspect": pixel_aspect + "resolutionWidth": instance.data["width"], + "resolutionHeight": instance.data["height"], + "pixelAspect": instance.data["pixelAspect"] }) in_info['tasks'] = instance.data['tasks'] diff --git a/pype/plugins/nukestudio/publish/collect_plates.py b/pype/plugins/nukestudio/publish/collect_plates.py index 9843307f14..7f6f4138cb 100644 --- a/pype/plugins/nukestudio/publish/collect_plates.py +++ b/pype/plugins/nukestudio/publish/collect_plates.py @@ -66,11 +66,14 @@ class CollectPlates(api.InstancePlugin): item = instance.data["item"] width = int(item.source().mediaSource().width()) height = int(item.source().mediaSource().height()) - self.log.info("Source Width and Height are: `{0} x {1}`".format( - width, height)) + pixel_aspect = int(item.source().mediaSource().pixelAspect()) + + self.log.info("Source Width and Height are: `{0} x {1} : {2}`".format( + width, height, pixel_aspect)) data.update({ "width": width, - "height": height + "height": height, + "pixelAspect": pixel_aspect }) self.log.debug("Creating instance with name: {}".format(data["name"])) @@ -123,7 +126,7 @@ class CollectPlatesData(api.InstancePlugin): transfer_data = [ "handleStart", "handleEnd", "sourceIn", "sourceOut", "frameStart", "frameEnd", "sourceInH", "sourceOutH", "clipIn", "clipOut", - "clipInH", "clipOutH", "asset", "track", "version" + "clipInH", "clipOutH", "asset", "track", "version", "width", "height", "pixelAspect" ] # pass data to version @@ -133,6 +136,7 @@ class CollectPlatesData(api.InstancePlugin): version_data.update({ "handles": version_data['handleStart'], "colorspace": item.sourceMediaColourTransform(), + "colorspaceScript": instance.context.data["colorspace"], "families": [f for f in families if 'ftrack' not in f], "subset": name, "fps": instance.context.data["fps"] diff --git a/pype/plugins/nukestudio/publish/collect_selection.py b/pype/plugins/nukestudio/publish/collect_selection.py index ec8d513de8..28a529d560 100644 --- a/pype/plugins/nukestudio/publish/collect_selection.py +++ b/pype/plugins/nukestudio/publish/collect_selection.py @@ -14,12 +14,4 @@ class CollectSelection(pyblish.api.ContextPlugin): self.log.debug("selection: {}".format(selection)) - # if not selection: - # self.log.debug( - # "Nothing is selected. Collecting all items from sequence " - # "\"{}\"".format(hiero.ui.activeSequence()) - # ) - # for track in hiero.ui.activeSequence().items(): - # selection.extend(track.items()) - context.data["selection"] = selection diff --git a/pype/plugins/nukestudio/publish/collect_shots.py b/pype/plugins/nukestudio/publish/collect_shots.py index 9fc14536fb..c1fcf05b89 100644 --- a/pype/plugins/nukestudio/publish/collect_shots.py +++ b/pype/plugins/nukestudio/publish/collect_shots.py @@ -1,7 +1,7 @@ from pyblish import api -class CollectShots(api.ContextPlugin): +class CollectShots(api.InstancePlugin): """Collect Shot from Clip.""" # Run just before CollectClipSubsets @@ -10,39 +10,39 @@ class CollectShots(api.ContextPlugin): hosts = ["nukestudio"] families = ["clip"] - def process(self, context): - for instance in context[:]: - # Exclude non-tagged instances. - tagged = False - for tag in instance.data["tags"]: - if tag["name"].lower() == "hierarchy": - tagged = True + def process(self, instance): + self.log.debug( + "Skipping \"{}\" because its not tagged with " + "\"Hierarchy\"".format(instance)) + # Exclude non-tagged instances. + tagged = False + for tag in instance.data["tags"]: + if tag["name"].lower() == "hierarchy": + tagged = True - if not tagged: - self.log.debug( - "Skipping \"{}\" because its not tagged with " - "\"Hierarchy\"".format(instance) - ) - continue - - # Collect data. - data = {} - for key, value in instance.data.iteritems(): - data[key] = value - - data["family"] = "shot" - data["families"] = [] - - data["subset"] = data["family"] + "Main" - - data["name"] = data["subset"] + "_" + data["asset"] - - data["label"] = data["asset"] + " - " + data["subset"] + " - tasks: {} - assetbuilds: {}".format( - data["tasks"], [x["name"] for x in data.get("assetbuilds", [])] + if not tagged: + self.log.debug( + "Skipping \"{}\" because its not tagged with " + "\"Hierarchy\"".format(instance) ) + return - # Create instance. - self.log.debug("Creating instance with: {}".format(data["name"])) - instance.context.create_instance(**data) + # Collect data. + data = {} + for key, value in instance.data.iteritems(): + data[key] = value - self.log.debug("_ context: {}".format(context[:])) + data["family"] = "shot" + data["families"] = [] + + data["subset"] = data["family"] + "Main" + + data["name"] = data["subset"] + "_" + data["asset"] + + data["label"] = data["asset"] + " - " + data["subset"] + " - tasks: {} - assetbuilds: {}".format( + data["tasks"], [x["name"] for x in data.get("assetbuilds", [])] + ) + + # Create instance. + self.log.debug("Creating instance with: {}".format(data["name"])) + instance.context.create_instance(**data) diff --git a/pype/plugins/nukestudio/publish/extract_effects.py b/pype/plugins/nukestudio/publish/extract_effects.py new file mode 100644 index 0000000000..5e2d95d943 --- /dev/null +++ b/pype/plugins/nukestudio/publish/extract_effects.py @@ -0,0 +1,231 @@ +# from pype import plugins +import os +import json +import re +import pyblish.api +import tempfile +from avalon import io, api + +class ExtractVideoTracksLuts(pyblish.api.InstancePlugin): + """Collect video tracks effects into context.""" + + order = pyblish.api.ExtractorOrder + label = "Export Soft Lut Effects" + families = ["lut"] + + def process(self, instance): + item = instance.data["item"] + effects = instance.data.get("effectTrackItems") + + instance.data["families"] = [f for f in instance.data.get("families", []) if f not in ["lut"]] + + self.log.debug("___ instance.data[families]: `{}`".format(instance.data["families"])) + + # skip any without effects + if not effects: + return + + subset = instance.data.get("subset") + subset_split = re.findall(r'[A-Z][^A-Z]*', subset) + + if len(subset_split) > 0: + root_name = subset.replace(subset_split[0], "") + subset_split.insert(0, root_name.capitalize()) + + subset_split.insert(0, "lut") + + self.log.debug("creating staging dir") + # staging_dir = self.staging_dir(instance) + + # TODO: only provisory will be replace by function + staging_dir = instance.data.get('stagingDir', None) + + if not staging_dir: + staging_dir = os.path.normpath( + tempfile.mkdtemp(prefix="pyblish_tmp_") + ) + instance.data['stagingDir'] = staging_dir + + self.log.debug("creating staging dir: `{}`".format(staging_dir)) + + transfers = list() + if "transfers" not in instance.data: + instance.data["transfers"] = list() + + name = "".join(subset_split) + ext = "json" + file = name + "." + ext + + # create new instance and inherit data + data = {} + for key, value in instance.data.iteritems(): + data[key] = value + + # change names + data["subset"] = name + data["family"] = "lut" + data["families"] = [] + data["name"] = data["subset"] + "_" + data["asset"] + data["label"] = "{} - {} - ({})".format( + data['asset'], data["subset"], os.path.splitext(file)[1] + ) + data["source"] = data["sourcePath"] + + # create new instance + instance = instance.context.create_instance(**data) + + dst_dir = self.resource_destination_dir(instance) + + # change paths in effects to files + for k, effect in effects["effects"].items(): + trn = self.copy_linked_files(effect, dst_dir) + if trn: + transfers.append((trn[0], trn[1])) + + instance.data["transfers"].extend(transfers) + self.log.debug("_ transfers: `{}`".format( + instance.data["transfers"])) + + # create representations + instance.data["representations"] = list() + + transfer_data = [ + "handleStart", "handleEnd", "sourceIn", "sourceOut", + "frameStart", "frameEnd", "sourceInH", "sourceOutH", + "clipIn", "clipOut", "clipInH", "clipOutH", "asset", "track", + "version" + ] + + # pass data to version + version_data = dict() + version_data.update({k: instance.data[k] for k in transfer_data}) + + # add to data of representation + version_data.update({ + "handles": version_data['handleStart'], + "colorspace": item.sourceMediaColourTransform(), + "colorspaceScript": instance.context.data["colorspace"], + "families": ["plate", "lut"], + "subset": name, + "fps": instance.context.data["fps"] + }) + instance.data["versionData"] = version_data + + representation = { + 'files': file, + 'stagingDir': staging_dir, + 'name': "lut" + ext.title(), + 'ext': ext + } + instance.data["representations"].append(representation) + + self.log.debug("_ representations: `{}`".format( + instance.data["representations"])) + + self.log.debug("_ version_data: `{}`".format( + instance.data["versionData"])) + + with open(os.path.join(staging_dir, file), "w") as outfile: + outfile.write(json.dumps(effects, indent=4, sort_keys=True)) + + return + + def copy_linked_files(self, effect, dst_dir): + for k, v in effect["node"].items(): + if k in "file" and v is not '': + base_name = os.path.basename(v) + dst = os.path.join(dst_dir, base_name).replace("\\", "/") + + # add it to the json + effect["node"][k] = dst + return (v, dst) + + def resource_destination_dir(self, instance): + anatomy = instance.context.data['anatomy'] + self.create_destination_template(instance, anatomy) + + return os.path.join( + instance.data["assumedDestination"], + "resources" + ) + + def create_destination_template(self, instance, anatomy): + """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) + """ + + # get all the stuff from the database + subset_name = instance.data["subset"] + self.log.info(subset_name) + asset_name = instance.data["asset"] + project_name = api.Session["AVALON_PROJECT"] + a_template = anatomy.templates + + project = io.find_one({"type": "project", + "name": project_name}, + projection={"config": True, "data": True}) + + template = a_template['publish']['path'] + # 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 += version["name"] + + if instance.data.get('version'): + version_number = int(instance.data.get('version')) + + padding = int(a_template['render']['padding']) + + hierarchy = asset['data']['parents'] + if hierarchy: + # hierarchy = os.path.sep.join(hierarchy) + hierarchy = "/".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, + "frame": ('#' * padding), + "version": version_number, + "hierarchy": hierarchy, + "representation": "TEMP"} + + instance.data["assumedTemplateData"] = template_data + self.log.info(template_data) + instance.data["template"] = template + # We take the parent folder of representation 'filepath' + instance.data["assumedDestination"] = os.path.dirname( + anatomy.format(template_data)["publish"]["path"] + ) diff --git a/setup/nukestudio/hiero_plugin_path/Icons/1_add_handles_end.png b/setup/nukestudio/hiero_plugin_path/Icons/1_add_handles_end.png index 31c41d1ac6..4561745d66 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/1_add_handles_end.png and b/setup/nukestudio/hiero_plugin_path/Icons/1_add_handles_end.png differ diff --git a/setup/nukestudio/hiero_plugin_path/Icons/2_add_handles.png b/setup/nukestudio/hiero_plugin_path/Icons/2_add_handles.png index ab911c5ebc..bb4c1802aa 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/2_add_handles.png and b/setup/nukestudio/hiero_plugin_path/Icons/2_add_handles.png differ diff --git a/setup/nukestudio/hiero_plugin_path/Icons/3D.png b/setup/nukestudio/hiero_plugin_path/Icons/3D.png index 4ace8911df..2de7a72775 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/3D.png and b/setup/nukestudio/hiero_plugin_path/Icons/3D.png differ diff --git a/setup/nukestudio/hiero_plugin_path/Icons/3_add_handles_start.png b/setup/nukestudio/hiero_plugin_path/Icons/3_add_handles_start.png index 4cdc09b541..c98e4f74f1 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/3_add_handles_start.png and b/setup/nukestudio/hiero_plugin_path/Icons/3_add_handles_start.png differ diff --git a/setup/nukestudio/hiero_plugin_path/Icons/4_2D.png b/setup/nukestudio/hiero_plugin_path/Icons/4_2D.png index 418272517f..18555698fe 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/4_2D.png and b/setup/nukestudio/hiero_plugin_path/Icons/4_2D.png differ diff --git a/setup/nukestudio/hiero_plugin_path/Icons/edit.png b/setup/nukestudio/hiero_plugin_path/Icons/edit.png index e0ba3c102f..97e42054e7 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/edit.png and b/setup/nukestudio/hiero_plugin_path/Icons/edit.png differ diff --git a/setup/nukestudio/hiero_plugin_path/Icons/fusion.png b/setup/nukestudio/hiero_plugin_path/Icons/fusion.png index 208c1279cf..2e498edd69 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/fusion.png and b/setup/nukestudio/hiero_plugin_path/Icons/fusion.png differ diff --git a/setup/nukestudio/hiero_plugin_path/Icons/hierarchy.png b/setup/nukestudio/hiero_plugin_path/Icons/hierarchy.png index 68ea352885..6acf39ced5 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/hierarchy.png and b/setup/nukestudio/hiero_plugin_path/Icons/hierarchy.png differ diff --git a/setup/nukestudio/hiero_plugin_path/Icons/houdini.png b/setup/nukestudio/hiero_plugin_path/Icons/houdini.png index 128eac262a..d8c842dd17 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/houdini.png and b/setup/nukestudio/hiero_plugin_path/Icons/houdini.png differ diff --git a/setup/nukestudio/hiero_plugin_path/Icons/lense.png b/setup/nukestudio/hiero_plugin_path/Icons/lense.png index 2eb2da982f..255b1753ed 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/lense.png and b/setup/nukestudio/hiero_plugin_path/Icons/lense.png differ diff --git a/setup/nukestudio/hiero_plugin_path/Icons/lense1.png b/setup/nukestudio/hiero_plugin_path/Icons/lense1.png index f76354f48c..1ad1264807 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/lense1.png and b/setup/nukestudio/hiero_plugin_path/Icons/lense1.png differ diff --git a/setup/nukestudio/hiero_plugin_path/Icons/maya.png b/setup/nukestudio/hiero_plugin_path/Icons/maya.png index 7dd1453c60..fcfa47ae4f 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/maya.png and b/setup/nukestudio/hiero_plugin_path/Icons/maya.png differ diff --git a/setup/nukestudio/hiero_plugin_path/Icons/nuke.png b/setup/nukestudio/hiero_plugin_path/Icons/nuke.png index 9d9dc4104c..107796914b 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/nuke.png and b/setup/nukestudio/hiero_plugin_path/Icons/nuke.png differ diff --git a/setup/nukestudio/hiero_plugin_path/Icons/resolution.png b/setup/nukestudio/hiero_plugin_path/Icons/resolution.png index 9904a60532..83803fc36d 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/resolution.png and b/setup/nukestudio/hiero_plugin_path/Icons/resolution.png differ diff --git a/setup/nukestudio/hiero_plugin_path/Icons/retiming.png b/setup/nukestudio/hiero_plugin_path/Icons/retiming.png index 4487ac0422..1c6f22e02c 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/retiming.png and b/setup/nukestudio/hiero_plugin_path/Icons/retiming.png differ diff --git a/setup/nukestudio/hiero_plugin_path/Icons/review.png b/setup/nukestudio/hiero_plugin_path/Icons/review.png index 49f28c492c..0d894b6987 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/review.png and b/setup/nukestudio/hiero_plugin_path/Icons/review.png differ diff --git a/setup/nukestudio/hiero_plugin_path/Icons/volume.png b/setup/nukestudio/hiero_plugin_path/Icons/volume.png index 47119dc98b..e5e1200653 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/volume.png and b/setup/nukestudio/hiero_plugin_path/Icons/volume.png differ diff --git a/setup/nukestudio/hiero_plugin_path/Icons/z_layer_bg.png b/setup/nukestudio/hiero_plugin_path/Icons/z_layer_bg.png index d01fe683e5..51742b5df2 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/z_layer_bg.png and b/setup/nukestudio/hiero_plugin_path/Icons/z_layer_bg.png differ diff --git a/setup/nukestudio/hiero_plugin_path/Icons/z_layer_fg.png b/setup/nukestudio/hiero_plugin_path/Icons/z_layer_fg.png index a1d5751622..01e5f4f816 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/z_layer_fg.png and b/setup/nukestudio/hiero_plugin_path/Icons/z_layer_fg.png differ diff --git a/setup/nukestudio/hiero_plugin_path/Icons/z_layer_main.png b/setup/nukestudio/hiero_plugin_path/Icons/z_layer_main.png index 0fe806d86e..0ffb939a7f 100644 Binary files a/setup/nukestudio/hiero_plugin_path/Icons/z_layer_main.png and b/setup/nukestudio/hiero_plugin_path/Icons/z_layer_main.png differ