From 906471e74677f9a4ab164a9929c5cea642f08783 Mon Sep 17 00:00:00 2001 From: Mike Brainerd Date: Thu, 31 Aug 2017 17:09:32 -0700 Subject: [PATCH 1/4] added ability to set tank_channel and use_node_name in environment file --- info.yml | 5 +++++ python/tk_nuke_writenode/handler.py | 33 ++++++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/info.yml b/info.yml index c4802f5..8623a85 100644 --- a/info.yml +++ b/info.yml @@ -48,6 +48,11 @@ configuration: type: dict tank_type: type: tank_type + tank_channel: + type: str + use_node_name: + type: bool + default_value: False render_template: type: template fields: context, version, SEQ, [channel], [output], [name], [width], [height], [eye], [YYYY], [MM], [DD], * diff --git a/python/tk_nuke_writenode/handler.py b/python/tk_nuke_writenode/handler.py index a40313b..ee2fff3 100644 --- a/python/tk_nuke_writenode/handler.py +++ b/python/tk_nuke_writenode/handler.py @@ -15,6 +15,7 @@ import datetime import base64 import re +import functools import nuke import nukescripts @@ -22,6 +23,7 @@ import tank from tank import TankError from tank.platform import constants +from tank.platform.qt import QtCore # Special exception raised when the work file cannot be resolved. class TkComputePathError(TankError): @@ -60,6 +62,7 @@ def __init__(self, app): # flags to track when the render and proxy paths are being updated. self.__is_updating_render_path = False self.__is_updating_proxy_path = False + self.__nuke_10_setup_timer = None self.populate_profiles_from_settings() @@ -582,7 +585,25 @@ def on_node_created_gizmo_callback(self): be when the node is created for the first time or when it is loaded or imported/pasted from an existing script. """ - self.__setup_new_node(nuke.thisNode()) + # This is unfortunate. In Nuke 10.0 we have a problem whereby Nuke + # is sometimes triggering this callback at a time when its root + # node isn't completely initialized. As a result, we get some bogus + # noise in the form of ValueErrors complaining about the lack of + # a PythonObject attached to the node. The not-ideal solution is to + # delay the callback by a short period of time -- 100 milliseconds -- + # before allowing it to actually be called. This gives Nuke enough + # time to complete its root-node initialization and all is well. + if not self.__nuke_10_setup_timer: + self.__nuke_10_setup_timer = QtCore.QTimer() + self.__nuke_10_setup_timer.setSingleShot(True) + self.__nuke_10_setup_timer.timeout.connect( + functools.partial( + self.__setup_new_node, + nuke.thisNode(), + ), + ) + + self.__nuke_10_setup_timer.start(100) def on_compute_path_gizmo_callback(self): """ @@ -1019,6 +1040,8 @@ def __set_profile(self, node, profile_name, reset_all_settings=False): file_type = profile["file_type"] file_settings = profile["settings"] tile_color = profile["tile_color"] + use_node_name = profile["use_node_name"] + tank_channel = profile["tank_channel"] promote_write_knobs = profile.get("promote_write_knobs", []) # Make sure any invalid entries are removed from the profile list: @@ -1029,7 +1052,11 @@ def __set_profile(self, node, profile_name, reset_all_settings=False): # update both the list and the cached value for profile name: self.__update_knob_value(node, "profile_name", profile_name) self.__update_knob_value(node, "tk_profile_list", profile_name) - + + # update the default tank channel + self.__update_knob_value(node, "tk_use_name_as_channel", use_node_name) + self.__update_knob_value(node, "tank_channel", tank_channel) + # set the format self.__populate_format_settings( node, @@ -2003,4 +2030,4 @@ def __on_user_create(self): - \ No newline at end of file + From 4edda18e0d7eb2c1ad28201b3cdb3109cda72419 Mon Sep 17 00:00:00 2001 From: Mike Brainerd Date: Thu, 14 Sep 2017 20:05:12 -0700 Subject: [PATCH 2/4] fix for exposed write node parameters not being preserved #105729 --- python/tk_nuke_writenode/handler.py | 39 +++++++++++------------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/python/tk_nuke_writenode/handler.py b/python/tk_nuke_writenode/handler.py index ee2fff3..df9abf1 100644 --- a/python/tk_nuke_writenode/handler.py +++ b/python/tk_nuke_writenode/handler.py @@ -1054,9 +1054,10 @@ def __set_profile(self, node, profile_name, reset_all_settings=False): self.__update_knob_value(node, "tk_profile_list", profile_name) # update the default tank channel - self.__update_knob_value(node, "tk_use_name_as_channel", use_node_name) - self.__update_knob_value(node, "tank_channel", tank_channel) - + if reset_all_settings: + self.__update_knob_value(node, "tk_use_name_as_channel", use_node_name) + self.__update_knob_value(node, "tank_channel", tank_channel) + # set the format self.__populate_format_settings( node, @@ -1222,6 +1223,16 @@ def __populate_format_settings( write_node = node.node(TankWriteNodeHandler.WRITE_NODE_NAME) promoted_write_knobs = promoted_write_knobs or [] + # set the file_type + write_node.knob("file_type").setValue(file_type) + + # and read it back to check that the value is what we expect + if write_node.knob("file_type").value() != file_type: + self._app.log_error("Shotgun write node configuration refers to an invalid file " + "format '%s'! Reverting to auto-detect mode instead." % file_type) + write_node.knob("file_type").setValue(" ") + return + # If we're not resetting everything, then we need to try and # make sure that the settings that the user made to the internal # write knobs are retained. The reason for this is that promoted @@ -1246,19 +1257,8 @@ def __populate_format_settings( filtered_settings = [] for setting in re.split(r"\n", knob_settings): if not setting.startswith("file ") and not setting.startswith("proxy "): - filtered_settings.append(setting) - write_node.readKnobs(r"\n".join(filtered_settings)) + write_node.readKnobs(setting) self.reset_render_path(node) - - # set the file_type - write_node.knob("file_type").setValue(file_type) - - # and read it back to check that the value is what we expect - if write_node.knob("file_type").value() != file_type: - self._app.log_error("Shotgun write node configuration refers to an invalid file " - "format '%s'! Reverting to auto-detect mode instead." % file_type) - write_node.knob("file_type").setValue(" ") - return # get a list of the settings we shouldn't update: knobs_to_skip = [] @@ -2022,12 +2022,3 @@ def __on_user_create(self): # populate the initial output name based on the render template: render_template = self.get_render_template(node) self.__populate_initial_output_name(render_template, node) - - - - - - - - - From 2491b37764324176e36453b04ec80a58408bc5d1 Mon Sep 17 00:00:00 2001 From: Mike Brainerd Date: Tue, 3 Oct 2017 11:33:15 -0700 Subject: [PATCH 3/4] merge from shotgun latest --- python/tk_nuke_writenode/handler.py | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/python/tk_nuke_writenode/handler.py b/python/tk_nuke_writenode/handler.py index df9abf1..e9b7e14 100644 --- a/python/tk_nuke_writenode/handler.py +++ b/python/tk_nuke_writenode/handler.py @@ -15,7 +15,6 @@ import datetime import base64 import re -import functools import nuke import nukescripts @@ -23,7 +22,6 @@ import tank from tank import TankError from tank.platform import constants -from tank.platform.qt import QtCore # Special exception raised when the work file cannot be resolved. class TkComputePathError(TankError): @@ -62,7 +60,6 @@ def __init__(self, app): # flags to track when the render and proxy paths are being updated. self.__is_updating_render_path = False self.__is_updating_proxy_path = False - self.__nuke_10_setup_timer = None self.populate_profiles_from_settings() @@ -585,25 +582,7 @@ def on_node_created_gizmo_callback(self): be when the node is created for the first time or when it is loaded or imported/pasted from an existing script. """ - # This is unfortunate. In Nuke 10.0 we have a problem whereby Nuke - # is sometimes triggering this callback at a time when its root - # node isn't completely initialized. As a result, we get some bogus - # noise in the form of ValueErrors complaining about the lack of - # a PythonObject attached to the node. The not-ideal solution is to - # delay the callback by a short period of time -- 100 milliseconds -- - # before allowing it to actually be called. This gives Nuke enough - # time to complete its root-node initialization and all is well. - if not self.__nuke_10_setup_timer: - self.__nuke_10_setup_timer = QtCore.QTimer() - self.__nuke_10_setup_timer.setSingleShot(True) - self.__nuke_10_setup_timer.timeout.connect( - functools.partial( - self.__setup_new_node, - nuke.thisNode(), - ), - ) - - self.__nuke_10_setup_timer.start(100) + self.__setup_new_node(nuke.thisNode()) def on_compute_path_gizmo_callback(self): """ From 2dcc329c61ef445c9cf12cdd6dad4991dfa89933 Mon Sep 17 00:00:00 2001 From: Mike Brainerd Date: Tue, 3 Oct 2017 17:39:01 -0700 Subject: [PATCH 4/4] #105729: Imported TankWrite nodes are losing settings --- python/tk_nuke_writenode/handler.py | 135 ++++++++++++++++------------ 1 file changed, 77 insertions(+), 58 deletions(-) diff --git a/python/tk_nuke_writenode/handler.py b/python/tk_nuke_writenode/handler.py index e9b7e14..16aa82c 100644 --- a/python/tk_nuke_writenode/handler.py +++ b/python/tk_nuke_writenode/handler.py @@ -954,7 +954,7 @@ def set_path_knob(name, value): set_path_knob("path_filename", file_name) - def __apply_cached_file_format_settings(self, node): + def __apply_cached_file_format_settings(self, node, promoted_knobs=[]): """ Apply the file_type and settings that have been cached on the node to the internal Write node. This mechanism is used when the settings can't be retrieved from the @@ -974,9 +974,9 @@ def __apply_cached_file_format_settings(self, node): except Exception, e: self._app.log_warning("Failed to extract cached file settings from node '%s' - %s" % node.name(), e) - + # update the node: - self.__populate_format_settings(node, file_type, file_settings) + self.__populate_format_settings(node, file_type, file_settings, False, promoted_knobs) def __set_profile(self, node, profile_name, reset_all_settings=False): @@ -999,7 +999,7 @@ def __set_profile(self, node, profile_name, reset_all_settings=False): # get the profile details: profile = self._profiles.get(profile_name) if not profile: - # this shouldn't really every happen! + # this shouldn't really ever happen! self._app.log_warning("Failed to find a write node profile called '%s' for node '%s'!" % profile_name, node.name()) # at the very least, try to restore the file format settings from the cached values: @@ -1010,7 +1010,7 @@ def __set_profile(self, node, profile_name, reset_all_settings=False): # keep track of the old profile name: old_profile_name = node.knob("profile_name").value() - + # pull settings from profile: render_template = self._app.get_template_by_name(profile["render_template"]) publish_template = self._app.get_template_by_name(profile["publish_template"]) @@ -1023,6 +1023,11 @@ def __set_profile(self, node, profile_name, reset_all_settings=False): tank_channel = profile["tank_channel"] promote_write_knobs = profile.get("promote_write_knobs", []) + # If the profile hasn't changed, just apply cached file format settings + if profile_name == old_profile_name: + self.__apply_cached_file_format_settings(node, promote_write_knobs) + return + # Make sure any invalid entries are removed from the profile list: list_profiles = node.knob("tk_profile_list").values() if list_profiles != self._profile_names: @@ -1032,11 +1037,25 @@ def __set_profile(self, node, profile_name, reset_all_settings=False): self.__update_knob_value(node, "profile_name", profile_name) self.__update_knob_value(node, "tk_profile_list", profile_name) - # update the default tank channel - if reset_all_settings: - self.__update_knob_value(node, "tk_use_name_as_channel", use_node_name) - self.__update_knob_value(node, "tank_channel", tank_channel) - + # Save the old output knob values + old_use_node_name = node.knob(TankWriteNodeHandler.USE_NAME_AS_OUTPUT_KNOB_NAME).value() + old_output_name = node.knob(TankWriteNodeHandler.OUTPUT_KNOB_NAME).value() + + output_name = tank_channel + if use_node_name: + # Ensure that the output name matches the node name if + # that option is enabled on the node. This is primarily + # going to handle the situation where a node with "use name as + # output name" enabled is copied and pasted. When it is + # pasted the node will get a new name to avoid a collision + # and we need to make sure we update the output name to + # match that new name. + output_name = node.knob("name").value() + + # update the output tank channel + self.__update_knob_value(node, TankWriteNodeHandler.OUTPUT_KNOB_NAME, output_name) + self.__update_knob_value(node, TankWriteNodeHandler.USE_NAME_AS_OUTPUT_KNOB_NAME, use_node_name) + # set the format self.__populate_format_settings( node, @@ -1045,56 +1064,12 @@ def __set_profile(self, node, profile_name, reset_all_settings=False): reset_all_settings, promote_write_knobs, ) - + # cache the type and settings on the root node so that # they get serialized with the script: self.__update_knob_value(node, "tk_file_type", file_type) self.__update_knob_value(node, "tk_file_type_settings", pickle.dumps(file_settings)) - # Hide the promoted knobs that might exist from the previously - # active profile. - for promoted_knob in self._promoted_knobs.get(node, []): - promoted_knob.setFlag(nuke.INVISIBLE) - - self._promoted_knobs[node] = [] - write_node = node.node(TankWriteNodeHandler.WRITE_NODE_NAME) - - # We'll use link knobs to tie our top-level knob to the write node's - # knob that we want to promote. - for i, knob_name in enumerate(promote_write_knobs): - target_knob = write_node.knob(knob_name) - if not target_knob: - self._app.log_warning("Knob %s does not exist and will not be promoted." % knob_name) - continue - - link_name = "_promoted_" + str(i) - - # We have 20 link knobs stashed away to use. If we overflow that - # then we will simply create a new link knob and deal with the - # fact that it will end up in a "User" tab in the UI. The reason - # that we store a gaggle of link knobs on the gizmo is that it's - # the only way to present the promoted knobs in the write node's - # primary tab. Adding knobs after the node exists results in them - # being shoved into a "User" tab all by themselves, which is lame. - if i > 19: - link_knob = nuke.Link_Knob(link_name) - else: - # We have to pull the link knobs from the knobs dict rather than - # by name, otherwise we'll get the link target and not the link - # itself if this is a link that was previously used. - link_knob = node.knobs()[link_name] - - link_knob.setLink(target_knob.fullyQualifiedName()) - label = target_knob.label() or knob_name - link_knob.setLabel(label) - link_knob.clearFlag(nuke.INVISIBLE) - self._promoted_knobs[node].append(link_knob) - - # Adding knobs might have caused us to jump tabs, so we will set - # back to the first tab. - if len(promote_write_knobs) > 19: - node.setTab(0) - # write the template name to the node so that we know it later self.__update_knob_value(node, "render_template", render_template.name) self.__update_knob_value(node, "publish_template", publish_template.name) @@ -1125,7 +1100,9 @@ def __set_profile(self, node, profile_name, reset_all_settings=False): # Reset the render path but only if the named profile has changed - this will only # be the case if the user has changed the profile through the UI so this will avoid # the node automatically updating without the user's knowledge. - if profile_name != old_profile_name: + # Also update if the use_node_name or tank_channel attrs have changed. + if profile_name != old_profile_name or use_node_name != old_use_node_name or \ + output_name != old_output_name: self.reset_render_path(node) def __populate_initial_output_name(self, template, node): @@ -1233,7 +1210,6 @@ def __populate_format_settings( # going to be baked into these knob settings. If we don't, the baked-out # paths will replace the expressions that we have hooked up for those # knobs. - filtered_settings = [] for setting in re.split(r"\n", knob_settings): if not setting.startswith("file ") and not setting.startswith("proxy "): write_node.readKnobs(setting) @@ -1270,6 +1246,49 @@ def __populate_format_settings( self._app.log_error("Could not set %s file format setting %s to '%s'. Instead the value was set to '%s'" % (file_type, setting_name, setting_value, knob.value())) + # Hide the promoted knobs that might exist from the previously + # active profile. + for promoted_knob in self._promoted_knobs.get(node, []): + promoted_knob.setFlag(nuke.INVISIBLE) + + self._promoted_knobs[node] = [] + + # We'll use link knobs to tie our top-level knob to the write node's + # knob that we want to promote. + for i, knob_name in enumerate(promoted_write_knobs): + target_knob = write_node.knob(knob_name) + if not target_knob: + self._app.log_warning("Knob %s does not exist and will not be promoted." % knob_name) + continue + + link_name = "_promoted_" + str(i) + + # We have 20 link knobs stashed away to use. If we overflow that + # then we will simply create a new link knob and deal with the + # fact that it will end up in a "User" tab in the UI. The reason + # that we store a gaggle of link knobs on the gizmo is that it's + # the only way to present the promoted knobs in the write node's + # primary tab. Adding knobs after the node exists results in them + # being shoved into a "User" tab all by themselves, which is lame. + if i > 19: + link_knob = nuke.Link_Knob(link_name) + else: + # We have to pull the link knobs from the knobs dict rather than + # by name, otherwise we'll get the link target and not the link + # itself if this is a link that was previously used. + link_knob = node.knobs()[link_name] + + link_knob.setLink(target_knob.fullyQualifiedName()) + label = target_knob.label() or knob_name + link_knob.setLabel(label) + link_knob.clearFlag(nuke.INVISIBLE) + self._promoted_knobs[node].append(link_knob) + + # Adding knobs might have caused us to jump tabs, so we will set + # back to the first tab. + if len(promoted_write_knobs) > 19: + node.setTab(0) + def __set_output(self, node, output_name): """ Set the output on the specified node from user interaction. @@ -1796,7 +1815,7 @@ def __setup_new_node(self, node): self.__update_knob_value(node, "tk_profile_list", current_profile_name) # and make sure the node is up-to-date with the profile: self.__set_profile(node, current_profile_name, reset_all_settings=reset_all_profile_settings) - + # ensure that the disable value properly propogates to the internal write node: write_node = node.node(TankWriteNodeHandler.WRITE_NODE_NAME) write_node["disable"].setValue(node["disable"].value())