From 8ad54a4e5b298da31d2c2f0dcd31234f133802c9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 29 Jul 2019 09:19:02 +0200 Subject: [PATCH 1/9] fix(nuke): setting resolution problem, _tool to _node name change --- pype/nuke/lib.py | 49 ++++++++++++++++++++---------------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index a9aac47228..f6f6767163 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -43,7 +43,7 @@ def checkInventoryVersions(): container = avalon.nuke.parse_container(each) if container: - node = container["_tool"] + node = container["_node"] avalon_knob_data = get_avalon_knob_data(node) # get representation from io @@ -403,8 +403,8 @@ def reset_frame_range_handles(): # adding handle_start/end to root avalon knob if not avalon.nuke.set_avalon_knob_data(root, { - "handle_start": handle_start, - "handle_end": handle_end + "handle_start": int(handle_start), + "handle_end": int(handle_end) }): log.warning("Cannot set Avalon knob to Root node!") @@ -425,34 +425,25 @@ def reset_resolution(): asset = api.Session["AVALON_ASSET"] asset = io.find_one({"name": asset, "type": "asset"}) - try: - width = asset.get('data', {}).get('resolution_width', 1920) - height = asset.get('data', {}).get('resolution_height', 1080) - pixel_aspect = asset.get('data', {}).get('pixel_aspect', 1) - bbox = asset.get('data', {}).get('crop', "0.0.1920.1080") + width = asset.get('data', {}).get('resolution_width') + height = asset.get('data', {}).get('resolution_height') + pixel_aspect = asset.get('data', {}).get('pixel_aspect') - if bbox not in "0.0.1920.1080": - try: - x, y, r, t = bbox.split(".") - except Exception as e: - x = 0 - y = 0 - r = width - t = height - bbox = None - log.error("{}: {} \nFormat:Crop need to be set with dots, example: " - "0.0.1920.1080, /nSetting to default".format(__name__, e)) - else: - bbox = None - - except KeyError: - log.warning( - "No resolution information found for \"{0}\".".format( - project["name"] - ) - ) + log.info("pixel_aspect: {}".format(pixel_aspect)) + if any(not x for x in [width, height, pixel_aspect]): + log.error("Missing set shot attributes in DB. \nContact your supervisor!. \n\nWidth: `{0}` \nHeight: `{1}` \nPixel Asspect: `{2}`".format(width, height, pixel_aspect)) return + bbox = asset.get('data', {}).get('crop') + + if bbox: + try: + x, y, r, t = bbox.split(".") + except Exception as e: + bbox = None + log.error("{}: {} \nFormat:Crop need to be set with dots, example: " + "0.0.1920.1080, /nSetting to default".format(__name__, e)) + used_formats = list() for f in nuke.formats(): if project["name"] in str(f.name()): @@ -609,7 +600,7 @@ def get_hierarchical_attr(entity, attr, default=None): # dict # """ # -# node = container["_tool"] +# node = container["_node"] # tile_color = node['tile_color'].value() # if tile_color is None: # return {} From a2c6a1b574c6514fc94fa1bf6ed90ced13491a38 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 29 Jul 2019 09:20:06 +0200 Subject: [PATCH 2/9] fix(nuke): rename _tool to _node attribute --- pype/plugins/nuke/_load_unused/load_sequence.py | 6 +++--- pype/plugins/nuke/inventory/select_containers.py | 2 +- pype/plugins/nuke/inventory/set_tool_color.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pype/plugins/nuke/_load_unused/load_sequence.py b/pype/plugins/nuke/_load_unused/load_sequence.py index 695dd0b981..f3c54db6fb 100644 --- a/pype/plugins/nuke/_load_unused/load_sequence.py +++ b/pype/plugins/nuke/_load_unused/load_sequence.py @@ -196,8 +196,8 @@ class LoadSequence(api.Loader): ls_img_sequence, update_container ) - log.info("this i can see") - node = container["_tool"] + + node = container["_node"] # TODO: prepare also for other readers img/geo/camera assert node.Class() == "Reader", "Must be Reader" @@ -245,7 +245,7 @@ class LoadSequence(api.Loader): from avalon.nuke import viewer_update_and_undo_stop - node = container["_tool"] + node = container["_node"] assert node.Class() == "Reader", "Must be Reader" with viewer_update_and_undo_stop(): diff --git a/pype/plugins/nuke/inventory/select_containers.py b/pype/plugins/nuke/inventory/select_containers.py index 339e3a4992..b420f53431 100644 --- a/pype/plugins/nuke/inventory/select_containers.py +++ b/pype/plugins/nuke/inventory/select_containers.py @@ -11,7 +11,7 @@ class SelectContainers(api.InventoryAction): import avalon.nuke - nodes = [i["_tool"] for i in containers] + nodes = [i["_node"] for i in containers] with avalon.nuke.viewer_update_and_undo_stop(): # clear previous_selection diff --git a/pype/plugins/nuke/inventory/set_tool_color.py b/pype/plugins/nuke/inventory/set_tool_color.py index 725a3f3e74..7a81444c90 100644 --- a/pype/plugins/nuke/inventory/set_tool_color.py +++ b/pype/plugins/nuke/inventory/set_tool_color.py @@ -20,7 +20,7 @@ # # # Get tool color # first = containers[0] -# tool = first["_tool"] +# tool = first["_node"] # color = tool.TileColor # # if color is not None: @@ -40,7 +40,7 @@ # rgb_f_table = {"R": rgb_f[0], "G": rgb_f[1], "B": rgb_f[2]} # # # Update tool -# tool = container["_tool"] +# tool = container["_node"] # tool.TileColor = rgb_f_table # # result.append(container) From 8b3271adb135a60da082a281f8d6f36100684875 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 29 Jul 2019 09:20:38 +0200 Subject: [PATCH 3/9] feat(nuke): adding atom_server.py for better nuke development --- setup/nuke/nuke_path/atom_server.py | 54 +++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 setup/nuke/nuke_path/atom_server.py diff --git a/setup/nuke/nuke_path/atom_server.py b/setup/nuke/nuke_path/atom_server.py new file mode 100644 index 0000000000..1742c290c1 --- /dev/null +++ b/setup/nuke/nuke_path/atom_server.py @@ -0,0 +1,54 @@ +''' + Simple socket server using threads +''' + +import socket +import sys +import threading +import StringIO +import contextlib + +import nuke + +HOST = '' +PORT = 8888 + + +@contextlib.contextmanager +def stdoutIO(stdout=None): + old = sys.stdout + if stdout is None: + stdout = StringIO.StringIO() + sys.stdout = stdout + yield stdout + sys.stdout = old + + +def _exec(data): + with stdoutIO() as s: + exec(data) + return s.getvalue() + + +def server_start(): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.bind((HOST, PORT)) + s.listen(5) + + while 1: + client, address = s.accept() + try: + data = client.recv(4096) + if data: + result = nuke.executeInMainThreadWithResult(_exec, args=(data)) + client.send(str(result)) + except SystemExit: + result = self.encode('SERVER: Shutting down...') + client.send(str(result)) + raise + finally: + client.close() + +t = threading.Thread(None, server_start) +t.setDaemon(True) +t.start() From 58703e57fbddc0ba9e9495cc0f67d80db501d909 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 29 Jul 2019 09:22:38 +0200 Subject: [PATCH 4/9] fix(nuke): moving function into avalon-core --- pype/plugins/nuke/publish/collect_instances.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/plugins/nuke/publish/collect_instances.py b/pype/plugins/nuke/publish/collect_instances.py index 7f119f9a1e..5d0ad0175f 100644 --- a/pype/plugins/nuke/publish/collect_instances.py +++ b/pype/plugins/nuke/publish/collect_instances.py @@ -3,7 +3,7 @@ import os import nuke import pyblish.api from avalon import io, api -from pype.nuke.lib import get_avalon_knob_data +from avalon.nuke.lib import get_avalon_knob_data @pyblish.api.log @@ -34,6 +34,7 @@ class CollectNukeInstances(pyblish.api.ContextPlugin): # get data from avalon knob avalon_knob_data = get_avalon_knob_data(node) + self.log.debug("avalon_knob_data: {}".format(avalon_knob_data)) if not avalon_knob_data: continue From 3b80a389c573dc1fb74f95ced7c4a14e51e3eb46 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 29 Jul 2019 09:23:22 +0200 Subject: [PATCH 5/9] feat(nuke): enabling auto version sync on publishable write nodes --- setup/nuke/nuke_path/menu.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup/nuke/nuke_path/menu.py b/setup/nuke/nuke_path/menu.py index 82438084aa..fd87c98246 100644 --- a/setup/nuke/nuke_path/menu.py +++ b/setup/nuke/nuke_path/menu.py @@ -1,3 +1,4 @@ +import atom_server from pype.nuke.lib import ( writes_version_sync, @@ -15,5 +16,6 @@ log = Logger().get_logger(__name__, "nuke") nuke.addOnScriptSave(onScriptLoad) nuke.addOnScriptLoad(checkInventoryVersions) nuke.addOnScriptSave(checkInventoryVersions) +nuke.addOnScriptSave(writes_version_sync) log.info('Automatic syncing of write file knob to script version') From afd1c3447add4903c394d66d7408759bd3fef4c7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 29 Jul 2019 09:24:19 +0200 Subject: [PATCH 6/9] fix(global): get version didnt count with `.v001` --- pype/templates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/templates.py b/pype/templates.py index 5a31e2af45..e3356e8d60 100644 --- a/pype/templates.py +++ b/pype/templates.py @@ -28,7 +28,7 @@ def get_version_from_path(file): v: version number in string ('001') """ - pattern = re.compile(r"_v([0-9]*)") + pattern = re.compile(r"v([0-9]*)") try: v = pattern.findall(file)[0] return v From 3f6af490d6fab1e27aeeb7c9d2ceed37deb56bb6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 29 Jul 2019 20:47:01 +0200 Subject: [PATCH 7/9] feat(nuke): make publishable write node as group node with imprinted data and linked properties on panel --- pype/nuke/lib.py | 127 +++++++++++++++++++++-- pype/plugins/nuke/create/create_write | 17 --- pype/plugins/nuke/create/create_write.py | 81 ++++++--------- 3 files changed, 145 insertions(+), 80 deletions(-) delete mode 100644 pype/plugins/nuke/create/create_write diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index f6f6767163..87cff9bc72 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -18,6 +18,7 @@ log = Logger().get_logger(__name__, "nuke") self = sys.modules[__name__] self._project = None + def onScriptLoad(): if nuke.env['LINUX']: nuke.tcl('load ffmpegReader') @@ -102,6 +103,9 @@ def writes_version_sync(): node_new_file = node_file.replace(node_version, new_version) each['file'].setValue(node_new_file) + if not os.path.isdir(os.path.dirname(node_new_file)): + log.info("path does not exist") + os.makedirs(os.path.dirname(node_new_file), 0o766) except Exception as e: log.debug( "Write node: `{}` has no version in path: {}".format(each.name(), e)) @@ -172,7 +176,32 @@ def script_name(): return nuke.root().knob('name').value() -def create_write_node(name, data): +def create_write_node(name, data, prenodes=None): + '''Creating write node which is group node + + Arguments: + name (str): name of node + data (dict): data to be imprinted + prenodes (list, optional): list of lists, definitions for nodes + to be created before write + + Example: + prenodes = [( + "NameNode", # string + "NodeClass", # string + ( # OrderDict: knob and values pairs + ("knobName", "knobValue"), + ("knobName", "knobValue") + ), + ( # list inputs + "firstPrevNodeName", + "secondPrevNodeName" + ) + ) + ] + + ''' + nuke_dataflow_writes = get_node_dataflow_preset(**data) nuke_colorspace_writes = get_node_colorspace_preset(**data) application = lib.get_application(os.environ["AVALON_APP_NAME"]) @@ -191,7 +220,7 @@ def create_write_node(name, data): # build file path to workfiles fpath = str(anatomy_filled["work"]["folder"]).replace("\\", "/") - fpath = '{work}/renders/v{version}/{subset}.{frame}.{ext}'.format( + fpath = data["fpath_template"].format( work=fpath, version=data["version"], subset=data["subset"], frame=data["frame"], ext=data["nuke_dataflow_writes"]["file_type"]) @@ -219,14 +248,89 @@ def create_write_node(name, data): log.debug(_data) _data["frame_range"] = data.get("frame_range", None) - log.info("__ _data3: {}".format(_data)) - instance = avalon.nuke.lib.add_write_node( - name, - **_data - ) - instance = avalon.nuke.lib.imprint(instance, data["avalon"]) - add_rendering_knobs(instance) - return instance + + # todo: hange this to new way + GN = nuke.createNode("Group", "name {}".format(name)) + + prev_node = None + with GN: + # creating pre-write nodes `prenodes` + if prenodes: + for name, klass, properties, set_input_to in prenodes: + # create node + now_node = nuke.createNode(klass, "name {}".format(name)) + + # add data to knob + for k, v in properties: + if k and v: + now_node[k].serValue(str(v)) + + # connect to previous node + if set_input_to: + if isinstance(set_input_to, (tuple or list)): + for i, node_name in enumerate(set_input_to): + input_node = nuke.toNode(node_name) + now_node.setInput(1, input_node) + elif isinstance(set_input_to, str): + input_node = nuke.toNode(set_input_to) + now_node.setInput(0, input_node) + else: + now_node.setInput(0, prev_node) + + # swith actual node to previous + prev_node = now_node + else: + prev_node = nuke.createNode("Input", "name rgba") + + + # creating write node + now_node = avalon.nuke.lib.add_write_node("inside_{}".format(name), + **_data + ) + write_node = now_node + # connect to previous node + now_node.setInput(0, prev_node) + + # swith actual node to previous + prev_node = now_node + + now_node = nuke.createNode("Output", "name write") + + # connect to previous node + now_node.setInput(0, prev_node) + + # imprinting group node + GN = avalon.nuke.imprint(GN, data["avalon"]) + + divider = nuke.Text_Knob('') + GN.addKnob(divider) + + add_rendering_knobs(GN) + + divider = nuke.Text_Knob('') + GN.addKnob(divider) + + # set tile color + tile_color = _data.get("tile_color", "0xff0000ff") + GN["tile_color"].setValue(tile_color) + + + # add render button + lnk = nuke.Link_Knob("Render") + lnk.makeLink(write_node.name(), "Render") + lnk.setName("Render") + GN.addKnob(lnk) + + # linking knobs to group property panel + linking_knobs = ["first", "last", "use_limit"] + for k in linking_knobs: + lnk = nuke.Link_Knob(k) + lnk.makeLink(write_node.name(), k) + lnk.setName(k.replace('_', ' ').capitalize()) + lnk.clearFlag(nuke.STARTLINE) + GN.addKnob(lnk) + + return GN def add_rendering_knobs(node): @@ -431,7 +535,8 @@ def reset_resolution(): log.info("pixel_aspect: {}".format(pixel_aspect)) if any(not x for x in [width, height, pixel_aspect]): - log.error("Missing set shot attributes in DB. \nContact your supervisor!. \n\nWidth: `{0}` \nHeight: `{1}` \nPixel Asspect: `{2}`".format(width, height, pixel_aspect)) + log.error("Missing set shot attributes in DB. \nContact your supervisor!. \n\nWidth: `{0}` \nHeight: `{1}` \nPixel Asspect: `{2}`".format( + width, height, pixel_aspect)) return bbox = asset.get('data', {}).get('crop') diff --git a/pype/plugins/nuke/create/create_write b/pype/plugins/nuke/create/create_write deleted file mode 100644 index dcb132875a..0000000000 --- a/pype/plugins/nuke/create/create_write +++ /dev/null @@ -1,17 +0,0 @@ -# type: render -# if no render type node in script then first is having in name [master] for definition of main script renderer -# colorspace setting from templates -# dataflow setting from templates - -# type: mask_render -# created with shuffle gizmo for RGB separation into davinci matte -# colorspace setting from templates -# dataflow setting from templates - -# type: prerender -# backdrop with write and read -# colorspace setting from templates -# dataflow setting from templates - -# type: geo -# dataflow setting from templates diff --git a/pype/plugins/nuke/create/create_write.py b/pype/plugins/nuke/create/create_write.py index ff1fde6638..6d325ab259 100644 --- a/pype/plugins/nuke/create/create_write.py +++ b/pype/plugins/nuke/create/create_write.py @@ -5,7 +5,7 @@ from pype.nuke import ( create_write_node ) from pype import api as pype -# from pypeapp import Logger +from pypeapp import config import nuke @@ -33,6 +33,11 @@ class CreateWriteRender(avalon.nuke.Creator): def __init__(self, *args, **kwargs): super(CreateWriteRender, self).__init__(*args, **kwargs) + self.presets = config.get_presets()['plugins']["nuke"]["create"].get( + self.__class__.__name__, {} + ) + + self.name = self.data["subset"] data = OrderedDict() @@ -44,7 +49,6 @@ class CreateWriteRender(avalon.nuke.Creator): self.data = data def process(self): - self.name = self.data["subset"] family = self.family node = 'write' @@ -58,6 +62,16 @@ class CreateWriteRender(avalon.nuke.Creator): "avalon": self.data } + if self.presets.get('fpath_template'): + self.log.info("Adding template path from preset") + write_data.update( + {"fpath_template": self.presets["fpath_template"]} + ) + else: + self.log.info("Adding template path from plugin") + write_data.update({ + "fpath_template": "{work}/renders/v{version}/{subset}.{frame}.{ext}"}) + create_write_node(self.data["subset"], write_data) return @@ -77,6 +91,9 @@ class CreateWritePrerender(avalon.nuke.Creator): def __init__(self, *args, **kwargs): super(CreateWritePrerender, self).__init__(*args, **kwargs) + self.presets = config.get_presets()['plugins']["nuke"]["create"].get( + self.__class__.__name__, {} + ) data = OrderedDict() @@ -100,56 +117,16 @@ class CreateWritePrerender(avalon.nuke.Creator): "avalon": self.data } + if self.presets.get('fpath_template'): + self.log.info("Adding template path from preset") + write_data.update( + {"fpath_template": self.presets["fpath_template"]} + ) + else: + self.log.info("Adding template path from plugin") + write_data.update({ + "fpath_template": "{work}/prerenders/{subset}/{subset}.{frame}.{ext}"}) + 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 -""" From b2f97c8606593e8e41bb7df52054ef4e6034ff91 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 29 Jul 2019 20:51:04 +0200 Subject: [PATCH 8/9] feat(ftrack): plugin for removing ftrack published components after ublishing --- .../publish/integrate_remove_components.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 pype/plugins/ftrack/publish/integrate_remove_components.py diff --git a/pype/plugins/ftrack/publish/integrate_remove_components.py b/pype/plugins/ftrack/publish/integrate_remove_components.py new file mode 100644 index 0000000000..a215ee1b97 --- /dev/null +++ b/pype/plugins/ftrack/publish/integrate_remove_components.py @@ -0,0 +1,27 @@ +import pyblish.api +import os + + +class IntegrateCleanComponentData(pyblish.api.InstancePlugin): + """ + Cleaning up thumbnail an mov files after they have been integrated + """ + + order = pyblish.api.IntegratorOrder + 0.5 + label = 'Clean component data' + families = ["ftrack"] + optional = True + active = True + + def process(self, instance): + + for comp in instance.data['representations']: + self.log.debug('component {}'.format(comp)) + + if comp.get('thumbnail') or ("thumbnail" in comp.get('tags', [])): + os.remove(comp['published_path']) + self.log.info('Thumbnail image was erased') + + elif comp.get('preview') or ("preview" in comp.get('tags', [])): + os.remove(comp['published_path']) + self.log.info('Preview mov file was erased') From 0d51a6e5bc1c66f5ed5bb84cc9f487073ec81a3a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 31 Jul 2019 02:56:06 +0200 Subject: [PATCH 9/9] fix conflict --- .../nuke/_load_unused/load_sequence.py | 252 ------------------ 1 file changed, 252 deletions(-) delete mode 100644 pype/plugins/nuke/_load_unused/load_sequence.py diff --git a/pype/plugins/nuke/_load_unused/load_sequence.py b/pype/plugins/nuke/_load_unused/load_sequence.py deleted file mode 100644 index f3c54db6fb..0000000000 --- a/pype/plugins/nuke/_load_unused/load_sequence.py +++ /dev/null @@ -1,252 +0,0 @@ -import os -import contextlib - -from avalon import api -import avalon.io as io - -from avalon.nuke import log -import nuke - - -@contextlib.contextmanager -def preserve_inputs(node, knobs): - """Preserve the node's inputs after context""" - - values = {} - for name in knobs: - try: - knob_value = node[name].vaule() - values[name] = knob_value - except ValueError: - log.warning("missing knob {} in node {}" - "{}".format(name, node['name'].value())) - - try: - yield - finally: - for name, value in values.items(): - node[name].setValue(value) - - -@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()["start_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()["start_frame"].value() - - 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() - - if relative: - shift = frame - else: - if start_at_frame: - shift = frame - if offset_frame: - shift = frame + offset_frame - - # Shifting global in will try to automatically compensate for the change - # in the "ClipTimeStart" and "HoldFirstFrame" inputs, so we preserve those - # input values to "just shift" the clip - with preserve_inputs(node, knobs=["file", - "first", - "last", - "originfirst", - "originlast", - "frame_mode", - "frame"]): - - # GlobalIn cannot be set past GlobalOut or vice versa - # so we must apply them in the order of the shift. - if start_at_frame: - node['frame_mode'].setValue("start at") - node['frame'].setValue(str(script_start + shift)) - if offset_frame: - node['frame_mode'].setValue("offset") - node['frame'].setValue(str(shift)) - - return int(shift) - - -class LoadSequence(api.Loader): - """Load image sequence into Nuke""" - - families = ["write"] - representations = ["*"] - - label = "Load sequence" - order = -10 - icon = "code-fork" - color = "orange" - - def load(self, context, name, namespace, data): - - from avalon.nuke import ( - containerise, - ls_img_sequence, - viewer_update_and_undo_stop - ) - log.info("here i am") - # 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 - ) - - node = container["_node"] - # 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["_node"] - assert node.Class() == "Reader", "Must be Reader" - - with viewer_update_and_undo_stop(): - nuke.delete(node)