diff --git a/.gitignore b/.gitignore index d2d6f36..c2d5fbe 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ nosetests.xml .mr.developer.cfg .project .pydevproject +gizmos/tab_stats.dat +python/tk_nuke_writenode/handler.py.functional diff --git a/app.py b/app.py index 53ee711..0ce0841 100644 --- a/app.py +++ b/app.py @@ -153,7 +153,9 @@ def get_node_render_template(self, node): """ Return the render template for the specified node """ - return self.__write_node_handler.get_render_template(node) + write_type = self.__write_node_handler.get_node_write_type_name(node) + + return self.__write_node_handler.get_render_template(node, write_type) def get_node_publish_template(self, node): """ @@ -204,7 +206,8 @@ def reset_node_render_path(self, node): """ self.__write_node_handler.reset_render_path(node) - def convert_to_write_nodes(self, show_warning=False): + def convert_to_write_nodes(self, selected_node=None, show_warning=False): + """ Convert all Shotgun write nodes found in the current Script to regular Nuke Write nodes. Additional toolkit information will be stored on @@ -215,6 +218,7 @@ def convert_to_write_nodes(self, show_warning=False): :param create_folders: Optional bool that sets whether the operation will create the required output folders; defaults to False """ + self.__write_node_handler.convert_sg_to_nuke_write_nodes(selected_node=None) # By default we want to convert the write nodes, unless the warning is shown and the user chooses to abort. continue_with_convert = True @@ -265,33 +269,53 @@ def convert_from_write_nodes(self, show_warning=False): if continue_with_convert: self.__write_node_handler.convert_nuke_to_sg_write_nodes() - def create_new_write_node(self, profile_name): + def create_new_write_node(self, profile_name, write_type): """ Creates a Shotgun write node using the provided profile_name. """ - self.__write_node_handler.create_new_node(profile_name) + new_node = self.__write_node_handler.create_new_node(profile_name, write_type) + + return new_node # Private methods - # def __add_write_node_commands(self, context=None): """ Creates write node menu entries for all write node configurations and the convert to and from Shotgun write node actions if configured to do so. """ context = context or self.context - - write_node_icon = os.path.join(self.disk_location, "resources", "tk2_write.png") - - for profile_name in self.__write_node_handler.profile_names: + write_type = "Version" + profile_list = [] + write_node_icon = os.path.join(self.disk_location, "resources", "tk2_write.png").replace("\\","/") + profile_set = set(self.__write_node_handler.profile_names) + + # Remove fileset types nt associated with Project + if not self.__write_node_handler.proj_info['sg_delivery_fileset']: + nuke.tprint("No fileset specified. Loading defaults...") + profile_list = self.__write_node_handler.profile_names + else: + if any(self.__write_node_handler.proj_info['sg_delivery_fileset']['name'] in s.lower() for s in self.__write_node_handler.profile_names): + if context.step['name'] != 'Roto': + match_set = {"Jpeg", self.__write_node_handler.proj_info['sg_delivery_fileset']['name'].title()} + else: + nuke.tprint("Context is " + context.step['name'] + ".") + match_set = {"Jpeg", "Exr"} + + profile_list = list(match_set.intersection(profile_set)) + else: + nuke.tprint("Profile name not in list!") + profile_list = self.__write_node_handler.profile_names + + for profile_name in profile_list: # add to toolbar menu - cb_fn = lambda pn=profile_name: self.__write_node_handler.create_new_node(pn) + cb_fn = lambda pn=profile_name,wt=write_type: self.__write_node_handler.create_new_node(pn,wt) self.engine.register_command( - "%s [Shotgun]" % profile_name, + "%s" % profile_name, cb_fn, dict( type="node", - icon=write_node_icon, context=context, + icon=write_node_icon ) ) @@ -302,7 +326,8 @@ def __add_write_node_commands(self, context=None): # as these aren't supported when converting back # todo: We should check the settings and then scan the scene to see if any SG write nodes use promoted knobs write_nodes = self.get_setting("write_nodes") - promoted_knob_write_nodes = next((a_node for a_node in write_nodes if a_node['promote_write_knobs']), None) + if write_nodes: + promoted_knob_write_nodes = next((a_node for a_node in write_nodes if a_node['promote_write_knobs']), None) if not promoted_knob_write_nodes: # no presets use promoted knobs so we are OK to register the menus. diff --git a/gizmos/WriteTank.gizmo b/gizmos/WriteTank.gizmo index 73f3eb8..19a3bb0 100644 --- a/gizmos/WriteTank.gizmo +++ b/gizmos/WriteTank.gizmo @@ -21,9 +21,9 @@ version 6.3 # # Additional knob flags # --------------------- -# These are set/removed by doing +/-FLAG_NAME, e.g. +STARTLINE to ensure the knob starts a -# new line in the property editor. A full list of flags can be found here: -# http://docs.thefoundry.co.uk/nuke/63/ndkdevguide/knobs-and-handles/knobflags.html +# These are set/removed by doing +/-FLAG_NAME, e.g. +STARTLINE to ensure the knob starts a +# new line in the property editor.A full list of flags can be found here: +# http://docs.thefoundry.co.uk/nuke/63/ndkdevguide/knobs-and-handles/knobflags.html # ########################################################################################## ########################################################################################## @@ -36,23 +36,34 @@ Gizmo { l "Shotgun Write" } addUserKnob { - 4 tk_profile_list - l "Profile" - M {} - t "Select the Profile to use for this node" - +DO_NOT_WRITE + 1 element_render_template + l "Sgtk Elements Template" + t "The Sgtk elements template associated with this node" + +INVISIBLE } addUserKnob { - 1 tank_channel - l "Output" - t "Choose an output name for this Write Node to help identify it when you have more than one output in your scene." - +STARTLINE + 1 denoise_render_template + l "Sgtk Denoise Template" + t "The Sgtk denoise template associated with this node" + +INVISIBLE } addUserKnob { - 6 tk_use_name_as_channel - l "Use node name" - t "Use the node name in place of the output name" - -STARTLINE + 1 smartvector_render_template + l "Sgtk SmartVector Template" + t "The SmartVector template associated with this node" + +INVISIBLE + } + addUserKnob { + 1 stmap_render_template + l "Sgtk STMap Template" + t "The STMap template associated with this node" + +INVISIBLE + } + addUserKnob { + 1 test_render_template + l "Sgtk Test Template" + t "The Sgtk test template associated with this node" + +INVISIBLE } addUserKnob { 6 tk_is_fully_constructed @@ -61,7 +72,7 @@ Gizmo { +DO_NOT_WRITE +STARTLINE +INVISIBLE - } + } addUserKnob { 1 profile_name l "Profile Name" @@ -103,32 +114,45 @@ Gizmo { l "Sgtk Cached Proxy Path" t "The path for this write node when rendering in proxy mode" +INVISIBLE + } + addUserKnob { + 1 channels_cache + l "" + t "Cache location for channels setting" + +STARTLINE + +INVISIBLE + } + addUserKnob { + 1 tk_channel_cache + l "" + t "Cache location for user entered text to change the output naming for write nodes" + +STARTLINE + +INVISIBLE } addUserKnob { 1 tk_last_known_script l "Last Known Script" t "The last known script this Write node was saved in - used to determine if the script is being saved as a new file or not" +INVISIBLE - } + } addUserKnob { 1 tk_file_type l "File Type" t "The file type to be used for the write node output - this cached value is used if the profile can't be determined" +INVISIBLE - } + } addUserKnob { 1 tk_file_type_settings l "File Type Settings" t "A dictionary of file type settings to be applied to the Write node - this cached value is used if the profile can't be determined" +INVISIBLE - } + } addUserKnob { 1 tk_write_node_settings l "Write Node Settings" t "A string of tcl containing a record of settings for the internal write node at save time." +INVISIBLE - } - + } addUserKnob { 26 "" l "" @@ -158,7 +182,6 @@ Gizmo { T " " t "A preview of the file location generated by the node." } - addUserKnob { 26 path_warning l " " @@ -167,16 +190,16 @@ Gizmo { +STARTLINE } addUserKnob { - 22 reset_path - l "Reset Path" - T "import nuke\nif hasattr(nuke, \"_shotgun_write_node_handler\"):\n nuke._shotgun_write_node_handler.on_reset_render_path_gizmo_callback()" + 26 files_warning + l " " + T " " + t "" +STARTLINE } - addUserKnob { - 26 "" - l "" - T " " + 22 reset_path + l "Reset Path" + T "import nuke\nif hasattr(nuke, \"_shotgun_write_node_handler\"):\n nuke._shotgun_write_node_handler.on_reset_render_path_gizmo_callback()" +STARTLINE } addUserKnob { @@ -188,17 +211,160 @@ Gizmo { } addUserKnob { 22 tk_copy_path - l "Copy Path to Clipboard" - T "import nuke\nif hasattr(nuke, \"_shotgun_write_node_handler\"):\n nuke._shotgun_write_node_handler.on_copy_path_to_clipboard_gizmo_callback()" + l "Copy Path" + T "import nuke\nif hasattr(nuke, \"_shotgun_write_node_handler\"):\n nuke._shotgun_write_node_handler.on_copy_path_to_clipboard_gizmo_callback(False, False)" + t "Copies the current render/proxy path to the clipboard" + -STARTLINE + } + addUserKnob { + 22 tk_copy_path_win + l "Copy Path - Windows" + T "import nuke\nif hasattr(nuke, \"_shotgun_write_node_handler\"):\n nuke._shotgun_write_node_handler.on_copy_path_to_clipboard_gizmo_callback(True, False)" t "Copies the current render/proxy path to the clipboard" -STARTLINE } - + addUserKnob { + 22 tk_copy_folder_win + l "Copy Folder - Windows" + T "import nuke\nif hasattr(nuke, \"_shotgun_write_node_handler\"):\n nuke._shotgun_write_node_handler.on_copy_path_to_clipboard_gizmo_callback(True, True)" + t "Copies the current render/proxy path folder to the clipboard" + -STARTLINE + } addUserKnob { 26 "" l "" +STARTLINE } + addUserKnob { + 4 write_type + l "SG Write Type" + t "Select the type of node you require this to be." + M {Version Element Denoise SmartVector STMap Test} + } + addUserKnob { + 22 check_mattes + l "Check mattes" + T " " + } + addUserKnob { + 22 write_type_info + l "Info" + T " " + } + addUserKnob { + 22 convert_to_write + l "Convert to Write Node" + T " " + t "This write type should be conerted after the Detail name is input!" + +STARTLINE + } + addUserKnob { + 1 write_type_cache + l "SG Write Type Cache" + t "The cached Write Type for this node" + +INVISIBLE + } + addUserKnob { + 1 tk_project_format_cache + l "Project Format Cache" + t "The cached option to apply the Project format" + +INVISIBLE + } + addUserKnob { + 1 tk_apply_ocio_cache + l "Apply OCIO apply Cache" + t "The cached option to apply the Project format" + +INVISIBLE + } + addUserKnob { + 25 "" + - STARTLINE + } + addUserKnob { + 4 tk_profile_list + l "Profile" + M {} + t "Select the Profile to use for this node" + -STARTLINE + +DO_NOT_WRITE + } + addUserKnob { + 1 tank_channel + l "Detail" + t "Choose an output name for this Write Node to help identify it when you have more than one output in your scene." + +STARTLINE + -INVISIBLE + } + addUserKnob { + 6 project_crop_bool + l "Project delivery format" + t "Choose to crop footage to the Project format." + +STARTLINE + -INVISIBLE + } + addUserKnob { + 6 shot_ocio_bool + l "Apply shot OCIO" + t "Choose to apply the embeded shot OCIO." + +STARTLINE + +INVISIBLE + } + addUserKnob { + 26 ocio_warning + l "Ocio Warning" + T "" + t "Flag with the artist if the OCIO is not being applied." + +STARTLINE + +INVISIBLE + } + addUserKnob { + 25 "" + - STARTLINE + } + addUserKnob { + 4 exr_datatype + l "dataype" + t "Choose EXR datatype" + M {"16 bit half" "32 bit float"} + +STARTLINE + -INVISIBLE + } + addUserKnob { + 6 auto_crop + l "autocrop" + t "Choose to crop footage to the Project format." + +STARTLINE + -INVISIBLE + } + addUserKnob { + 4 dpx_datatype + l "dataype" + t "Choose DPX datatype" + M {"8 bit" "10 bit" "12 bit" "16 bit"} + +STARTLINE + -INVISIBLE + } + addUserKnob { + 25 "" + - STARTLINE + } + addUserKnob { + 6 tk_use_name_as_channel + l "Use node name" + t "Use the node name in place of the output name" + -STARTLINE + +INVISIBLE + } + addUserKnob { + 26 version_divider + l "" + +STARTLINE + -INVISIBLE + } + addUserKnob { + 25 "" + - STARTLINE + } addUserKnob { 41 channels T Write1.channels @@ -233,7 +399,6 @@ Gizmo { 41 views T Write1.views } - addUserKnob { 26 "" l "" @@ -253,6 +418,7 @@ Gizmo { } addUserKnob { 41 Render + l "Local Render" T Write1.Render -STARTLINE } @@ -273,7 +439,6 @@ Gizmo { T Write1.use_limit -STARTLINE } - addUserKnob { 41 reading l "read file" @@ -296,12 +461,6 @@ Gizmo { T Write1.reload -STARTLINE } - - addUserKnob { - 26 "" - l "" - +STARTLINE - } addUserKnob { 41 _promoted_0 l Promoted @@ -411,7 +570,7 @@ Gizmo { addUserKnob {41 afterFrameRender l "after each frame" T Write1.afterFrameRender} addUserKnob {1 tk_after_render l "after render"} addUserKnob {41 renderProgress l "render progress" T Write1.renderProgress} - + knobChanged "import nuke\nif hasattr(nuke, \"_shotgun_write_node_handler\"):\n nuke._shotgun_write_node_handler.on_knob_changed_gizmo_callback()" onCreate "import nuke\nif hasattr(nuke, \"_shotgun_write_node_handler\"):\n nuke._shotgun_write_node_handler.on_node_created_gizmo_callback()" } @@ -421,20 +580,60 @@ Gizmo { xpos 195 ypos -74 } + Shuffle { + name internal_shuffle + disable true + } + Reformat { + name delivery_reformat + type "to format" + filter "Impulse" + box_width 800 + box_height 1 + black_outside true + resize width + pbb false + disable true + } + Crop { + name format_crop + reformat true + crop false + } + Clamp { + name matte_clamp + disable true + } + AddTimeCode { + startcode 01:00:00:01 + name project_tc + metafps False + fps 23.98 + useFrame True + frame 1 + } + OCIOColorSpace { + name shot_ocio + disable true + } + ModifyMetaData { + name content_meta_data + } Write { file "\[python __import__('nuke')._shotgun_write_node_handler.on_compute_path_gizmo_callback() if hasattr(__import__('nuke'), '_shotgun_write_node_handler') else nuke.thisParent().knob('cached_path').value()]" proxy "\[python __import__('nuke')._shotgun_write_node_handler.on_compute_proxy_path_gizmo_callback() if hasattr(__import__('nuke'), '_shotgun_write_node_handler') else nuke.thisParent().knob('tk_cached_proxy_path').value()]" beforeRender "import nuke\nif hasattr(nuke, \"_shotgun_write_node_handler\"):\n nuke._shotgun_write_node_handler.on_before_render_gizmo_callback()" afterRender "import nuke\nif hasattr(nuke, \"_shotgun_write_node_handler\"):\n nuke._shotgun_write_node_handler.on_after_render_gizmo_callback()" name Write1 + create_directories True xpos 195 - ypos -34 + ypos 65 } set N11879e50 [stack 0] Output { name Output1 xpos 195 - ypos 66 + ypos 150 } push $N11879e50 Reformat { diff --git a/info.yml b/info.yml index f9015f5..1011e56 100644 --- a/info.yml +++ b/info.yml @@ -64,6 +64,28 @@ configuration: fields: context, version, SEQ, [channel], [output], [name], [width], [height], [eye], [TankType], * allows_empty: True default_value: null + element_render_template: + type: template + fields: context, version, SEQ, [channel], [output], [name], [width], [height], [eye], * + allows_empty: True + default_value: null + denoise_render_template: + type: template + fields: context, version, SEQ, [channel], [output], [name], [width], [height], [eye], * + allows_empty: True + smartvector_render_template: + type: template + fields: context, version, SEQ, [channel], [output], [name], [width], [height], [eye], * + allows_empty: True + stmap_render_template: + type: template + fields: context, version, SEQ, [channel], [output], [name], [width], [height], [eye], * + allows_empty: True + test_render_template: + type: template + fields: context, version, SEQ, [channel], [output], [name], [width], [height], [eye], * + allows_empty: True + default_value: null tile_color: type: list values: diff --git a/python/tk_nuke_writenode/handler.py b/python/tk_nuke_writenode/handler.py index 3d9d9fa..bab6c60 100644 --- a/python/tk_nuke_writenode/handler.py +++ b/python/tk_nuke_writenode/handler.py @@ -15,13 +15,20 @@ import datetime import base64 import re +import webbrowser import nuke import nukescripts import tank from tank import TankError -from tank.platform import constants +from tank.platform.qt import QtCore + +try: + from software.nuke.nuke_python import nuke_tools as nt + ntools = nt.NukeTools() +except: + nuke.tprint("Could not load studio tools") # Special exception raised when the work file cannot be resolved. class TkComputePathError(TankError): @@ -31,11 +38,15 @@ class TankWriteNodeHandler(object): """ Handles requests and processing from a tank write node. """ - SG_WRITE_NODE_CLASS = "WriteTank" - SG_WRITE_DEFAULT_NAME = "ShotgunWrite" + SG_WRITE_DEFAULT_NAME = "Version" WRITE_NODE_NAME = "Write1" - + EMBED_META_DATA = "content_meta_data" + EMBED_SHOT_OCIO = "shot_ocio" + EMBED_DELIVERY_REFORMAT = "delivery_reformat" + EMBED_SHUFFLE = "internal_shuffle" + EMBED_FORMAT_CROP = "format_crop" + EMBED_MATTE_CLAMP = "matte_clamp" OUTPUT_KNOB_NAME = "tank_channel" USE_NAME_AS_OUTPUT_KNOB_NAME = "tk_use_name_as_channel" @@ -48,10 +59,11 @@ def __init__(self, app): """ self._app = app self._script_template = self._app.get_template("template_script_work") - + # cache the profiles: self._promoted_knobs = {} self._profile_names = [] + self._project_setting_groups = [] self._profiles = {} self.__currently_rendering_nodes = set() @@ -62,7 +74,30 @@ def __init__(self, app): self.__is_updating_proxy_path = False self.populate_profiles_from_settings() - + + self.sg = self._app.engine.shotgun + self.proj_info = self.sg.find_one("Project", + [['id', 'is', self._app.context.project['id']]], + ['name', + 'sg_frame_rate', + 'sg_data_type', + 'sg_format_width', + 'sg_format_height', + 'sg_delivery_format_width', + 'sg_delivery_format_height', + 'sg_delivery_reformat_filter', + 'sg_delivery_reformat_type', + 'sg_pixel_aspect_ratio', + 'sg_short_name', + 'sg_delivery_fileset', + 'sg_delivery_fileset_compression', + 'sg_color_space', + 'sg_project_color_management']) + self.shot_info = None + + self.ctx_info = self._app.context + self.get_shot_frame_range() + ################################################################################################ # Properties @@ -98,7 +133,13 @@ def populate_script_template(self): Sources the current context's work file template from the parent app. """ self._script_template = self._app.get_template("template_script_work") - + + def get_shot_frame_range(self): + + if self._app.context.entity['type'] == "Shot": + self.frame_range_app = self._app.engine.apps["tk-multi-setframerange"] + self.frame_range = self.frame_range_app.get_frame_range_from_shotgun() + def get_nodes(self): """ Returns a list of tank write nodes @@ -109,7 +150,18 @@ def get_nodes(self): recurseGroups = True) else: return [] - + + def get_nodes_by_class(self, class_name): + """ + Returns a list of tank write nodes + """ + if nuke.exists("root"): + return nuke.allNodes(group=nuke.root(), + filter=class_name, + recurseGroups = True) + else: + return [] + def get_node_name(self, node): """ Return the name for the specified node @@ -130,11 +182,11 @@ def get_node_tank_type(self, node): if settings: return settings["tank_type"] - def get_render_template(self, node): + def get_render_template(self, node, write_type): """ helper function. Returns the associated render template obj for a node """ - return self.__get_render_template(node) + return self.__get_render_template(node,write_type) def get_publish_template(self, node): """ @@ -142,12 +194,12 @@ def get_publish_template(self, node): """ return self.__get_publish_template(node) - def get_proxy_render_template(self, node): + def get_proxy_render_template(self, node, write_type): """ helper function. Returns the associated render proxy template obj for a node. If this hasn't been defined then it falls back to the regular render template. """ - return self.__get_render_template(node, is_proxy=True, fallback_to_render=True) + return self.__get_render_template(node, write_type, is_proxy=True, fallback_to_render=True) def get_proxy_publish_template(self, node): """ @@ -210,7 +262,7 @@ def reset_render_path(self, node): self.__update_render_path(node, force_reset=True, is_proxy=is_proxy) self.__update_render_path(node, force_reset=True, is_proxy=(not is_proxy)) - def create_new_node(self, profile_name): + def create_new_node(self, profile_name, write_type): """ Creates a new write node @@ -228,23 +280,20 @@ def create_new_node(self, profile_name): return # new node please! - node = nuke.createNode(TankWriteNodeHandler.SG_WRITE_NODE_CLASS) + node = nuke.createNode(TankWriteNodeHandler.SG_WRITE_NODE_CLASS, inpanel = False) # rename to our new default name: - existing_node_names = [n.name() for n in nuke.allNodes()] - postfix = 1 - while True: - new_name = "%s%d" % (TankWriteNodeHandler.SG_WRITE_DEFAULT_NAME, postfix) - if new_name not in existing_node_names: - node.knob("name").setValue(new_name) - break - else: - postfix += 1 + existing_node_names = [n.name() for n in nuke.allNodes(TankWriteNodeHandler.SG_WRITE_NODE_CLASS)] + + new_name = self.__ensure_unique_name(TankWriteNodeHandler.SG_WRITE_DEFAULT_NAME, + existing_node_names) + + node.knob("name").setValue(new_name) self._app.log_debug("Created Shotgun Write Node %s" % node.name()) # set the profile: - self.__set_profile(node, profile_name, reset_all_settings=True) + self.__set_profile(node, profile_name, write_type, reset_all_settings=True) return node @@ -276,9 +325,11 @@ def process_placeholder_nodes(self): n.dependencies()[0].setSelected(True) except: pass + # set the write type for creation of correct output + write_type = self.get_node_write_type_name(node) # create the node: - new_node = self.create_new_node(profile_name) + new_node = self.create_new_node(profile_name, write_type) # set the output: self.__set_output(new_node, output_name) @@ -365,8 +416,49 @@ def remove_callbacks(self): nuke.removeOnScriptLoad(self.process_placeholder_nodes, nodeClass="Root") nuke.removeOnScriptSave(self.__on_script_save) nuke.removeOnUserCreate(self.__on_user_create, nodeClass=TankWriteNodeHandler.SG_WRITE_NODE_CLASS) - - def convert_sg_to_nuke_write_nodes(self): + + def create_project_settings_group(self, node): + + # Get properties of selected node to use for new group + nodePos = (node.xpos(), node.ypos()) + existing_node_names = [n.name() for n in nuke.allNodes("Group")] + node_name = self.__ensure_unique_name("%s_Group" %(node['name'].value()), + existing_node_names) + # Primary group setup + proj_group_nodes = [] + project_group = nuke.createNode("Group", inpanel = False) + project_group['name'].setValue(node_name) + project_group_process = nuke.toNode(project_group['name'].value()) + project_group_process.begin() + input_node = nuke.createNode("Input", inpanel = False) + internal_shuffle = nuke.createNode("Shuffle", inpanel = False) + internal_shuffle['name'].setValue("internal_shuffle") + delivery_reformat = nuke.createNode("Reformat", inpanel = False) + delivery_reformat['name'].setValue("delivery_reformat") + format_crop = nuke.createNode("Crop", inpanel = False) + format_crop['name'].setValue("format_crop") + content_metadata = nuke.createNode("ModifyMetaData", inpanel = False) + content_metadata['name'].setValue("content_meta_data") + shot_ocio = nuke.createNode("OCIOColorSpace", inpanel = False) + shot_ocio['name'].setValue("shot_ocio") + matte_clamp = nuke.createNode("Clamp", inpanel = False) + matte_clamp['name'].setValue("matte_clamp") + output_node = nuke.createNode("Output", inpanel = False) + proj_group_nodes.append(internal_shuffle) + proj_group_nodes.append(delivery_reformat) + proj_group_nodes.append(format_crop) + proj_group_nodes.append(content_metadata) + proj_group_nodes.append(shot_ocio) + proj_group_nodes.append(matte_clamp) + project_group_process.end() + + project_group.setXpos(nodePos[0]) + project_group.setYpos(nodePos[1] - 30) + project_group.setSelected(False) + + return project_group + + def convert_sg_to_nuke_write_nodes(self, selected_node=None): """ Utility function to convert all Shotgun Write nodes to regular Nuke Write nodes. @@ -383,20 +475,67 @@ def convert_sg_to_nuke_write_nodes(self): """ # clear current selection: nukescripts.clear_selection_recursive() - + + sg_write_nodes =[] # get write nodes: - sg_write_nodes = self.get_nodes() + if not selected_node: + nuke.tprint("Converting all SG write nodes to Nuke write nodes.") + sg_write_nodes = self.get_nodes() + else: + nuke.tprint("Converting selected SG write nodes to Nuke write nodes.") + sg_write_nodes.append(selected_node) + for sg_wn in sg_write_nodes: - - # set as selected: + + extra_node = None sg_wn.setSelected(True) - node_name = sg_wn.name() node_pos = (sg_wn.xpos(), sg_wn.ypos()) - + # create new regular Write node: - new_wn = nuke.createNode("Write") - new_wn.setSelected(False) - + with nuke.root(): + new_wn = nuke.createNode("Write", inpanel = False ) + + if new_wn.input(0): + parent_node = new_wn.input(0) + extra_node = self.create_project_settings_group(sg_wn) + + parent_node = None + if sg_wn.input(0): + parent_node = sg_wn.input(0) + extra_node.setInput(0, parent_node) + new_wn.setInput(0, extra_node) + if not selected_node: + self._project_setting_groups.append(extra_node) + + if not selected_node: + node_name = sg_wn.name() + else: + node_name = sg_wn.name()+"_converted" + + # Embed shuffle + extra_node.node('internal_shuffle')['disable'].setValue(sg_wn.node('internal_shuffle')['disable'].value()) + # Embed reformat + extra_node.node('delivery_reformat')['disable'].setValue(sg_wn.node('delivery_reformat')['disable'].value()) + extra_node.node('delivery_reformat')['filter'].setValue(sg_wn.node('delivery_reformat')['filter'].value()) + extra_node.node('delivery_reformat')['resize'].setValue(sg_wn.node('delivery_reformat')['resize'].value()) + extra_node.node('delivery_reformat')['format'].setValue(sg_wn.node('delivery_reformat')['format'].value()) + extra_node.node('delivery_reformat')['pbb'].setValue(sg_wn.node('delivery_reformat')['pbb'].value()) + extra_node.node('delivery_reformat')['black_outside'].setValue(sg_wn.node('delivery_reformat')['black_outside'].value()) + # Embed crop + extra_node.node('format_crop')['disable'].setValue(sg_wn.node('format_crop')['disable'].value()) + extra_node.node('format_crop')['box'].setValue(sg_wn.node('format_crop')['box'].value()) + extra_node.node('format_crop')['reformat'].setValue(sg_wn.node('format_crop')['reformat'].value()) + extra_node.node('format_crop')['crop'].setValue(sg_wn.node('format_crop')['crop'].value()) + # Embed metadata + md = extra_node.node('content_meta_data')['metadata'] + md.fromScript(self.__get_metadata(sg_wn)) + # Embed OCIO + extra_node.node('shot_ocio')['in_colorspace'].setValue(sg_wn.node('shot_ocio')['in_colorspace'].value()) + extra_node.node('shot_ocio')['in_colorspace'].setValue(sg_wn.node('shot_ocio')['in_colorspace'].value()) + extra_node.node('shot_ocio')['disable'].setValue(sg_wn.node('shot_ocio')['disable'].value()) + # Embed matte + extra_node.node('matte_clamp')['disable'].setValue(sg_wn.node('matte_clamp')['disable'].value()) + # copy across file & proxy knobs (if we've defined a proxy template): new_wn["file"].setValue(sg_wn["cached_path"].evaluate()) if sg_wn["proxy_render_template"].value(): @@ -412,7 +551,7 @@ def convert_sg_to_nuke_write_nodes(self): for knob_name, knob in int_wn.knobs().iteritems(): # skip knobs we don't want to copy: if knob_name in ["file_type", "file", "proxy", "beforeRender", "afterRender", - "name", "xpos", "ypos"]: + "name", "xpos", "ypos"]: continue if knob_name in new_wn.knobs(): @@ -442,12 +581,31 @@ def convert_sg_to_nuke_write_nodes(self): knob = nuke.String_Knob("tk_output") knob.setValue(sg_wn[TankWriteNodeHandler.OUTPUT_KNOB_NAME].value()) new_wn.addKnob(knob) - + # use node name for output knob = nuke.Boolean_Knob(TankWriteNodeHandler.USE_NAME_AS_OUTPUT_KNOB_NAME) knob.setValue(sg_wn[TankWriteNodeHandler.USE_NAME_AS_OUTPUT_KNOB_NAME].value()) new_wn.addKnob(knob) - + + # channels + knob = nuke.String_Knob("tk_channel_cache") + knob.setValue(sg_wn["channels"].value()) + new_wn.addKnob(knob) + + # datatype + knob = nuke.String_Knob("tk_exr_datatype") + knob.setValue(sg_wn["exr_datatype"].value()) + new_wn.addKnob(knob) + + knob = nuke.String_Knob("tk_dpx_datatype") + knob.setValue(sg_wn["dpx_datatype"].value()) + new_wn.addKnob(knob) + + # autocrop + knob = nuke.String_Knob("tk_autocrop") + knob.setValue(str(sg_wn["auto_crop"].value())) + new_wn.addKnob(knob) + # templates knob = nuke.String_Knob("tk_render_template") knob.setValue(sg_wn["render_template"].value()) @@ -464,7 +622,43 @@ def convert_sg_to_nuke_write_nodes(self): knob = nuke.String_Knob("tk_proxy_publish_template") knob.setValue(sg_wn["proxy_publish_template"].value()) new_wn.addKnob(knob) - + + # Store tank_channel + knob = nuke.String_Knob("tk_tank_channel") + knob.setValue(sg_wn["tank_channel"].value()) + new_wn.addKnob(knob) + + #write type + knob = nuke.String_Knob("tk_write_type") + knob.setValue(sg_wn["write_type_cache"].value()) + new_wn.addKnob(knob) + + # project bool + knob = nuke.String_Knob("tk_project_format_cache") + if sg_wn["project_crop_bool"].value(): + knob.setValue("True") + else: + knob.setValue("False") + new_wn.addKnob(knob) + + # ocio bool + knob = nuke.String_Knob("tk_apply_ocio_cache") + if sg_wn["shot_ocio_bool"].value(): + knob.setValue("True") + else: + knob.setValue("False") + new_wn.addKnob(knob) + + # Copy across colorspace + colorspace_name = r'default \((\w{1,9})\)' + colorspace_match = re.match(colorspace_name, sg_wn['colorspace'].value()) + if colorspace_match: + new_wn['colorspace'].setValue(str(colorspace_match.group(1))) + nuke.tprint("Setting colorspace to %s" % colorspace_match.group(1)) + else: + nuke.tprint("Setting colorspace to default") + new_wn['colorspace'].setValue(sg_wn['colorspace'].value()) + # delete original node: nuke.delete(sg_wn) @@ -492,91 +686,208 @@ def convert_nuke_to_sg_write_nodes(self): # get write nodes: write_nodes = nuke.allNodes(group=nuke.root(), filter="Write", recurseGroups = True) - for wn in write_nodes: - - # look for additional toolkit knobs: - profile_knob = wn.knob("tk_profile_name") - output_knob = wn.knob("tk_output") - use_name_as_output_knob = wn.knob(TankWriteNodeHandler.USE_NAME_AS_OUTPUT_KNOB_NAME) - render_template_knob = wn.knob("tk_render_template") - publish_template_knob = wn.knob("tk_publish_template") - proxy_render_template_knob = wn.knob("tk_proxy_render_template") - proxy_publish_template_knob = wn.knob("tk_proxy_publish_template") - - if (not profile_knob - or not output_knob - or not use_name_as_output_knob - or not render_template_knob - or not publish_template_knob - or not proxy_render_template_knob - or not proxy_publish_template_knob): - # can't convert to a Shotgun Write Node as we have missing parameters! - continue - # set as selected: - wn.setSelected(True) - node_name = wn.name() - node_pos = (wn.xpos(), wn.ypos()) - - # create new Shotgun Write node: - new_sg_wn = nuke.createNode(TankWriteNodeHandler.SG_WRITE_NODE_CLASS) - new_sg_wn.setSelected(False) - - # copy across file & proxy knobs as well as all cached templates: - new_sg_wn["cached_path"].setValue(wn["file"].value()) - new_sg_wn["tk_cached_proxy_path"].setValue(wn["proxy"].value()) - new_sg_wn["render_template"].setValue(render_template_knob.value()) - new_sg_wn["publish_template"].setValue(publish_template_knob.value()) - new_sg_wn["proxy_render_template"].setValue(proxy_render_template_knob.value()) - new_sg_wn["proxy_publish_template"].setValue(proxy_publish_template_knob.value()) - - # set the profile & output - this will cause the paths to be reset: - # Note, we don't call the method __set_profile() as we don't want to - # run all the normal logic that runs as part of switching the profile. - # Instead we want this node to be rebuilt as close as possible to the - # original before it was converted to a regular Nuke write node. - profile_name = profile_knob.value() - new_sg_wn["profile_name"].setValue(profile_name) - new_sg_wn["tk_profile_list"].setValue(profile_name) - new_sg_wn[TankWriteNodeHandler.OUTPUT_KNOB_NAME].setValue(output_knob.value()) - new_sg_wn[TankWriteNodeHandler.USE_NAME_AS_OUTPUT_KNOB_NAME].setValue(use_name_as_output_knob.value()) + for gn in self._project_setting_groups: + nuke.tprint(gn) + nuke.delete(gn) - # make sure file_type is set properly: - int_wn = new_sg_wn.node(TankWriteNodeHandler.WRITE_NODE_NAME) - int_wn["file_type"].setValue(wn["file_type"].value()) + self._project_setting_groups = [] - # copy across and knob values from the internal write node. - for knob_name, knob in wn.knobs().iteritems(): - # skip knobs we don't want to copy: - if knob_name in ["file_type", "file", "proxy", "beforeRender", "afterRender", - "name", "xpos", "ypos", "disable", "tile_color", "postage_stamp", - "label"]: + for wn in write_nodes: + if "_converted" in wn.name(): + pass + else: + # look for additional toolkit knobs: + profile_knob = wn.knob("tk_profile_name") + write_type = wn.knob("tk_write_type") + exr_datatype = wn.knob("tk_exr_datatype") + dpx_datatype = wn.knob("tk_dpx_datatype") + auto_crop = wn.knob("tk_autocrop") + output_knob = wn.knob("tk_output") + use_name_as_output_knob = wn.knob(TankWriteNodeHandler.USE_NAME_AS_OUTPUT_KNOB_NAME) + # channels_knob = wn.knob("channels") + render_template_knob = wn.knob("tk_render_template") + publish_template_knob = wn.knob("tk_publish_template") + proxy_render_template_knob = wn.knob("tk_proxy_render_template") + proxy_publish_template_knob = wn.knob("tk_proxy_publish_template") + tk_tank_channel = wn.knob("tk_tank_channel") + project_format_knob = wn.knob("tk_project_format_cache") + shot_ocio_knob = wn.knob("tk_apply_ocio_cache") + + if (not profile_knob + or not output_knob + or not use_name_as_output_knob + or not write_type + or not exr_datatype + or not dpx_datatype + or not auto_crop + # or not channels_knob + or not project_format_knob + or not shot_ocio_knob + or not render_template_knob + or not publish_template_knob + or not proxy_render_template_knob + or not proxy_publish_template_knob): + # can't convert to a Shotgun Write Node as we have missing parameters! continue + + # set as selected: + wn.setSelected(True) + node_name = wn.name() + node_pos = (wn.xpos(), wn.ypos()) + wn.setName(node_name) + + # create new Shotgun Write node: + new_sg_wn = nuke.createNode(TankWriteNodeHandler.SG_WRITE_NODE_CLASS, inpanel = False) + new_sg_wn.setSelected(False) + # copy across file & proxy knobs as well as all cached templates: + new_sg_wn["cached_path"].setValue(wn["file"].value()) + new_sg_wn["tk_cached_proxy_path"].setValue(wn["proxy"].value()) + # new_sg_wn["channels"].setValue(channels_knob.value()) + new_sg_wn["render_template"].setValue(render_template_knob.value()) + new_sg_wn["publish_template"].setValue(publish_template_knob.value()) + new_sg_wn["proxy_render_template"].setValue(proxy_render_template_knob.value()) + new_sg_wn["proxy_publish_template"].setValue(proxy_publish_template_knob.value()) + new_sg_wn["tk_channel_cache"].setValue(tk_tank_channel.value()) + + # set the profile & output - this will cause the paths to be reset: + # Note, we don't call the method __set_profile() as we don't want to + # run all the normal logic that runs as part of switching the profile. + # Instead we want this node to be rebuilt as close as possible to the + # original before it was converted to a regular Nuke write node. + profile_name = profile_knob.value() + new_sg_wn["profile_name"].setValue(profile_name) + new_sg_wn["tk_profile_list"].setValue(profile_name) + new_sg_wn[TankWriteNodeHandler.OUTPUT_KNOB_NAME].setValue(output_knob.value()) + new_sg_wn[TankWriteNodeHandler.USE_NAME_AS_OUTPUT_KNOB_NAME].setValue(use_name_as_output_knob.value()) + + # make sure file_type is set properly: + int_wn = new_sg_wn.node(TankWriteNodeHandler.WRITE_NODE_NAME) + int_wn["file_type"].setValue(wn["file_type"].value()) + + # copy across and knob values from the internal write node. + for knob_name, knob in wn.knobs().iteritems(): + # skip knobs we don't want to copy: + if knob_name in ["file_type", "file", "proxy", "beforeRender", "afterRender", + "name", "xpos", "ypos", "disable", "tile_color", "postage_stamp", + "label"]: + continue + + if knob_name in int_wn.knobs(): + try: + int_wn[knob_name].setValue(knob.value()) + except TypeError: + # ignore type errors: + pass + + # explicitly copy some settings to the new Shotgun Write Node instead: + for knob_name in ["disable", "tile_color", "postage_stamp"]: + new_sg_wn[knob_name].setValue(wn[knob_name].value()) - if knob_name in int_wn.knobs(): - try: - int_wn[knob_name].setValue(knob.value()) - except TypeError: - # ignore type errors: - pass - - # explicitly copy some settings to the new Shotgun Write Node instead: - for knob_name in ["disable", "tile_color", "postage_stamp"]: - new_sg_wn[knob_name].setValue(wn[knob_name].value()) + # delete original node: + nuke.delete(wn) + + # set SG write type + write_type_name = write_type.value() + new_sg_wn["write_type"].setValue(write_type_name) - # delete original node: - nuke.delete(wn) + #Set tank channel + tank_channel_text = tk_tank_channel.value() + new_sg_wn["tank_channel"].setValue(tank_channel_text) - # rename new node: - new_sg_wn.setName(node_name) - new_sg_wn.setXpos(node_pos[0]) - new_sg_wn.setYpos(node_pos[1]) + # datatype + new_sg_wn["exr_datatype"].setValue(exr_datatype.value()) + new_sg_wn["dpx_datatype"].setValue(dpx_datatype.value()) + # autocrop + if auto_crop.value() == "True": + ac_val = True + else: + ac_val = False + new_sg_wn["auto_crop"].setValue(ac_val) + + # project format + if project_format_knob.value() == "True": + project_format_val = True + elif project_format_knob.value() == "False": + project_format_val = False + new_sg_wn["project_crop_bool"].setValue(project_format_val) + + # shot ocio + if shot_ocio_knob.value() == "True": + shot_ocio_val = True + elif shot_ocio_knob.value() == "False": + shot_ocio_val = False + new_sg_wn["shot_ocio_bool"].setValue(shot_ocio_val) + + # rename new node: + new_sg_wn.setName(node_name) + new_sg_wn.setXpos(node_pos[0]) + new_sg_wn.setYpos(node_pos[1]) + + def add_format(self, project_shortcode, format_type, format_width, format_height, pixel_aspect_ratio): + """ + Test Nuke script for given format + Return if exact format is found + Create a new one and return if not found + """ + format_name = "%s_%s_%d" % (project_shortcode,format_type,format_width) + format_match = next((format_ for format_ in nuke.formats() if format_.name() == format_name),None) + if format_match: + if (format_match.width()== format_width and + format_match.height()== format_height and + format_match.pixelAspect() == pixel_aspect_ratio): + pass + else: + format_match = nuke.addFormat("%d %d %d %s" %(format_width, format_height, pixel_aspect_ratio, format_name)) + else: + format_match = nuke.addFormat("%d %d %d %s" %(format_width, format_height, pixel_aspect_ratio,format_name)) + + return format_match ################################################################################################ # Public methods called from gizmo - although these are public, they should # be considered as private and not used directly! + def get_node_write_type_name(self, node): + """ + Return the name of the profile the specified node is using + """ + return node.knob("write_type").value() + + def test_folder_for_renders(self, path): + """ + Tests a given folder location - mainly the writenode's file path for containing files + + :returns: True/False + """ + file_path = "" + path_items = [] + ext_match = [] + path_dir = os.path.dirname(path) + + if os.path.isdir(path_dir): + file_output_ext = [] + path_items_ext = [] + path_file_output_ext = os.path.split(path) + path_file_output_ext = os.path.splitext(path_file_output_ext[-1])[-1] + file_output_ext.append(path_file_output_ext) + path_items = os.listdir(path_dir) + path_items.sort() + if path_items: + for file in path_items: + path_item_ext = os.path.splitext(file)[1] + if path_item_ext not in path_items_ext: + path_items_ext.append(path_item_ext) + + file_path = os.path.normpath(os.path.join(path_dir,path_items[0])) + ext_match = list(set(path_items_ext).intersection(set(file_output_ext))) + + return (path_items, file_path, ext_match) + + else: + return False + def on_knob_changed_gizmo_callback(self): """ Called when the value of a knob on a Shotgun Write Node is changed @@ -596,7 +907,9 @@ def on_node_created_gizmo_callback(self): # alone from now on, unless we someday have a better understanding of # what's going on and the consequences of changing the on_node_created # behavior. + self.setup_new_node(nuke.thisNode()) + # self.reset_render_path(nuke.thisNode()) def on_compute_path_gizmo_callback(self): """ @@ -664,7 +977,7 @@ def on_show_in_fs_gizmo_callback(self): # render directory doesn't exist so try using location # of rendered frames instead: try: - files = self.get_files_on_disk(node) + files, fields = self.get_files_on_disk(node) if len(files) == 0: nuke.message("There are no %srenders for this node yet!\n" "When you render, the files will be written to " @@ -704,7 +1017,7 @@ def on_reset_render_path_gizmo_callback(self): self.reset_render_path(node) - def on_copy_path_to_clipboard_gizmo_callback(self): + def on_copy_path_to_clipboard_gizmo_callback(self, win_safe, folder): """ Callback from the gizmo whenever the 'Copy path to clipboard' button is pressed. @@ -714,11 +1027,66 @@ def on_copy_path_to_clipboard_gizmo_callback(self): # get the path depending if in full or proxy mode: is_proxy = node.proxy() render_path = self.__get_render_path(node, is_proxy) - + if win_safe: + system = sys.platform + if system == "win32": + render_path = os.path.normpath(render_path) + if folder: + try: + render_path = os.path.split(render_path)[0] + except: + self._app.log_debug("Could not get folder from path") + # use Qt to copy the path to the clipboard: from sgtk.platform.qt import QtGui QtGui.QApplication.clipboard().setText(render_path) + def on_open_in_player_gizmo_callback(self): + + node = nuke.thisNode() + if not node: + return + + render_dir = None + + # first, try to just use the current cached path: + is_proxy = node.proxy() + render_path = self.__get_render_path(node, is_proxy) + if render_path: + # the above method returns nuke style slashes, so ensure these + # are pointing correctly + render_path = render_path.replace("/", os.path.sep) + + dir_name = os.path.dirname(render_path) + if os.path.exists(dir_name): + render_dir = dir_name + + if os.path.exists(os.path.normpath(self.software_info['windows_path'])): + if render_dir: + system = sys.platform + # run the app + if system == "linux2": + pass + # cmd = "xdg-open \"%s\"" % render_dir + elif system == "darwin": + pass + # cmd = "open '%s'" % render_dir + elif system == "win32": + cmd = "start cmd.exe /c \"%s\" \"%s\" pause" % (self.software_info['windows_path'], render_dir) + else: + raise Exception("Platform '%s' is not supported." % system) + + self._app.log_debug("Executing command '%s'" % cmd) + exit_code = os.system(cmd) + if exit_code != 0: + nuke.message("Failed to launch '%s'!" % cmd) + + else: + nuke.message("Could not find the path to render") + else: + nuke.message("Could not find the path to %s: %s" % (self.software_info['code'], + self.software_info['windows_path'])) + def on_before_render_gizmo_callback(self): """ Callback from nuke whenever a tank write node is about to be rendered. @@ -769,6 +1137,7 @@ def on_before_render_gizmo_callback(self): self._app.log_error("The Write node's beforeRender setting failed " "to execute!") raise + def on_after_render_gizmo_callback(self): """ Callback from nuke whenever a tank write node has finished being rendered @@ -797,7 +1166,6 @@ def on_after_render_gizmo_callback(self): ################################################################################################ # Private methods - def __get_node_profile_settings(self, node): """ Find the profile settings for the specified node @@ -826,7 +1194,7 @@ def __get_template(self, node, name): return self._app.get_template_by_name(template_name) - def __get_render_template(self, node, is_proxy=False, fallback_to_render=False): + def __get_render_template(self, node, write_type, is_proxy=False, fallback_to_render=False): """ Get a specific render template for the current profile @@ -838,8 +1206,28 @@ def __get_render_template(self, node, is_proxy=False, fallback_to_render=False): template = self.__get_template(node, "proxy_render_template") if template or not fallback_to_render: return template - - return self.__get_template(node, "render_template") + elif write_type == "Element": + template = self.__get_template(node, "element_render_template") + if template or not fallback_to_render: + return template + elif write_type == "Denoise": + template = self.__get_template(node, "denoise_render_template") + if template or not fallback_to_render: + return template + elif write_type == "SmartVector": + template = self.__get_template(node, "smartvector_render_template") + if template or not fallback_to_render: + return template + elif write_type == "STMap": + template = self.__get_template(node, "stmap_render_template") + if template or not fallback_to_render: + return template + elif write_type == "Test": + template = self.__get_template(node, "test_render_template") + if template or not fallback_to_render: + return template + else: + return self.__get_template(node, "render_template") def __get_publish_template(self, node, is_proxy=False): """ @@ -855,8 +1243,9 @@ def __is_output_used(self, node): Determine if output key is used in either the render or the proxy render templates """ - render_template = self.__get_render_template(node, is_proxy=False) - proxy_render_template = self.__get_render_template(node, is_proxy=True) + write_type = self.get_node_write_type_name(node) + render_template = self.__get_render_template(node, write_type, is_proxy=False) + proxy_render_template = self.__get_render_template(node, write_type, is_proxy=True) for template in [render_template, proxy_render_template]: if not template: @@ -888,7 +1277,7 @@ def __update_output_knobs(self, node): output_is_used = self.__is_output_used(node) name_as_output = name_as_output_knob.value() - output_knob.setEnabled(output_is_used and not name_as_output) + # output_knob.setEnabled(output_is_used and not name_as_output) output_knob.setVisible(output_is_used) name_as_output_knob.setVisible(output_is_used) @@ -896,13 +1285,17 @@ def __update_path_preview(self, node, is_proxy): """ Updates the path preview fields on the tank write node. """ + # set the write type for creation of correct output + write_type = self.get_node_write_type_name(node) + # first set up the node label # this will be displayed on the node in the graph # useful to tell what type of node it is pn = node.knob("profile_name").value() - label = "Shotgun Write %s" % pn + label ="%s - %s" % (write_type, pn) self.__update_knob_value(node, "label", label) + # get the render path: path = self.__get_render_path(node, is_proxy) @@ -923,12 +1316,66 @@ def __update_path_preview(self, node, is_proxy): # get the file name file_name = os.path.basename(norm_path) render_dir = os.path.dirname(norm_path) - - # now get the context path + # retrieve the correct context based on the dynamic + # file structure we have to separate out test + # renders from primary ones + # get the current script path: + script_path = self.__get_current_script_path() + work_template = self._app.tank.template_from_path(script_path) + curr_fields = work_template.get_fields(script_path) context_path = None - for x in self._app.context.entity_locations: - if render_dir.startswith(x): - context_path = x + + if self._app.context.entity['type'] == 'Shot': + fields ={ + 'Shot': curr_fields['Shot'], + 'task_name': curr_fields['task_name'], + 'name': '', + 'output': '', + 'version': curr_fields['version'] + } + if write_type == "Test": + context_info = self._app.tank.templates['shot_render_test_global'] + elif write_type == "Element": + context_info = self._app.tank.templates['shot_render_global'] + elif write_type == "Denoise": + context_info = self._app.tank.templates['shot_render_global'] + elif write_type == "SmartVector": + context_info = self._app.tank.templates['shot_render_global'] + elif write_type == "STMap": + context_info = self._app.tank.templates['shot_render_global'] + else: + context_info = self._app.tank.templates['shot_render_global'] + + context_path = context_info.apply_fields(fields) + + elif self._app.context.entity['type'] == 'Asset': + fields ={ + 'Asset': curr_fields['Asset'], + 'task_name': curr_fields['task_name'], + 'sg_asset_type': curr_fields['sg_asset_type'], + 'name': '', + 'output': '', + 'version': curr_fields['version'] + } + if write_type == "Test": + context_info = self._app.tank.templates['asset_render_test_global'] + elif write_type == "Element": + context_info = self._app.tank.templates['asset_render_global'] + elif write_type == "Denoise": + context_info = self._app.tank.templates['asset_render_global'] + elif write_type == "SmartVector": + context_info = self._app.tank.templates['asset_render_global'] + elif write_type == "STMap": + context_info = self._app.tank.templates['asset_render_global'] + else: + context_info = self._app.tank.templates['asset_render_global'] + + context_path = context_info.apply_fields(fields) + + # now get the context path + # for x in self._app.context.entity_locations: + # if render_dir.startswith(x): + # context_path = x if context_path: # found a context path! @@ -967,7 +1414,6 @@ def set_path_knob(name, value): set_path_knob("path_local", local_path) set_path_knob("path_filename", file_name) - def __apply_cached_file_format_settings(self, node): """ Apply the file_type and settings that have been cached on the node to the internal @@ -992,8 +1438,7 @@ def __apply_cached_file_format_settings(self, node): # update the node: self.__populate_format_settings(node, file_type, file_settings) - - def __set_profile(self, node, profile_name, reset_all_settings=False): + def __set_profile(self, node, profile_name, write_type, reset_all_settings=False): """ Set the current profile for the specified node. @@ -1004,7 +1449,16 @@ def __set_profile(self, node, profile_name, reset_all_settings=False): be reset. For example, if colorspace has been set in the profile and force is False then the knob won't get reset to the value from the profile. """ + # nuke.tprint("Setting profile for %s" %profile_name) # can't change the profile if this isn't a valid profile: + self.shot_info = self.sg.find_one("Shot", + [['id', 'is', self._app.context.entity['id']]], + ['name', + 'id', + 'sg_main_plate', + 'sg_without_ocio', + 'sg_shot_mattes']) + if profile_name not in self._profiles: # at the very least, try to restore the file format settings from the cached values: self.__apply_cached_file_format_settings(node) @@ -1012,6 +1466,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! self._app.log_warning("Failed to find a write node profile called '%s' for node '%s'!" @@ -1024,17 +1479,65 @@ 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"]) proxy_render_template = self._app.get_template_by_name(profile["proxy_render_template"]) proxy_publish_template = self._app.get_template_by_name(profile["proxy_publish_template"]) + element_render_template = self._app.get_template_by_name(profile["element_render_template"]) + denoise_render_template = self._app.get_template_by_name(profile["denoise_render_template"]) + smartvector_render_template = self._app.get_template_by_name(profile["smartvector_render_template"]) + test_render_template = self._app.get_template_by_name(profile["test_render_template"]) + file_type = profile["file_type"] file_settings = profile["settings"] tile_color = profile["tile_color"] promote_write_knobs = profile.get("promote_write_knobs", []) + # Sets project specific data type + exr_datayype = '16 bit half' + dpx_datatype = '10 bit' + + if profile_name == "Exr": + if self.proj_info['sg_data_type']: + if self.proj_info['sg_data_type'] == '16 bit': + exr_datayype = '16 bit half' + elif self.proj_info['sg_data_type'] == '32 bit': + exr_datayype = '32 bit float' + elif profile_name == "Dpx": + if self.proj_info['sg_data_type']: + self.__update_knob_value(node, 'dpx_datatype', self.proj_info['sg_data_type']) + file_settings.update({'datatype' : self.proj_info['sg_data_type']}) + nuke.tprint("Set datatype to %s" % self.proj_info['sg_data_type']) + else: + self.__update_knob_value(node, 'dpx_datatype', dpx_datatype) + + # Apply datatype info based on context + if file_type == "exr" and write_type == "Version": + if self.ctx_info.step['name'] == "roto": + nuke.tprint("Task context is " + self.ctx_info.step['name']+ + ". Applying ZIP compression to "+ write_type +" output.") + self.__update_knob_value(node, 'exr_datatype', '16 bit half') + file_settings.update({'compression' : 'Zip (1 scanline)'}) + file_settings.update({'datatype' : '16 bit half'}) + else: + self.__update_knob_value(node, 'exr_datatype', exr_datayype) + file_settings.update({'compression' : 'none'}) + file_settings.update({'datatype' : exr_datayype}) + elif (file_type == "exr" and write_type == "Element" or + write_type == "Denoise"): + self.__update_knob_value(node, 'exr_datatype', '16 bit half') + file_settings.update({'compression' : 'Zip (1 scanline)'}) + file_settings.update({'datatype' : '16 bit half'}) + nuke.tprint("Applying ZIP compression to %s output." % write_type) + elif write_type == "SmartVector": + self.__update_knob_value(node, 'exr_datatype', '16 bit half') + file_settings.update({'datatype' : '16 bit half'}) + elif write_type == "STMap": + self.__update_knob_value(node, 'exr_datatype', '32 bit float') + file_settings.update({'datatype' : '32 bit float'}) + promote_write_knobs = profile.get("promote_write_knobs", []) # 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: @@ -1042,8 +1545,10 @@ 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, "write_type_cache", write_type) self.__update_knob_value(node, "tk_profile_list", profile_name) + # set the format self.__populate_format_settings( node, @@ -1052,12 +1557,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, []): @@ -1103,12 +1608,23 @@ def __set_profile(self, node, profile_name, reset_all_settings=False): 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) + self.__update_knob_value(node, "render_template", + render_template.name) + self.__update_knob_value(node, "publish_template", + publish_template.name) self.__update_knob_value(node, "proxy_render_template", proxy_render_template.name if proxy_render_template else "") self.__update_knob_value(node, "proxy_publish_template", proxy_publish_template.name if proxy_publish_template else "") + self.__update_knob_value(node, "element_render_template", + element_render_template.name) + self.__update_knob_value(node, "denoise_render_template", + denoise_render_template.name) + self.__update_knob_value(node, "smartvector_render_template", + smartvector_render_template.name) + self.__update_knob_value(node, "test_render_template", + test_render_template.name) + # If a node's tile_color was defined in the profile then set it: if not tile_color or len(tile_color) != 3: @@ -1116,9 +1632,21 @@ def __set_profile(self, node, profile_name, reset_all_settings=False): # don't have exactly three values for RGB so log a warning: self._app.log_warning(("The tile_color setting for profile '%s' must contain 3 values (RGB) - this " "setting will be ignored!") % profile_name) - + # reset tile_color knob value back to default: - default_value = int(node["tile_color"].defaultValue()) + if write_type == "Element": + default_value = 1095751564801 + elif write_type == "Denoise": + default_value = 2231304447 + elif write_type == "SmartVector": + default_value = 4278254335 + elif write_type == "STMap": + default_value = 2857762815L + elif write_type == "Test": + default_value = 4278190081 + else: + default_value = int(node["tile_color"].defaultValue()) + self.__update_knob_value(node, "tile_color", default_value) else: # build packed RGB @@ -1128,6 +1656,218 @@ def __set_profile(self, node, profile_name, reset_all_settings=False): packed_rgb = (packed_rgb + min(max(element, 0), 255)) << 8 self.__update_knob_value(node, "tile_color", packed_rgb) + + # set the channel info based on the profile type + profile_channel = "rgba" + + context = self._app.context.entity['type'] + if context != 'Asset': + if not self.shot_info['sg_shot_mattes']: + profile_channel = "rgb" + else: + nuke.tprint("Found assigned mattes on SG. Setting channels to all") + profile_channel = "all" + else: + pass + + if profile_name == "Dpx": + node.knob('dpx_datatype').setVisible(True) + node.knob('exr_datatype').setVisible(False) + node.knob('auto_crop').setVisible(False) + node.knob('auto_crop').setValue(False) + profile_channel = "rgb" + elif profile_name == "Exr": + node.knob('exr_datatype').setVisible(True) + node.knob('dpx_datatype').setVisible(False) + + try: + if node['channels_cache'].value() != "": + profile_channel = node['channels_cache'].value() + except: + pass + node.node(TankWriteNodeHandler.WRITE_NODE_NAME)['metadata'].setValue('all metadata') + node.knob('auto_crop').setVisible(False) + if write_type == "Element": + profile_channel = "all" + node.knob('auto_crop').setVisible(True) + node.knob('auto_crop').setValue(True) + node.node("Write1").knob("autocrop").setValue(True) + if write_type == "SmartVector": + profile_channel = "all" + node.knob('auto_crop').setVisible(True) + node.knob('auto_crop').setValue(True) + node.node("Write1").knob("autocrop").setValue(True) + if write_type == "STMap": + profile_channel = "all" + node.knob('auto_crop').setVisible(True) + node.knob('auto_crop').setValue(True) + node.node("Write1").knob("autocrop").setValue(True) + if self.ctx_info.step['name'] == "roto": + profile_channel = "all" + node.knob('auto_crop').setVisible(True) + node.knob('auto_crop').setValue(True) + node.node("Write1").knob("autocrop").setValue(True) + elif profile_name == "Jpeg": + node.knob('dpx_datatype').setVisible(False) + node.knob('exr_datatype').setVisible(False) + node.knob('auto_crop').setVisible(False) + else: + nuke.tprint("No profile with that name") + + self.__update_knob_value(node, "channels", profile_channel) + + # Sets project specific fileset compression + if (file_type== "exr" and + write_type == "Version" and + self.ctx_info.step['name'] != "roto"): + if self.proj_info['sg_delivery_fileset_compression']: + node.node("Write1").knob("compression").setValue(self.proj_info['sg_delivery_fileset_compression']) + # nuke.tprint("Setting Version compression from SG Project values to : " + self.proj_info['sg_delivery_fileset_compression']) + + if self._app.context.entity['type'] == 'Shot': + delivery_reformat = node.node(TankWriteNodeHandler.EMBED_DELIVERY_REFORMAT) + internal_shuffle = node.node(TankWriteNodeHandler.EMBED_SHUFFLE) + format_crop = node.node(TankWriteNodeHandler.EMBED_FORMAT_CROP) + content_meta_data = node.node(TankWriteNodeHandler.EMBED_META_DATA) + shot_ocio = node.node(TankWriteNodeHandler.EMBED_SHOT_OCIO) + matte_clamp = node.node(TankWriteNodeHandler.EMBED_MATTE_CLAMP) + proj_fps = self.proj_info['sg_frame_rate'] + + # Set the embeded delivery reformat + if not (self.proj_info['sg_delivery_format_width'] and + self.proj_info['sg_delivery_format_height']): + delivery_reformat['disable'].setValue(True) + node.node("format_crop")['disable'].setValue(True) + node.knob("project_crop_bool").setValue(False) + nuke.tprint("No delivery reformat info given on Projects.") + else: + if (self.proj_info['sg_delivery_format_width'] == self.proj_info['sg_format_width'] + and self.proj_info['sg_delivery_format_height'] == self.proj_info['sg_format_height']): + nuke.tprint("Delvery Format is the same as Project Format - disabling!") + delivery_reformat['disable'].setValue(True) + node.node("format_crop")['disable'].setValue(True) + node.knob("project_crop_bool").setValue(False) + node.knob('project_crop_bool').setVisible(False) + else: + # Set the project reformat first + delivery_format = self.add_format(self.proj_info['sg_short_name'], + "delivery", + int(self.proj_info['sg_delivery_format_width']), + int(self.proj_info['sg_delivery_format_height']), + int(self.proj_info['sg_pixel_aspect_ratio'])) + + if write_type != "Version": + delivery_reformat['disable'].setValue(True) + node.node("format_crop")['disable'].setValue(True) + else: + # Add SG reformat settings + filter_match = next((f for f in delivery_reformat['filter'].values() if f == self.proj_info['sg_delivery_reformat_filter']), None) + if filter_match: + delivery_reformat['filter'].setValue(filter_match) + resize_match = next((r for r in delivery_reformat['resize'].values() if r == self.proj_info['sg_delivery_reformat_type']), None) + if resize_match: + delivery_reformat['resize'].setValue(resize_match) + # Set the delivery_reformat node + if self.ctx_info.step['name'] == "cleanup": + node.knob("project_crop_bool").setValue(False) + delivery_reformat.knobs()["format"].setValue(delivery_format) + crop_box_value = (0,0, delivery_format.width(), delivery_format.height()) + format_crop['box'].setValue(crop_box_value) + delivery_reformat['disable'].setValue(True) + format_crop['disable'].setValue(True) + else: + delivery_reformat.knobs()["format"].setValue(delivery_format) + crop_box_value = (0,0, delivery_format.width(), delivery_format.height()) + format_crop['box'].setValue(crop_box_value) + delivery_reformat['disable'].setValue(False) + format_crop['disable'].setValue(False) + + color_space = None + # Set colorspace based of SG values + if (self.ctx_info.step['name'] != "roto" and + write_type == "Version"): + if self.proj_info['sg_color_space']: + if self.proj_info['sg_color_space'] == "raw": + node['raw'].setValue(True) + else: + color_space = self.proj_info['sg_color_space'] + else: + color_space = next((color for color in node.knob('colorspace').values() if 'default' in color), None) + elif self.ctx_info.step['name'] != "roto": + if self.proj_info['sg_color_space']: + if self.proj_info['sg_color_space'] == "raw": + node['raw'].setValue(True) + else: + color_space = self.proj_info['sg_color_space'] + else: + color_space = next((color for color in node.knob('colorspace').values() if 'default' in color), None) + elif self.ctx_info.step['name'] == "roto": + # color_space = "linear" + node.knob("project_crop_bool").setValue(False) + self.__embedded_format_option(node, False) + nuke.tprint("--- Setting colorspace to %s for roto" % color_space) + else: + color_space = next((color for color in node.knob('colorspace').values() if 'default' in color), None) + + if color_space: + node['colorspace'].setValue(color_space) + + md = content_meta_data['metadata'] + md.fromScript(self.__get_metadata(node)) + + elif self._app.context.entity['type'] == 'Asset': + + delivery_reformat = node.node(TankWriteNodeHandler.EMBED_DELIVERY_REFORMAT) + internal_shuffle = node.node(TankWriteNodeHandler.EMBED_SHUFFLE) + format_crop = node.node(TankWriteNodeHandler.EMBED_FORMAT_CROP) + content_meta_data = node.node(TankWriteNodeHandler.EMBED_META_DATA) + shot_ocio = node.node(TankWriteNodeHandler.EMBED_SHOT_OCIO) + matte_clamp = node.node(TankWriteNodeHandler.EMBED_MATTE_CLAMP) + proj_fps = self.proj_info['sg_frame_rate'] + + # Set the embeded delivery reformat + if not (self.proj_info['sg_delivery_format_width'] and + self.proj_info['sg_delivery_format_height']): + delivery_reformat['disable'].setValue(True) + node.node("format_crop")['disable'].setValue(True) + + nuke.tprint("No delivery reformat info given on Projects.") + else: + # Set the project reformat first + delivery_format = self.add_format(self.proj_info['sg_short_name'], + "delivery", + int(self.proj_info['sg_delivery_format_width']), + int(self.proj_info['sg_delivery_format_height']), + int(self.proj_info['sg_pixel_aspect_ratio'])) + + if write_type != "Version": + delivery_reformat['disable'].setValue(True) + node.node("format_crop")['disable'].setValue(True) + else: + # Add SG reformat settings + filter_match = next((f for f in delivery_reformat['filter'].values() if f == self.proj_info['sg_delivery_reformat_filter']), None) + if filter_match: + delivery_reformat['filter'].setValue(filter_match) + resize_match = next((r for r in delivery_reformat['resize'].values() if r == self.proj_info['sg_delivery_reformat_type']), None) + if resize_match: + delivery_reformat['resize'].setValue(resize_match) + + # Set the delivery_reformat node + delivery_reformat.knobs()["format"].setValue(delivery_format) + crop_box_value = (0,0, delivery_format.width(), delivery_format.height()) + format_crop['box'].setValue(crop_box_value) + delivery_reformat['disable'].setValue(False) + format_crop['disable'].setValue(False) + color_space = None + # Set colorspace based of SG values + color_space = "linear" + node.knob("project_crop_bool").setValue(False) + self.__embedded_format_option(node, False) + + node['colorspace'].setValue(color_space) + + md = content_meta_data['metadata'] + md.fromScript(self.__get_metadata(node)) # 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 @@ -1135,6 +1875,29 @@ def __set_profile(self, node, profile_name, reset_all_settings=False): if profile_name != old_profile_name: self.reset_render_path(node) + def __get_metadata(self, node): + + # Set meta data info + user_name = "" + step_name = "" + script_name = "" + computer_name = os.getenv('COMPUTERNAME') + + try: + user_name = self.ctx_info.user['name'].replace(' ', '_') + step_name = self.ctx_info.step['name'] + except: + self._app.log_debug("Failed to gt context info for meta data.") + if nuke.root(): + script_name = os.path.split(nuke.root().name())[1] + metadata_info = "{set artist_name %s}\n" % user_name + metadata_info += "{set computer_name %s}\n" % computer_name + metadata_info += "{set script_name %s}\n" % script_name + metadata_info += "{set write_node %s}\n" % node.name() + metadata_info += "{set pipeline_step %s}\n" % step_name + + return metadata_info + def __populate_initial_output_name(self, template, node): """ Create a suitable output name for a node based on it's profile and @@ -1162,35 +1925,35 @@ def __populate_initial_output_name(self, template, node): # Nothing to do! return - if output_default is None: - # no default name - use hard coded built in - output_default = "output" + # if output_default is None: + # # no default name - use hard coded built in + # output_default = "output" - # get the output names for all other nodes that are using the same profile - used_output_names = set() - node_profile = self.get_node_profile_name(node) - for n in self.get_nodes(): - if n != node and self.get_node_profile_name(n) == node_profile: - used_output_names.add(n.knob(TankWriteNodeHandler.OUTPUT_KNOB_NAME).value()) + # # get the output names for all other nodes that are using the same profile + # used_output_names = set() + # node_profile = self.get_node_profile_name(node) + # for n in self.get_nodes(): + # if n != node and self.get_node_profile_name(n) == node_profile: + # used_output_names.add(n.knob(TankWriteNodeHandler.OUTPUT_KNOB_NAME).value()) - # handle if output is optional: - if output_is_optional and "" not in used_output_names: - # default should be an empty string: - output_default = "" + # # handle if output is optional: + # if output_is_optional and "" not in used_output_names: + # # default should be an empty string: + # output_default = "" - # now ensure output name is unique: - postfix = 1 - output_name = output_default - while output_name in used_output_names: - output_name = "%s%d" % (output_default, postfix) - postfix += 1 + # # now ensure output name is unique: + # postfix = 1 + # output_name = output_default + # while output_name in used_output_names: + # output_name = "%s%d" % (output_default, postfix) + # postfix += 1 - # finally, set the output name on the knob: - node.knob(TankWriteNodeHandler.OUTPUT_KNOB_NAME).setValue(output_name) + # # finally, set the output name on the knob: + # node.knob(TankWriteNodeHandler.OUTPUT_KNOB_NAME).setValue(output_name) def __populate_format_settings( self, node, file_type, file_settings, reset_all_settings=False, promoted_write_knobs=None - ): + ): """ Controls the file format of the write node @@ -1296,7 +2059,7 @@ def __populate_format_settings( self._app.log_debug( "Promoted write node knob settings to be applied: %s" % filtered_settings ) - write_node.readKnobs("\n".join(filtered_settings)) + write_node.readKnobs(r"\n".join(filtered_settings)) self.reset_render_path(node) def __set_output(self, node, output_name): @@ -1308,7 +2071,7 @@ def __set_output(self, node, output_name): # update output knob: self.__update_knob_value(node, TankWriteNodeHandler.OUTPUT_KNOB_NAME, output_name) - # reset the render path: + # reset the render path self.reset_render_path(node) def __wrap_text(self, t, line_length): @@ -1334,6 +2097,19 @@ def __wrap_text(self, t, line_length): lines.append(this_line) return lines + def __read_node_metadata(self, read_path): + + read_path = read_path.replace('\\','/') + read_metadata = nuke.nodes.Read(file="%s" %(read_path)) + read_metadata['file'].setValue(read_path) + read_metadata_info = read_metadata.metadata() + nuke.delete(read_metadata) + + try: + return read_metadata_info + except: + return None + def __update_render_path(self, node, force_reset=False, is_proxy=False): """ Update the render path and the various feedback knobs based on the current @@ -1401,7 +2177,7 @@ def __update_render_path(self, node, force_reset=False, is_proxy=False): if compute_path_error: raise TkComputePathError(compute_path_error) else: - # compute the render path: + # compute the render path render_path = self.__compute_render_path_from(node, render_template, width, height, output_name) except TkComputePathError as e: @@ -1436,7 +2212,11 @@ def __update_render_path(self, node, force_reset=False, is_proxy=False): # to see if it is locked. A path is considered locked if the render path differs # from the cached path ignoring certain dynamic fields (e.g. width, height). path_is_locked = self.__is_render_path_locked(node, render_path, cached_path, is_proxy) - + else: + # compute the render path + render_path = self.__compute_render_path_from(node, render_template, width, height, output_name) + + if path_is_locked: # render path was not what we expected! path_warning += "
".join(self.__wrap_text( @@ -1501,7 +2281,8 @@ def __update_render_path(self, node, force_reset=False, is_proxy=False): # finally, update preview: self.__update_path_preview(node, is_proxy) - + + return render_path finally: @@ -1540,8 +2321,8 @@ def __get_files_on_disk(self, node, is_proxy=False): Returns the files on disk associated with this node """ file_name = self.__get_render_path(node, is_proxy) - template = self.__get_render_template(node, is_proxy, fallback_to_render=True) - + write_type = self.get_node_write_type_name(node) + template = self.__get_render_template(node, write_type, is_proxy, fallback_to_render=True) if not template.validate(file_name): raise Exception("Could not resolve the files on disk for node %s." "The path '%s' is not recognized by Shotgun!" % (node.name(), file_name)) @@ -1551,7 +2332,7 @@ def __get_files_on_disk(self, node, is_proxy=False): # make sure we don't look for any eye - %V or SEQ - %04d stuff frames = self._app.tank.paths_from_template(template, fields, ["SEQ", "eye"]) - return frames + return (frames, fields) def __calculate_proxy_dimensions(self, node): """ @@ -1604,7 +2385,6 @@ def __calculate_proxy_dimensions(self, node): #print ("sx:", scale_x, "sy:", scale_y, "tx:", offset_x, "ty:", offset_y, # "w:", scaled_format.width(), "h:", scaled_format.height()) return (scaled_format.width(), scaled_format.height()) - def __gather_render_settings(self, node, is_proxy=False): """ @@ -1615,7 +2395,8 @@ def __gather_render_settings(self, node, is_proxy=False): :param is_proxy: If True then compute the proxy path, otherwise compute the standard render path :returns: Tuple containing (render template, width, height, output name) """ - render_template = self.__get_render_template(node, is_proxy) + write_type = self.get_node_write_type_name(node) + render_template = self.__get_render_template(node, write_type, is_proxy) width = height = 0 output_name = "" @@ -1642,7 +2423,6 @@ def __gather_render_settings(self, node, is_proxy=False): return (render_template, width, height, output_name) - def __compute_render_path(self, node, is_proxy=False): """ Computes the render path for a node. @@ -1658,7 +2438,7 @@ def __compute_render_path(self, node, is_proxy=False): # compute the render path: return self.__compute_render_path_from(node, render_template, width, height, output_name) - def __compute_render_path_from(self, node, render_template, width, height, output_name): + def __compute_render_path_from(self, node, render_template, width, height, output_name, next_version= 1): """ Computes the render path for a node using the specified settings @@ -1669,6 +2449,9 @@ def __compute_render_path_from(self, node, render_template, width, height, outpu :param output_name: The toolkit output name specified by the user for this node :returns: The computed render path """ + # nuke.tprint("Computing render path!") + # Get write type + write_type = self.get_node_write_type_name(node) # make sure we have a valid template: if not render_template: @@ -1684,6 +2467,12 @@ def __compute_render_path_from(self, node, render_template, width, height, outpu fields = {} if curr_filename and self._script_template and self._script_template.validate(curr_filename): fields = self._script_template.get_fields(curr_filename) + if (write_type == "Version" or + write_type == "Test"): + pass + + + if not fields: raise TkComputePathError("The current script is not a Shotgun Work File!") @@ -1730,8 +2519,8 @@ def __compute_render_path_from(self, node, render_template, width, height, outpu # make slahes uniform: path = path.replace(os.path.sep, "/") - - return path + + return path def __is_render_path_locked(self, node, render_path, cached_path, is_proxy=False): """ @@ -1743,7 +2532,8 @@ def __is_render_path_locked(self, node, render_path, cached_path, is_proxy=False would be different to the cached path ignoring the width & height fields. """ # get the render template: - render_template = self.__get_render_template(node, is_proxy, fallback_to_render=True) + write_type = self.get_node_write_type_name(node) + render_template = self.__get_render_template(node, write_type, is_proxy, fallback_to_render=True) if not render_template: return True @@ -1795,7 +2585,7 @@ def setup_new_node(self, node): assume that setting up of these nodes only needs to happen once - it needs to happen whenever the toolkit write node configuration changes. - + :param node: The Shotgun Write Node to set up """ # check that this node is actually a Gizmo. It might not be if @@ -1803,6 +2593,10 @@ def setup_new_node(self, node): if not isinstance(node, nuke.Gizmo): return + if self.__is_node_fully_constructed(node): + # node has already been constructed for this session! + return + self._app.log_debug("Setting up new node...") # reset the construction flag to ensure that @@ -1831,15 +2625,8 @@ def setup_new_node(self, node): # and as this node has never had a profile set, lets make # sure we reset all settings reset_all_profile_settings = True - - # ensure that the correct entry is selected from the list: - 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()) + + # Ensure that the output name matches the node name if # that option is enabled on the node. This is primarily @@ -1852,11 +2639,74 @@ def setup_new_node(self, node): # force output name to be the node name: new_output_name = node.knob("name").value() self.__set_output(node, new_output_name) - + # now that the node is constructed, we can process # knob changes correctly. self.__set_final_construction_flag(node, True) + # set the write type for creation of correct output + write_type = self.get_node_write_type_name(node) + if self._app.context.entity['type'] == 'Shot': + if self.proj_info['name'] == "Breakdowns": + node.knob('project_crop_bool').setVisible(False) + node.knob('shot_ocio_bool').setVisible(False) + else: + if write_type == "Version": + node.knob('convert_to_write').setVisible(False) + if self.ctx_info.step['name'] == "roto": + nuke.tprint("Creating roto SG Write node") + self.__update_knob_value(node, "tk_profile_list", "Exr") + node.knob('write_type').setValues(['Version', 'Denoise', 'Element']) + node.knob(TankWriteNodeHandler.OUTPUT_KNOB_NAME).setEnabled(True) + node.knob("project_crop_bool").setValue(False) + self.__embedded_format_option(node, False) + else: + node.knob(TankWriteNodeHandler.OUTPUT_KNOB_NAME).setEnabled(False) + if node['tk_project_format_cache'].value() == "False": + node.knob("project_crop_bool").setValue(False) + self.__embedded_format_option(node, False) + elif node['tk_project_format_cache'].value() == "True": + node.knob("project_crop_bool").setValue(True) + self.__embedded_format_option(node, True) + else: + node.knob("project_crop_bool").setValue(True) + self.__embedded_format_option(node, True) + elif self._app.context.entity['type'] == 'Asset': + if write_type == "Version": + node.knob('convert_to_write').setVisible(False) + + if node['tk_project_format_cache'].value() == "False": + node.knob("project_crop_bool").setValue(False) + self.__embedded_format_option(node, False) + + elif node['tk_project_format_cache'].value() == "True": + node.knob("project_crop_bool").setValue(True) + self.__embedded_format_option(node, True) + + else: + node.knob("project_crop_bool").setValue(True) + self.__embedded_format_option(node, True) + + node.knob(TankWriteNodeHandler.OUTPUT_KNOB_NAME).setEnabled(False) + node.knob('project_crop_bool').setVisible(False) + # ensure that the correct entry is selected from the list: + 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, write_type, 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()) + + # now that the node is constructed, we can process + # knob changes correctly. + self.__set_final_construction_flag(node, True) + + # now that the node is constructed, we can process knob changes + # correctly. + # node.knob("tk_is_fully_constructed").setValue(True) + # node.knob("tk_is_fully_constructed").setEnabled(False) + def __set_final_construction_flag(self, node, status): """ Controls the flag that indicates that a node has been @@ -1878,11 +2728,62 @@ def __is_node_fully_constructed(self, node): mechanism allows the code to ignore other callbacks that may fail because things aren't set up correctly (e.g. knobChanged calls for default values when loading a script). """ - if not node.knob("tk_is_fully_constructed"): + try: + if not node.knob("tk_is_fully_constructed"): + return False + else: + pass + # self._app.log_debug("Fully constructed: %s" % node.knob("tk_is_fully_constructed").value()) + except: return False + return node.knob("tk_is_fully_constructed").value() + + def __write_type_changed(self, node, detail_enabled):#, profile_type, channel_type): + # Profile input + # write_type_profile = profile_type + # profile_channels = channel_type + # Detail input + self.__update_knob_value(node, TankWriteNodeHandler.OUTPUT_KNOB_NAME, "") + + node.knob(TankWriteNodeHandler.OUTPUT_KNOB_NAME).setEnabled(detail_enabled) + + def __test_write_message(self): + # Pop warning that the renders saved to the Test location + + user_name = self._app.context.user['name'].split() + nuke.message( + "%s!" % str(user_name[0]) + "\n" + "\n" + "Please be aware that this is a temporary location.\n" + "Renders saved here will be removed at the end of the week.\n") + def __embedded_format_option(self, node, value): + if value == True: + node.node("delivery_reformat")['disable'].setValue(False) + node.node("format_crop")['disable'].setValue(False) + + elif value == False: + node.node("delivery_reformat")['disable'].setValue(True) + node.node("format_crop")['disable'].setValue(True) + + def __embedded_ocio_option(self, node, value): + if value == True: + node.node("shot_ocio")['disable'].setValue(False) + elif value == False: + node.node("shot_ocio")['disable'].setValue(True) + + def __set_project_crop(self, node, bool_value): + node["project_crop_bool"].setValue(bool_value) + + def __set_project_crop_cache(self, node, bool_value): + + if bool_value == True: + node['tk_project_format_cache'].setValue("True") + elif bool_value == False: + node['tk_project_format_cache'].setValue("False") + def __on_knob_changed(self): """ Callback that gets called whenever the value for a knob on a Shotgun Write @@ -1894,7 +2795,6 @@ def __on_knob_changed(self): node = nuke.thisNode() knob = nuke.thisKnob() grp = nuke.thisGroup() - if not self.__is_node_fully_constructed(node): # knobChanged will be called during script load for all knobs with non-default # values. We want to ignore these implicit changes so we make use of a knob to @@ -1903,25 +2803,36 @@ def __on_knob_changed(self): #print "Ignoring change to %s.%s value = %s" % (node.name(), knob.name(), knob.value()) return + # Set project fileset based on SG info + write_type = self.get_node_write_type_name(node) + write_type_profile = "Exr" + if self.proj_info['sg_delivery_fileset'] != None: + if write_type == "Version": + write_type_profile = self.proj_info['sg_delivery_fileset']['name'].capitalize() + + # Main handler area for knob changed if knob.name() == "tk_profile_list": # change the profile for the specified node: new_profile_name = knob.value() - self.__set_profile(node, new_profile_name, reset_all_settings=True) - + self.__set_profile(node, new_profile_name, write_type, reset_all_settings=True) elif knob.name() == TankWriteNodeHandler.OUTPUT_KNOB_NAME: # internal cached output has been changed! new_output_name = knob.value() - if node.knob(TankWriteNodeHandler.USE_NAME_AS_OUTPUT_KNOB_NAME).value(): - # force output name to be the node name: - new_output_name = node.knob("name").value() - self.__set_output(node, new_output_name) - - elif knob.name() == "name": + if (not new_output_name or + write_type == 'Version'): + pass + else: + node['name'].setValue(write_type+"_"+str(new_output_name)) + if node.knob(TankWriteNodeHandler.USE_NAME_AS_OUTPUT_KNOB_NAME).value(): + # force output name to be the node name: + new_output_name = node.knob("name").value() + self.__set_output(node, new_output_name) + elif knob.name() == "name": # node name has changed: - if node.knob(TankWriteNodeHandler.USE_NAME_AS_OUTPUT_KNOB_NAME).value(): - # set the output to the node name: - self.__set_output(node, knob.value()) - + if write_type != "Version": + if node.knob(TankWriteNodeHandler.USE_NAME_AS_OUTPUT_KNOB_NAME).value(): + # set the output to the node name + self.__set_output(node, knob.value()) elif knob.name() == TankWriteNodeHandler.USE_NAME_AS_OUTPUT_KNOB_NAME: # checkbox controlling if the name should be used as the output has been toggled name_as_output = knob.value() @@ -1929,7 +2840,178 @@ def __on_knob_changed(self): if name_as_output: # update output to reflect the node name: self.__set_output(node, node.knob("name").value()) + elif knob.name() == "write_type": + if self._app.context.entity['type'] == 'Shot': + if write_type == "Version": + node.knob('convert_to_write').setVisible(False) + self.__set_project_crop(node, True) + self.__write_type_changed(node, False) + self.__embedded_format_option(node, True) + if self.ctx_info.step['name'] == "roto": + self.__set_project_crop(node, False) + if write_type_profile == "Dpx": + node.node("Write1").knob("transfer").setValue('(auto detect)') + elif write_type == "Test": + self.__set_project_crop(node, False) + self.__write_type_changed(node, True) + self.__test_write_message() + self.__embedded_format_option(node, False) + else: + node.knob('convert_to_write').setVisible(True) + self.__set_project_crop(node, False) + self.__write_type_changed(node, True) + write_type_profile = "Exr" + self.__embedded_format_option(node, False) + try: + node.node("Write1").knob("autocrop").setValue(True) + except: + pass + # Scans script for existing name clashes and renames accordingly + existing_node_names = [n.name() for n in nuke.allNodes(group=nuke.root())] + new_output_name = "" + postfix = 1 + while True: + new_name = "%s%d" % (knob.value(), postfix) + if new_name not in existing_node_names: + node.knob("name").setValue(new_name) + break + else: + postfix += 1 + + self.__set_output(node, new_output_name) + # Updates the predefined profile based on the write type + self.__update_knob_value(node, "tk_profile_list", write_type_profile) + # reset profile + self.__set_profile(node, write_type_profile, write_type, reset_all_settings=True) + + # Asset Context is (basically) a clone of Shot Context + # but with some added knob adjustments + elif self._app.context.entity['type'] == 'Asset': + if write_type== "Version": + node.knob('convert_to_write').setVisible(False) + self.__set_project_crop(node, True) + self.__write_type_changed(node, False) + self.__embedded_format_option(node, True) + if self.ctx_info.step['name'] == "roto": + self.__set_project_crop(node, False) + if write_type_profile == "Dpx": + node.node("Write1").knob("transfer").setValue('(auto detect)') + self.__update_knob_value(node, TankWriteNodeHandler.OUTPUT_KNOB_NAME, "") + node.knob(TankWriteNodeHandler.OUTPUT_KNOB_NAME).setEnabled(True) + elif write_type == "Element": + node.knob('convert_to_write').setVisible(True) + self.__set_project_crop(node, False) + self.__write_type_changed(node, True) + write_type_profile = "Exr" + self.__embedded_format_option(node, False) + try: + node.node("Write1").knob("autocrop").setValue(True) + except: + pass + self.__update_knob_value(node, TankWriteNodeHandler.OUTPUT_KNOB_NAME, "") + node.knob(TankWriteNodeHandler.OUTPUT_KNOB_NAME).setEnabled(True) + write_type_profile = "Exr" + elif write_type == "Denoise": + node.knob('convert_to_write').setVisible(True) + self.__set_project_crop(node, False) + self.__write_type_changed(node, True) + write_type_profile = "Exr" + self.__embedded_format_option(node, False) + try: + node.node("Write1").knob("autocrop").setValue(True) + except: + pass + self.__update_knob_value(node, TankWriteNodeHandler.OUTPUT_KNOB_NAME, "") + node.knob(TankWriteNodeHandler.OUTPUT_KNOB_NAME).setEnabled(False) + elif write_type == "SmartVector": + node.knob('convert_to_write').setVisible(True) + self.__set_project_crop(node, False) + self.__write_type_changed(node, True) + write_type_profile = "Exr" + self.__embedded_format_option(node, False) + try: + node.node("Write1").knob("autocrop").setValue(True) + except: + pass + self.__update_knob_value(node, TankWriteNodeHandler.OUTPUT_KNOB_NAME, "") + node.knob(TankWriteNodeHandler.OUTPUT_KNOB_NAME).setEnabled(False) + elif write_type == "STMap": + node.knob('convert_to_write').setVisible(True) + self.__set_project_crop(node, False) + self.__write_type_changed(node, True) + write_type_profile = "Exr" + self.__embedded_format_option(node, False) + try: + node.node("Write1").knob("autocrop").setValue(True) + except: + pass + self.__update_knob_value(node, TankWriteNodeHandler.OUTPUT_KNOB_NAME, "") + node.knob(TankWriteNodeHandler.OUTPUT_KNOB_NAME).setEnabled(False) + elif write_type == "Test": + self.__set_project_crop(node, False) + self.__write_type_changed(node, True) + self.__embedded_format_option(node, False) + self.__update_knob_value(node, TankWriteNodeHandler.OUTPUT_KNOB_NAME, "") + node.knob(TankWriteNodeHandler.OUTPUT_KNOB_NAME).setEnabled(True) + self.__test_write_message() + + # Scans script for existing name clashes and renames accordingly + existing_node_names = [n.name() for n in nuke.allNodes(group=nuke.root())] + new_output_name = "" + postfix = 1 + while True: + new_name = "%s%d" % (knob.value(), postfix) + if new_name not in existing_node_names: + node.knob("name").setValue(new_name) + break + else: + postfix += 1 + + self.__set_output(node, new_output_name) + # Updates the predefined profile based on the write type + self.__update_knob_value(node, "tk_profile_list", write_type_profile) + # reset profile + self.__set_profile(node, write_type_profile, write_type, reset_all_settings=True) + + elif knob.name() == "write_type_info": + write_type_url = "http://10.80.8.252/ssvfx-wiki-sphinx/workflow/nuke/nuke.html#sg-write-node" + webbrowser.open_new_tab(write_type_url) + elif knob.name() == "check_mattes": + + if not ntools: + nuke.tprint("Could not find NukeTools") + else: + + try: + ntools.test_channels(node, self.shot_info, ['rgb', 'rgba']) + except: + pass + + elif knob.name() == "project_crop_bool": + self.__embedded_format_option(node, knob.value()) + self.__set_project_crop_cache(node, knob.value()) + elif knob.name() == "shot_ocio_bool": + self.__embedded_ocio_option(node, knob.value()) + elif knob.name() == "exr_datatype": + try: + node.node("Write1").knob("datatype").setValue(knob.value()) + except: + pass + elif knob.name() == "dpx_datatype": + try: + node.node("Write1").knob("datatype").setValue(knob.value()) + except: + pass + elif knob.name() == "convert_to_write": + self.convert_sg_to_nuke_write_nodes(selected_node=node) + elif knob.name() == "auto_crop": + try: + node.node("Write1").knob("autocrop").setValue(knob.value()) + except: + pass + elif knob.name() == "revert_to_version": + nuke.tprint("Reverting to last version.") else: # Propogate changes to certain knobs from the gizmo/group to the # encapsulated Write node. @@ -1951,7 +3033,14 @@ def __on_knob_changed(self): % (grp.name(), knob_name, grp.name(), write_node.name(), knob_name)) write_node.knob(knob_name).setValue(nuke.thisKnob().value()) - + node_channel = node['channels'].value() + + # Add channels info to cahce for reopen reference + try: + node['channels_cache'].setValue(node_channel) + except: + pass + def __get_current_script_path(self): """ Get the current script path (if the current script has been saved). This will @@ -1985,7 +3074,6 @@ def __get_current_script_path(self): return script_path - def __on_script_save(self): """ Called when the script is saved. @@ -2053,14 +3141,56 @@ def __on_user_create(self): self.setup_new_node(node) # populate the initial output name based on the render template: - render_template = self.get_render_template(node) + write_type = self.get_node_write_type_name(node) + render_template = self.get_render_template(node, write_type) self.__populate_initial_output_name(render_template, node) + def __hide_UI(self, node, ui_name_array, visibility): + + if not ui_name_array: + return + else: + for i in ui_name_array: + k = node.knob(i) + if k: + k.setVisible(visibility) + def __disable_UI(self, node, ui_name_array, enabled): + if not ui_name_array: + return + else: + for i in ui_name_array: + k = node.knob(i) + k.setEnabled(enabled) + def __update_knob_values(self, node, name, values_list): + if not values_list: + return None + else: + k = node.knob(name) + if k.values != values_list: + k.setValues(values_list) + def __ensure_unique_name(self, name, existing_node_names): - - \ No newline at end of file + # rename to our new default name: + postfix = 1 + while True: + new_name = "%s%d" % (name, postfix) + if new_name not in existing_node_names: + return new_name + break + else: + postfix += 1 + + def __get_list_index(self, the_list, item_name): + + + if the_list: + if item_name in the_list: + return the_list.index(item_name) + else: + nuke.tprint ("Couldn't find the given item_name in list") + return 0 \ No newline at end of file