{"action":"created","comment":{"url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/pulls/comments/47151846","id":47151846,"diff_hunk":"@@ -1,71 +1,739 @@\n-# Copyright (c) 2013 Shotgun Software Inc.\n-# \n+# Copyright (c) 2015 Shotgun Software Inc.\n+#\n # CONFIDENTIAL AND PROPRIETARY\n-# \n-# This work is provided \"AS IS\" and subject to the Shotgun Pipeline Toolkit \n+#\n+# This work is provided \"AS IS\" and subject to the Shotgun Pipeline Toolkit\n # Source Code License included in this distribution package. See LICENSE.\n-# By accessing, using, copying or modifying this work you indicate your \n-# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights \n+# By accessing, using, copying or modifying this work you indicate your\n+# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights\n # not expressly granted therein are reserved by Shotgun Software Inc.\n \n+# built-ins\n import os\n+import sys\n \n+# houdini\n import hou\n+\n+# toolkit\n import sgtk\n \n \n-class ToolkitMantraNodeHandler(object):\n-    def __init__(self, app):\n-        self._app = app\n-        self._work_file_template = self._app.get_template(\"work_file_template\")\n+class TkMantraNodeHandler(object):\n+    \"\"\"Handle Tk Mantra node operations and callbacks.\"\"\"\n+\n+    ############################################################################\n+    # Class data\n+\n+    # mostly a collection of strings that are reused throughout the handler.  \n+\n+    HOU_MANTRA_NODE_TYPE = \"ifd\"\n+    \"\"\"Houdini type for mantra node.\"\"\"\n+\n+    NODE_OUTPUT_PATH_PARM = \"sgtk_vm_filename\"\n+\n+    TK_EXTRA_PLANE_COUNT_PARM = 'vm_numaux'\n+    \"\"\"Parameter that stores the number of aov planes.\"\"\"\n+\n+    TK_EXTRA_PLANE_TEMPLATE_MAPPING = {\n+        'sgtk_vm_filename_plane#': 'output_extra_plane_template'\n+    }\n+    \"\"\"Maps additional plane parameter names to output template names\"\"\"\n+\n+    TK_EXTRA_PLANES_NAME = \"sgtk_aov_name_%s\"\n+    \"\"\"Placeholder used to format extra plane names\"\"\"\n+\n+    TK_INIT_PARM_NAME = \"sgtk_initialized\"\n+    \"\"\"Parameter used to store whether a tk mantra node has been initialized.\"\"\"\n+\n+    TK_HIP_PATH_PARM_NAME = 'sgtk_hip_path'\n+    \"\"\"Holds cached path to the hip file.\"\"\"\n+\n+    TK_MANTRA_NODE_TYPE = \"sgtk_mantra\"\n+    \"\"\"The clase of node as defined in Houdini for the Mantra nodes.\"\"\"\n+\n+    TK_OUTPUT_PROFILE_PARM = \"sgtk_output_profile\" \n+    \"\"\"The name of the parameter that stores the current output profile.\"\"\"\n+\n+    TK_OUTPUT_PROFILE_NAME_KEY = \"tk_output_profile_name\"\n+    \"\"\"The key in the user data that stores the output profile name.\"\"\"\n+\n+    TK_RENDER_TEMPLATE_MAPPING = {\n+        \"sgtk_soho_diskfile\": \"output_ifd_template\",\n+        \"sgtk_vm_dcmfilename\": \"output_dcm_template\",\n+        \"sgtk_vm_filename\": \"output_render_template\",\n+    }\n+    \"\"\"Mapping between tk mantra parms and corresponding render templates.\"\"\"\n+\n+    TK_RESET_PARM_NAMES = [\n+        \"soho_compression\",\n+        \"soho_mkpath\",\n+        \"vm_device\",\n+        \"vm_image_exr_compression\",\n+        \"vm_image_jpeg_quality\",\n+        \"vm_image_tiff_compression\",\n+    ]\n+    \"\"\"The default parameters to reset when the profile changes.\"\"\"\n+\n+    TK_DEFAULT_UPDATE_PARM_MAPPING = {     \n+        \"sgtk_soho_diskfile\": \"soho_diskfile\",\n+        \"sgtk_vm_dcmfilename\": \"vm_dcmfilename\",\n+        \"sgtk_vm_picture\": \"vm_picture\",\n+    }\n+    \"\"\"Map tk parms to mantra node parms.\"\"\"\n+\n+    ############################################################################\n+    # Class methods\n+\n+    @classmethod\n+    def convert_back_to_tk_mantra_nodes(cls, app):\n+        \"\"\"Convert Mantra nodes back to Toolkit Mantra nodes.\n+\n+        :param app: The calling Toolkit Application\n+\n+        Note: only converts nodes that had previously been Toolkit Mantra\n+        nodes.\n \n-    def _get_hipfile_fields(self):        \n         \"\"\"\n-        Extract fields from the current Houdini file using the template\n+\n+        # get all instances of the built-in mantra nodes\n+        mantra_nodes = hou.nodeType(\n+            hou.ropNodeTypeCategory(), cls.HOU_MANTRA_NODE_TYPE).instances()\n+\n+        if not mantra_nodes:\n+            app.log_debug(\"No Mantra Nodes found for conversion.\")\n+            return\n+        \n+        # iterate over all the mantra nodes and attempt to convert them\n+        for mantra_node in mantra_nodes:\n+\n+            # get the user data dictionary stored on the node\n+            user_dict = mantra_node.userDataDict()\n+\n+            # get the output_profile from the dictionary\n+            tk_output_profile_name = user_dict.get(\n+                cls.TK_OUTPUT_PROFILE_NAME_KEY)\n+\n+            if not tk_output_profile_name:\n+                app.log_warning(\n+                    \"Mantra node '%s' does not have an output profile name. \"\n+                    \"Can't convert to Tk Mantra node. Continuing.\" %\n+                    (mantra_node.name(),)\n+                )\n+                continue\n+\n+            # create new Shotgun Write node:\n+            tk_node_type = TkMantraNodeHandler.TK_MANTRA_NODE_TYPE\n+            tk_mantra_node = mantra_node.parent().createNode(tk_node_type)\n+\n+            # find the index of the stored name on the new tk mantra node\n+            # and set that item in the menu.\n+            try:\n+                output_profile_parm = tk_mantra_node.parm(\n+                    TkMantraNodeHandler.TK_OUTPUT_PROFILE_PARM)\n+                output_profile_index = output_profile_parm.menuLabels().index(\n+                    tk_output_profile_name)\n+                output_profile_parm.set(output_profile_index)\n+            except ValueError:\n+                app.log_warning(\"No output profile found named: %s\" % \n+                    (tk_output_profile_name,))\n+\n+            # copy over all parameter values except the output path \n+            _copy_parm_values(mantra_node, tk_mantra_node, excludes=[])\n+\n+            # explicitly copy AOV settings to the new tk mantra node\n+            plane_numbers = _get_extra_plane_numbers(mantra_node)\n+            for plane_number in plane_numbers:\n+                plane_parm_name = self.TK_EXTRA_PLANES_NAME % (plane_number,)\n+                aov_name = user_dict.get(plane_parm_name)\n+                tk_mantra_node.parm(plane_parm_name).set(aov_name)\n+\n+            # copy the inputs and move the outputs\n+            _copy_inputs(mantra_node, tk_mantra_node)\n+            _move_outputs(mantra_node, tk_mantra_node)\n+\n+            # make the new node the same color. the profile will set a color, \n+            # but do this just in case the user changed the color manually\n+            # prior to the conversion.\n+            tk_mantra_node.setColor(mantra_node.color())\n+\n+            # remember the name and position of the original mantra node\n+            mantra_node_name = mantra_node.name()\n+            mantra_node_pos = mantra_node.position()\n+\n+            # destroy the original mantra node\n+            mantra_node.destroy()\n+\n+            # name and reposition the new, regular mantra node to match the\n+            # original\n+            tk_mantra_node.setName(mantra_node_name)\n+            tk_mantra_node.setPosition(mantra_node_pos)\n+\n+            app.log_debug(\"Converted: Mantra node '%s' to TK Mantra node.\"\n+                % (mantra_node_name,))\n+\n+\n+    @classmethod\n+    def convert_to_regular_mantra_nodes(cls, app):\n+        \"\"\"Convert Toolkit Mantra nodes to regular Mantra nodes.\n+\n+        :param app: The calling Toolkit Application\n+\n         \"\"\"\n-        curr_filename = hou.hipFile.path()\n+\n+        # get all instances of tk mantra nodes\n+        tk_node_type = TkMantraNodeHandler.TK_MANTRA_NODE_TYPE\n+        tk_mantra_nodes = hou.nodeType(\n+            hou.ropNodeTypeCategory(), tk_node_type).instances()\n+\n+        if not tk_mantra_nodes:\n+            app.log_debug(\"No Toolkit Mantra Nodes found for conversion.\")\n+            return\n+\n+        for tk_mantra_node in tk_mantra_nodes:\n+\n+\n+            # create a new, regular Mantra node\n+            mantra_node = tk_mantra_node.parent().createNode(\n+                cls.HOU_MANTRA_NODE_TYPE)\n+\n+            # copy across knob values\n+            exclude_parms = [parm for parm in tk_mantra_node.parms() \n+                if parm.name().startswith(\"sgtk_\")]\n+            _copy_parm_values(tk_mantra_node, mantra_node,\n+                excludes=exclude_parms)\n+\n+            # store the mantra output profile name in the user data so that we\n+            # can retrieve it later.\n+            output_profile_parm = tk_mantra_node.parm(\n+                cls.TK_OUTPUT_PROFILE_PARM)\n+            tk_output_profile_name = \\\n+                output_profile_parm.menuLabels()[output_profile_parm.eval()]\n+            mantra_node.setUserData(cls.TK_OUTPUT_PROFILE_NAME_KEY, \n+                tk_output_profile_name)\n+\n+            # store AOV info on the new node\n+            plane_numbers = _get_extra_plane_numbers(tk_mantra_node)\n+            for plane_number in plane_numbers:\n+                plane_parm_name = self.TK_EXTRA_PLANES_NAME % (plane_number,)\n+                mantra_node.setUserData(plane_parm_name,\n+                    tk_mantra_node.parm(plane_parm_name).eval())\n+\n+            # copy the inputs and move the outputs\n+            _copy_inputs(tk_mantra_node, mantra_node)\n+            _move_outputs(tk_mantra_node, mantra_node)\n+\n+            # make the new node the same color\n+            mantra_node.setColor(tk_mantra_node.color())\n+\n+            # remember the name and position of the original tk mantra node\n+            tk_mantra_node_name = tk_mantra_node.name()\n+            tk_mantra_node_pos = tk_mantra_node.position()\n+\n+            # destroy the original tk mantra node\n+            tk_mantra_node.destroy()\n+\n+            # name and reposition the new, regular mantra node to match the\n+            # original\n+            mantra_node.setName(tk_mantra_node_name)\n+            mantra_node.setPosition(tk_mantra_node_pos)\n+\n+            app.log_debug(\"Converted: Tk Mantra node '%s' to Mantra node.\"\n+                % (tk_mantra_node_name,))\n+\n+\n+    ############################################################################\n+    # Instance methods\n+\n+    def __init__(self, app):\n+        \"\"\"Initialize the handler.\n         \n-        work_fields = {}\n-        if self._work_file_template and self._work_file_template.validate(curr_filename):\n-            work_fields = self._work_file_template.get_fields(curr_filename)\n-                \n-        return work_fields\n+        :params app: The application instance. \n+        \n+        \"\"\"\n+\n+        # keep a reference to the app for easy access to templates, settings,\n+        # logging methods, tank, context, etc.\n+        self._app = app\n+\n+        # get and cache the list of profiles defined in the settings\n+        self._output_profiles = {}\n+        for output_profile in self._app.get_setting(\"output_profiles\", []):\n+            output_profile_name = output_profile[\"name\"]\n+\n+            if output_profile_name in self._output_profiles:\n+                self._app.log_warning(\n+                    \"Found multiple output profiles named '%s' for the \"\n+                    \"Tk Mantra node! Only the first one will be available.\" %\n+                    (output_profile_name,)\n+                )\n+                continue\n+\n+            self._output_profiles[output_profile_name] = output_profile\n+            self._app.log_debug(\"Caching mantra output profile: '%s'\" % \n+                (output_profile_name,))\n+\n+\n+    ############################################################################\n+    # methods and callbacks executed via the OTL\n+\n+    def copy_path_to_clipboard(self):\n+\n+        render_path = self._get_render_path(hou.pwd())\n+\n+        # use Qt to copy the path to the clipboard:\n+        from sgtk.platform.qt import QtGui\n+        QtGui.QApplication.clipboard().setText(render_path)\n+\n+        self._app.log_debug(\n+            \"Copied render path to clipboard: %s\" % (render_path,))\n+\n+    # get labels for all tk-houdini-mantranode output profiles\n+    def get_output_profile_menu_labels(self):\n+\n+        menu_labels = []\n+        for count, output_profile_name in enumerate(self._output_profiles):\n+            menu_labels.extend([count, output_profile_name])\n+\n+        return menu_labels\n+\n+\n+    # returns a list of output path menu items for the current node\n+    def get_output_path_menu(self, node=None):\n+\n+        if not None:\n+            node = hou.pwd()\n+\n+        # is this the first time this has been created?\n+        is_first_run = (node.parm(self.TK_INIT_PARM_NAME).eval() == \"True\")\n+        if is_first_run:\n+            # set it to false for subsequent calls\n+            node.parm(is_first_run).set(\"False\")\n+\n+        # see if the hip file has changed\n+        hip_path_changed = (\n+            hou.hipFile.path() == node.parm(self.TK_HIP_PATH_PARM_NAME).eval())\n+\n+        if is_first_run or hip_path_changed:\n+            # make sure node is in default state.\n+            self.reset_render_path(node)\n+\n+            # cache current hip file path to compare against later\n+            node.parm(self.TK_HIP_PATH_PARM_NAME).set(hou.hipFile.path())\n+\n+        # get path from hidden parameter which acts like a cache.\n+        path = node.parm(self.NODE_OUTPUT_PATH_PARM).unexpandedString()\n+\n+        # Build the menu\n+        menu = [\"sgtk\", path,\n+                \"ip\", \"mplay (interactive)\",\n+                \"md\", \"mplay (non-interactive)\"]\n+\n+        return menu\n+\n+\n+    # Reset the render path of the specified node.  This will force the render\n+    # path to be updated based on the current script path and configuraton\n+    def reset_render_path(self, node=None):\n+\n+        if not node:\n+            node = hou.pwd()\n+\n+        # Checks to see if the supplied node is being copied. Houdini renames\n+        # the node by prepending original0_ to the original node when copying.\n+        if node.name().startswith('original0'):\n+            return\n+\n+        for (parm_name, template_name) in \\\n+            self.TK_RENDER_TEMPLATE_MAPPING.items():\n+            self._compute_and_set(node, parm_name, template_name)\n+\n+        # Extra Image Planes / AOVs\n+        plane_numbers = _get_extra_plane_numbers(node)\n+        for plane_number in plane_numbers:\n+            for (parm_name, template_name) in \\\n+                self.TK_EXTRA_PLANE_TEMPLATE_MAPPING.items():\n+                parm_name = parm_name.replace('#', str(plane_number))\n+                aov_name = node.parm(\n+                    self.TK_EXTRA_PLANES_NAME % (plane_number,)).eval()\n+                self._compute_and_set(node, parm_name, template_name, aov_name)\n+\n+        # set the output paths\n+        path = node.parm(self.NODE_OUTPUT_PATH_PARM).unexpandedString()\n+        node.parm('sgtk_vm_picture').set(path)\n+        node.parm('vm_picture').set(path)\n+\n+        self.update_parms(node)\n+\n+\n+    # apply the selected profile in the session\n+    def set_profile(self, node=None, reset=False):\n+\n+        if not node:\n+            node = hou.pwd()\n+\n+        output_profile = self._get_output_profile(node)\n+\n+        self._app.log_debug(\"Applying tk mantra node profile: %s\" % \n+            (output_profile[\"name\"],))\n+\n+        # reset some parameters if need be\n+        if reset:\n+            for parm_name in self.TK_RESET_PARM_NAMES:\n+                parm = node.parm(parm_name)\n+                if parm:\n+                    parm.revertToDefaults()\n+\n+            node.setColor(hou.Color([.8, .8, .8]))\n+\n+        # apply the supplied settings to the node\n+        settings = output_profile[\"settings\"]\n+        if settings:\n+            self._app.log_debug('Populating format settings: %s' % \n+                (file_settings,))\n+            node.setParms(settings)\n+\n+        # set the node color\n+        color = output_profile[\"color\"]\n+        if color:\n+            node.setColor(hou.Color(color))\n+\n+        self.reset_render_path(node)\n+\n+\n+    # open a file browser showing the render path of the current node\n+    def show_in_fs(self):\n+\n+        # retrieve the calling node\n+        current_node = hou.pwd()\n+        if not current_node:\n+            return\n+\n+        render_dir = None\n+\n+        # first, try to just use the current cached path:\n+        render_path = self._get_render_path(current_node)\n+\n+        if render_path:\n+            # the above method returns houdini style slashes, so ensure these\n+            # are pointing correctly\n+            render_path = render_path.replace(\"/\", os.path.sep)\n+\n+            dir_name = os.path.dirname(render_path)\n+            if os.path.exists(dir_name):\n+                render_dir = dir_name\n+\n+        if not render_dir:\n+            # render directory doesn't exist so try using location\n+            # of rendered frames instead:\n+            rendered_files = self._get_rendered_files(current_node)\n+\n+            if not rendered_files:\n+                msg = (\"Unable to find rendered files for node '%s'.\" \n+                       % (current_node,))\n+                self._app.log_error(msg)\n+                hou.ui.displayMessage(msg)\n+                return\n+            else:\n+                render_dir = os.path.dirname(rendered_files[0])\n+\n+        # if we have a valid render path then show it:\n+        if render_dir:\n+            # TODO: move to utility method in core\n+            system = sys.platform\n+\n+            # run the app\n+            if system == \"linux2\":\n+                cmd = \"xdg-open \\\"%s\\\"\" % render_dir\n+            elif system == \"darwin\":\n+                cmd = \"open '%s'\" % render_dir\n+            elif system == \"win32\":\n+                cmd = \"cmd.exe /C start \\\"Folder\\\" \\\"%s\\\"\" % render_dir\n+            else:\n+                msg = \"Platform '%s' is not supported.\" % (system,)\n+                self._app.log_error(msg)\n+                hou.ui.displayMessage(msg)\n+\n+            self._app.log_debug(\"Executing command:\\n '%s'\" % (cmd,))\n+            exit_code = os.system(cmd)\n+            if exit_code != 0:\n+                msg = \"Failed to launch '%s'!\" % (cmd,)\n+                hou.ui.displayMessage(msg)\n+\n+    # called when the node is created\n+    def setup_node(self, node):\n+        \n+        default_name = self._app.get_setting('default_node_name')\n+        node.setName(default_name, unique_name=True)\n+\n+        # apply the default profile\n+        self.set_profile(node, reset=True)\n+\n+        # make sure the render paths are in default state\n+        self.reset_render_path(node)\n+\n+\n+    def update_parms(self, node=None):\n+\n+        if not node:\n+            node = hou.pwd()\n+\n+        # copies the value of one parm to another\n+        copy_parm = lambda p1, p2: \\\n+            node.parm(p2).set(node.parm(p1).unexpandedString())\n+\n+        # copy the default udpate parms\n+        for parm1, parm2 in self.TK_DEFAULT_UPDATE_PARM_MAPPING.items():\n+            copy_parm(parm1, parm2)\n+\n+        # handle additional planes\n+        plane_numbers = _get_extra_plane_numbers(node)\n+        for plane_number in plane_numbers:\n+            for parm1, parm2 in \\\n+                self.TK_EXTRA_PLANE_TEMPLATE_MAPPING.items():\n+                parm1 = parm1.replace('#', str(num))\n+                parm2 = parm2.replace('#', str(num))\n+                copy_parm(parm1, parm2)\n+\n+    \n+    # Callback for \"Different File\" checkbox on every Extra Image Plane.  Sets\n+    # the AOV Name to Channel Name or VEX Variable.  Resets the render paths to\n+    # update the path for this AOV.  Sets the Label to \"Disabled.\" when it is\n+    # unchecked.\n+    def use_file_plane(self, node, parm):\n+\n+        # replace the parm basename with nothing, leaving the plane number\n+        plane_number = parm.name().replace('vm_usefile_plane', '')\n+\n+        if parm.eval():\n+\n+            value = node.parm(\"vm_channel_plane%s\" % (num,)).eval()\n+            if not value:\n+                value = node.parm(\"vm_variable_plane%s\" % (num,)).eval()\n+            node.parm(self.TK_EXTRA_PLANES_NAME % (num,)).set(value)\n+            self.reset_render_path(node)\n+        else:\n+            path_parm = node.parm(\"sgtk_vm_filename_plane%s\" % (num,))\n+            path_parm.lock(False)\n+            path_parm.set('Disabled')\n+            path_parm.lock(True)\n+            path_node.parm(self.TK_EXTRA_PLANES_NAME % (num,)).set(\"\")\n+\n+\n+    ############################################################################\n+    # Private methods\n+\n+    # compute and set and output path for the supplied parm\n+    def _compute_and_set(self, node, parm_name, template_name, aov_name=None):\n+\n+        try:\n+            path = self._compute_output_path(node, template_name, aov_name)\n+        except sgtk.TankError as err:\n+            self._app.log_warning(\"%s: %s\" % (node.name(), err))\n+            path = \"ERROR: %s\" % (err,)\n+\n+        # Unlock, set, lock\n+        node.parm(parm_name).lock(False)\n+        node.parm(parm_name).set(path)\n+        node.parm(parm_name).lock(True)\n+\n+\n+    # compute the output path based on the current work file and render template\n+    def _compute_output_path(self, node, template_name, aov_name=None):\n \n-    def compute_path(self, node):\n         # Get relevant fields from the scene filename and contents\n         work_file_fields = self._get_hipfile_fields()\n+\n         if not work_file_fields:\n-            raise sgtk.TankError(\"This Houdini file is not a Shotgun Toolkit work file!\")\n+            msg = \"This Houdini file is not a Shotgun Toolkit work file!\"\n+            raise sgtk.TankError(msg)\n \n-        # Get the templates from the app\n-        template = self._app.get_template(\"work_render_template\")\n+        output_profile = self._get_output_profile(node)\n+\n+        # Get the render template from the app\n+        output_template = self._app.get_template_by_name(\n+            output_profile[template_name])\n \n         # create fields dict with all the metadata\n-        fields = {}\n-        fields[\"name\"] = work_file_fields.get(\"name\")\n-        fields[\"version\"] = work_file_fields[\"version\"]\n-        fields[\"node\"] = node.name()\n-        fields[\"SEQ\"] = \"FORMAT: $F\"\n+        fields = {\n+            \"name\": work_file_fields.get(\"name\", None),\n+            \"node\": node.name(),\n+            \"renderpass\": node.name(),\n+            \"SEQ\": \"FORMAT: $F\",\n+            \"version\": work_file_fields.get(\"version\", None),\n+        } \n+\n+        # use %V - full view printout as default for the eye field\n+        fields[\"eye\"] = \"%V\"\n+\n+        if aov_name:\n+            fields[\"aov_name\"] = aov_name\n \n         # Get the camera width and height if necessary\n-        if \"width\" in template.keys or \"height\" in template.keys:\n-            # Get the camera\n-            cam_path = node.parm(\"mantra1_camera\").eval()\n-            cam_node = hou.node(cam_path)\n-            if not cam_node:\n-                raise sgtk.TankError(\"Camera %s not found.\" % cam_path)\n+        if \"width\" in output_template.keys or \"height\" in output_template.keys:\n+            width, height = _get_render_resolution(node)\n+            fields[\"width\"] = width\n+            fields[\"height\"] = height\n+\n+        fields.update(self._app.context.as_template_fields(output_template))\n+\n+        path = output_template.apply_fields(fields)\n+        path = path.replace(os.path.sep, '/')\n+\n+        return path\n+\n+\n+    # get the current output profile\n+    def _get_output_profile(self, node=None):\n+\n+        if not node:\n+            node = hou.pwd()\n+\n+        output_profile_parm = node.parm(self.TK_OUTPUT_PROFILE_PARM)\n+        output_profile_name = \\\n+            output_profile_parm.menuLabels()[output_profile_parm.eval()]\n+        return self._output_profiles[output_profile_name]\n+\n+\n+    # extract fields from current Houdini file using the workfile template\n+    def _get_hipfile_fields(self):\n+        current_file_path = hou.hipFile.path()\n+\n+        work_fields = {}\n+        work_file_template = self._app.get_template(\"work_file_template\")\n+        if (work_file_template and \n+            work_file_template.validate(current_file_path)):\n+            work_fields = work_file_template.get_fields(current_file_path)\n+\n+        return work_fields\n+\n+\n+    # get the render path from current item in the output path parm menu\n+    def _get_render_path(self, node):\n+        output_parm = node.parm(self.NODE_OUTPUT_PATH_PARM)\n+        return output_parm.unexpandedString()\n+\n+    \n+    # returns the files on disk associated with this node\n+    def _get_rendered_files(self, node):\n+\n+        file_name = self._get_render_path(node)\n+        output_profile = self._get_output_profile(node)\n+\n+        # get the output cache template for the current profile\n+        output_render_template = self._app.get_template_by_name(\n+            output_profile[\"output_render_template\"])\n+\n+        if not output_render_template.validate(file_name):\n+            msg = (\"Unable to validate files on disk for node %s.\"\n+                   \"The path '%s' is not recognized by Shotgun.\"\n+                   % (node.name(), file_name))\n+            self._app.log_error(msg)\n+            return []\n+            \n+        fields = output_render_template.get_fields(file_name)\n+\n+        # get the actual file paths based on the template. Ignore any sequence\n+        # or eye fields\n+        return self._app.tank.paths_from_template(\n+            output_render_template, fields, [\"SEQ\", \"eye\"])\n+\n+\n+################################################################################\n+# Utility methods\n \n-            fields[\"width\"] = cam_node.parm(\"resx\").eval()\n-            fields[\"height\"] = cam_node.parm(\"resy\").eval()\n+# Copy all the input connections from this node to the target node.\n+def _copy_inputs(source_node, target_node):\n \n-        fields.update(self._app.context.as_template_fields(template))\n+    input_connections = source_node.inputConnections()\n+    num_target_inputs = len(target_node.inputConnectors())\n+\n+    if len(input_connections) > num_target_inputs:\n+        raise hou.InvalidInput(\n+            \"Not enough inputs on target node. Cannot copy inputs from \"\n+            \"'%s' to '%s'\" % (source_node, target_node)\n+        )\n         \n-        path = template.apply_fields(fields)\n+    for connection in input_connections:\n+        target_node.setInput(connection.inputIndex(),\n+            connection.inputNode())\n \n-        # TODO: Move this out to some sort of pre-render callback\n-        out_dir = os.path.dirname(path)\n-        self._app.ensure_folder_exists(out_dir)\n \n-        path = path.replace(os.path.sep, \"/\")\n+# Copy parameter values of the source node to those of the target node if a\n+# parameter with the same name exists.\n+def _copy_parm_values(source_node, target_node, excludes=None):\n+\n+    if not excludes:\n+        excludes = []\n+\n+    # build a parameter list from the source node, ignoring the excludes\n+    source_parms = [\n+        parm for parm in source_node.parms() if parm.name() not in excludes]\n+\n+    for source_parm in source_parms:\n+\n+        source_parm_template = source_parm.parmTemplate()\n+\n+        # skip folder parms\n+        if isinstance(source_parm_template, hou.FolderSetParmTemplate):\n+            continue\n+\n+        target_parm = target_node.parm(source_parm.name())\n+\n+        # if the parm on the target node doesn't exist, skip it\n+        if target_parm is None:\n+            continue\n+\n+        # if we have keys/expressions we need to copy them all.\n+        if source_parm.keyframes():\n+            for key in source_parm.keyframes():\n+                target_parm.setKeyframe(key)\n+        else:\n+            # if the parameter is a string, copy the raw string.\n+            if isinstance(source_parm_template, hou.StringParmTemplate):\n+                target_parm.set(source_parm.unexpandedString())\n+            # copy the evaluated value\n+            else:\n+                target_parm.set(source_parm.eval())\n+\n+\n+# return a list of aov plane nubmers\n+def _get_extra_plane_numbers(node):\n+    return xrange(1,\n+        node.parm(TkMantraNodeHandler.TK_EXTRA_PLANE_COUNT_PARM).eval() + 1)\n+\n+\n+# get the render resolution for the supplied node based on its camera parm\n+def _get_render_resolution(node):\n+\n+    # Get the camera\n+    cam_path = node.parm(\"camera\").eval()\n+    cam_node = hou.node(cam_path)\n+\n+    if not cam_node:\n+        raise sgtk.TankError(\"Camera %s not found.\" % (cam_path,))\n+\n+    width = cam_node.parm(\"resx\").eval()\n+    height = cam_node.parm(\"resy\").eval()\n+\n+    # Calculate Resolution Override\n+    if node.parm('override_camerares').eval():\n+        scale = node.parm('res_fraction').eval()\n+        if scale == 'specific':\n+            width = node.parm('res_overridex').eval()\n+            height = node.parm('res_overridey').eval()\n+        else:","path":"python/tk_houdini_mantranode/handler.py","position":766,"original_position":766,"commit_id":"130d5d1f2dd22b4d4ffc1e22337c30d75dcffbe9","original_commit_id":"130d5d1f2dd22b4d4ffc1e22337c30d75dcffbe9","user":{"login":"robblau","id":319506,"avatar_url":"https://avatars.githubusercontent.com/u/319506?v=3","gravatar_id":"","url":"https://api.github.com/users/robblau","html_url":"https://github.com/robblau","followers_url":"https://api.github.com/users/robblau/followers","following_url":"https://api.github.com/users/robblau/following{/other_user}","gists_url":"https://api.github.com/users/robblau/gists{/gist_id}","starred_url":"https://api.github.com/users/robblau/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/robblau/subscriptions","organizations_url":"https://api.github.com/users/robblau/orgs","repos_url":"https://api.github.com/users/robblau/repos","events_url":"https://api.github.com/users/robblau/events{/privacy}","received_events_url":"https://api.github.com/users/robblau/received_events","type":"User","site_admin":false},"body":"double quotes","created_at":"2015-12-09T21:08:53Z","updated_at":"2015-12-09T21:08:53Z","html_url":"https://github.com/shotgunsoftware/tk-houdini-mantranode/pull/2#discussion_r47151846","pull_request_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/pulls/2","_links":{"self":{"href":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/pulls/comments/47151846"},"html":{"href":"https://github.com/shotgunsoftware/tk-houdini-mantranode/pull/2#discussion_r47151846"},"pull_request":{"href":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/pulls/2"}}},"pull_request":{"url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/pulls/2","id":53133081,"html_url":"https://github.com/shotgunsoftware/tk-houdini-mantranode/pull/2","diff_url":"https://github.com/shotgunsoftware/tk-houdini-mantranode/pull/2.diff","patch_url":"https://github.com/shotgunsoftware/tk-houdini-mantranode/pull/2.patch","issue_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/issues/2","number":2,"state":"open","locked":false,"title":"#31683 Update of the tk-houdini-mantranode from Pixomondo","user":{"login":"josh-t","id":2604646,"avatar_url":"https://avatars.githubusercontent.com/u/2604646?v=3","gravatar_id":"","url":"https://api.github.com/users/josh-t","html_url":"https://github.com/josh-t","followers_url":"https://api.github.com/users/josh-t/followers","following_url":"https://api.github.com/users/josh-t/following{/other_user}","gists_url":"https://api.github.com/users/josh-t/gists{/gist_id}","starred_url":"https://api.github.com/users/josh-t/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/josh-t/subscriptions","organizations_url":"https://api.github.com/users/josh-t/orgs","repos_url":"https://api.github.com/users/josh-t/repos","events_url":"https://api.github.com/users/josh-t/events{/privacy}","received_events_url":"https://api.github.com/users/josh-t/received_events","type":"User","site_admin":false},"body":"This pull requests represents the updated version of the tk-houdini-mantranode. The functionality comes from the team at Pixomondo. This diff shows those changes organized and documented to match Tk standards.","created_at":"2015-12-09T15:26:51Z","updated_at":"2015-12-09T21:08:53Z","closed_at":null,"merged_at":null,"merge_commit_sha":"21b0eba49263deceb78adf3de5e77666fa990a56","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/pulls/2/commits","review_comments_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/pulls/2/comments","review_comment_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/pulls/comments{/number}","comments_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/issues/2/comments","statuses_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/statuses/130d5d1f2dd22b4d4ffc1e22337c30d75dcffbe9","head":{"label":"shotgunsoftware:31683_pixo_merge","ref":"31683_pixo_merge","sha":"130d5d1f2dd22b4d4ffc1e22337c30d75dcffbe9","user":{"login":"shotgunsoftware","id":317894,"avatar_url":"https://avatars.githubusercontent.com/u/317894?v=3","gravatar_id":"","url":"https://api.github.com/users/shotgunsoftware","html_url":"https://github.com/shotgunsoftware","followers_url":"https://api.github.com/users/shotgunsoftware/followers","following_url":"https://api.github.com/users/shotgunsoftware/following{/other_user}","gists_url":"https://api.github.com/users/shotgunsoftware/gists{/gist_id}","starred_url":"https://api.github.com/users/shotgunsoftware/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shotgunsoftware/subscriptions","organizations_url":"https://api.github.com/users/shotgunsoftware/orgs","repos_url":"https://api.github.com/users/shotgunsoftware/repos","events_url":"https://api.github.com/users/shotgunsoftware/events{/privacy}","received_events_url":"https://api.github.com/users/shotgunsoftware/received_events","type":"Organization","site_admin":false},"repo":{"id":12335331,"name":"tk-houdini-mantranode","full_name":"shotgunsoftware/tk-houdini-mantranode","owner":{"login":"shotgunsoftware","id":317894,"avatar_url":"https://avatars.githubusercontent.com/u/317894?v=3","gravatar_id":"","url":"https://api.github.com/users/shotgunsoftware","html_url":"https://github.com/shotgunsoftware","followers_url":"https://api.github.com/users/shotgunsoftware/followers","following_url":"https://api.github.com/users/shotgunsoftware/following{/other_user}","gists_url":"https://api.github.com/users/shotgunsoftware/gists{/gist_id}","starred_url":"https://api.github.com/users/shotgunsoftware/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shotgunsoftware/subscriptions","organizations_url":"https://api.github.com/users/shotgunsoftware/orgs","repos_url":"https://api.github.com/users/shotgunsoftware/repos","events_url":"https://api.github.com/users/shotgunsoftware/events{/privacy}","received_events_url":"https://api.github.com/users/shotgunsoftware/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/shotgunsoftware/tk-houdini-mantranode","description":"","fork":false,"url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode","forks_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/forks","keys_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/keys{/key_id}","collaborators_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/teams","hooks_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/hooks","issue_events_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/issues/events{/number}","events_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/events","assignees_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/assignees{/user}","branches_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/branches{/branch}","tags_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/tags","blobs_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/git/refs{/sha}","trees_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/git/trees{/sha}","statuses_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/statuses/{sha}","languages_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/languages","stargazers_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/stargazers","contributors_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/contributors","subscribers_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/subscribers","subscription_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/subscription","commits_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/commits{/sha}","git_commits_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/git/commits{/sha}","comments_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/comments{/number}","issue_comment_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/issues/comments{/number}","contents_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/contents/{+path}","compare_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/compare/{base}...{head}","merges_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/merges","archive_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/downloads","issues_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/issues{/number}","pulls_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/pulls{/number}","milestones_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/milestones{/number}","notifications_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/labels{/name}","releases_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/releases{/id}","created_at":"2013-08-24T00:16:03Z","updated_at":"2015-03-21T04:14:38Z","pushed_at":"2015-12-09T15:26:52Z","git_url":"git://github.com/shotgunsoftware/tk-houdini-mantranode.git","ssh_url":"git@github.com:shotgunsoftware/tk-houdini-mantranode.git","clone_url":"https://github.com/shotgunsoftware/tk-houdini-mantranode.git","svn_url":"https://github.com/shotgunsoftware/tk-houdini-mantranode","homepage":null,"size":385,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"open_issues_count":1,"forks":2,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"shotgunsoftware:master","ref":"master","sha":"a5f257708d74468d232bd528a1d2bb8581b90e9f","user":{"login":"shotgunsoftware","id":317894,"avatar_url":"https://avatars.githubusercontent.com/u/317894?v=3","gravatar_id":"","url":"https://api.github.com/users/shotgunsoftware","html_url":"https://github.com/shotgunsoftware","followers_url":"https://api.github.com/users/shotgunsoftware/followers","following_url":"https://api.github.com/users/shotgunsoftware/following{/other_user}","gists_url":"https://api.github.com/users/shotgunsoftware/gists{/gist_id}","starred_url":"https://api.github.com/users/shotgunsoftware/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shotgunsoftware/subscriptions","organizations_url":"https://api.github.com/users/shotgunsoftware/orgs","repos_url":"https://api.github.com/users/shotgunsoftware/repos","events_url":"https://api.github.com/users/shotgunsoftware/events{/privacy}","received_events_url":"https://api.github.com/users/shotgunsoftware/received_events","type":"Organization","site_admin":false},"repo":{"id":12335331,"name":"tk-houdini-mantranode","full_name":"shotgunsoftware/tk-houdini-mantranode","owner":{"login":"shotgunsoftware","id":317894,"avatar_url":"https://avatars.githubusercontent.com/u/317894?v=3","gravatar_id":"","url":"https://api.github.com/users/shotgunsoftware","html_url":"https://github.com/shotgunsoftware","followers_url":"https://api.github.com/users/shotgunsoftware/followers","following_url":"https://api.github.com/users/shotgunsoftware/following{/other_user}","gists_url":"https://api.github.com/users/shotgunsoftware/gists{/gist_id}","starred_url":"https://api.github.com/users/shotgunsoftware/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shotgunsoftware/subscriptions","organizations_url":"https://api.github.com/users/shotgunsoftware/orgs","repos_url":"https://api.github.com/users/shotgunsoftware/repos","events_url":"https://api.github.com/users/shotgunsoftware/events{/privacy}","received_events_url":"https://api.github.com/users/shotgunsoftware/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/shotgunsoftware/tk-houdini-mantranode","description":"","fork":false,"url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode","forks_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/forks","keys_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/keys{/key_id}","collaborators_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/teams","hooks_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/hooks","issue_events_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/issues/events{/number}","events_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/events","assignees_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/assignees{/user}","branches_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/branches{/branch}","tags_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/tags","blobs_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/git/refs{/sha}","trees_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/git/trees{/sha}","statuses_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/statuses/{sha}","languages_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/languages","stargazers_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/stargazers","contributors_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/contributors","subscribers_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/subscribers","subscription_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/subscription","commits_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/commits{/sha}","git_commits_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/git/commits{/sha}","comments_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/comments{/number}","issue_comment_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/issues/comments{/number}","contents_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/contents/{+path}","compare_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/compare/{base}...{head}","merges_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/merges","archive_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/downloads","issues_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/issues{/number}","pulls_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/pulls{/number}","milestones_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/milestones{/number}","notifications_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/labels{/name}","releases_url":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/releases{/id}","created_at":"2013-08-24T00:16:03Z","updated_at":"2015-03-21T04:14:38Z","pushed_at":"2015-12-09T15:26:52Z","git_url":"git://github.com/shotgunsoftware/tk-houdini-mantranode.git","ssh_url":"git@github.com:shotgunsoftware/tk-houdini-mantranode.git","clone_url":"https://github.com/shotgunsoftware/tk-houdini-mantranode.git","svn_url":"https://github.com/shotgunsoftware/tk-houdini-mantranode","homepage":null,"size":385,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"open_issues_count":1,"forks":2,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/pulls/2"},"html":{"href":"https://github.com/shotgunsoftware/tk-houdini-mantranode/pull/2"},"issue":{"href":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/issues/2"},"comments":{"href":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/issues/2/comments"},"review_comments":{"href":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/pulls/2/comments"},"review_comment":{"href":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/pulls/2/commits"},"statuses":{"href":"https://api.github.com/repos/shotgunsoftware/tk-houdini-mantranode/statuses/130d5d1f2dd22b4d4ffc1e22337c30d75dcffbe9"}}}}