"""Hook class to handle Houdini Mantra nodes.
Designed to inherit ``base_render_handler`` i.e. define the ``hook`` attribute
in your project configuration settings with the following inheritance:
.. code-block:: yaml
:caption: env/includes/settings/tk-houdini_node_handlers.yml
node_handlers.shot_step:
- node_type: ifd
node_category: Driver
hook: "{self}/node_handlers/base_export_handler.py:{self}/node_handlers/base_render_handler.py:{self}/node_handlers/ifd_handler.py"
work_template: houdini_shot_render
publish_template: houdini_shot_publish_render
extra_args:
aov_work_template: houdini_shot_work_extra_plane
aov_publish_template: houdini_shot_publish_extra_plane
dcm_work_template: houdini_shot_work_dcm
dcm_publish_template: houdini_shot_publish_dcm
dsm_work_template: houdini_shot_work_dsm
dsm_publish_template: houdini_shot_publish_dsm
ifd_work_template: houdini_shot_ifd
ifd_publish_template: houdini_shot_publish_ifd
manifest_name_template: houdini_cryptomatte_json_name
"""
import sgtk
import hou
[docs]HookBaseClass = sgtk.get_hook_baseclass()
[docs]class IfdNodeHandler(HookBaseClass):
"""
Node handler for mantra renders in Houdini.
"""
[docs] NODE_CATEGORY = "Driver"
[docs] OUTPUT_PARM = "vm_picture"
# aovs
[docs] AOV_COUNT = "vm_numaux"
[docs] AOV_NAME_TMPL = "vm_channel_plane{}"
[docs] AOV_FILE_TMPL = "vm_filename_plane{}"
[docs] AOV_USE_FILE_TMPL = "vm_usefile_plane{}"
# deep output
[docs] VM_DCMFILENAME = "vm_dcmfilename"
[docs] VM_DSMFILENAME = "vm_dsmfilename"
# cryptomatte
[docs] VM_CRYPTOLAYERS = "vm_cryptolayers"
[docs] VM_CRYPTOLAYERNAME_TMPL = "vm_cryptolayername{}"
[docs] VM_CRYPTOLAYEROUTPUT_TMPL = "vm_cryptolayeroutput{}"
[docs] VM_CRYPTOLAYEROUTPUTENABLE_TMPL = "vm_cryptolayeroutputenable{}"
[docs] VM_CRYPTOLAYERSIDECAR_TMPL = "vm_cryptolayersidecar{}"
# ifd
[docs] ARCHIVE_ENABLED = "soho_outputmode"
[docs] ARCHIVE_OUTPUT = "soho_diskfile"
# shotgun
[docs] SGTK_AOV_NAME_TMPL = "sgtk_channel_plane{}"
[docs] SGTK_DEEP_EXT = "sgtk_deep_extension"
[docs] SGTK_CRYPTOLAYERNAME_TMPL = "sgtk_cryptolayername{}"
# templates
[docs] DCM_WORK_TEMPLATE = "dcm_work_template"
[docs] DCM_PUBLISH_TEMPLATE = "dcm_publish_template"
[docs] DSM_WORK_TEMPLATE = "dsm_work_template"
[docs] DSM_PUBLISH_TEMPLATE = "dsm_publish_template"
[docs] ARCHIVE_WORK_TEMPLATE = "ifd_work_template"
[docs] ARCHIVE_PUBLISH_TEMPLATE = "ifd_publish_template"
[docs] MANIFEST_NAME_TEMPLATE = "manifest_name_template"
# strings
[docs] AOV_ERROR = "Channel Name not defined"
[docs] def _update_aov_paths(self, node):
"""
Update all the aov output paths on the node.
:param node: A :class:`hou.Node` instance.
"""
super(IfdNodeHandler, self)._update_aov_paths(node)
vm_cryptolayers = node.parm(self.VM_CRYPTOLAYERS)
count = vm_cryptolayers.eval() + 1
for index in range(1, count):
vm_cryptolayername = node.parm(self.VM_CRYPTOLAYERNAME_TMPL.format(index))
sgtk_cryptolayername = node.parm(
self.SGTK_CRYPTOLAYERNAME_TMPL.format(index)
)
sgtk_cryptolayername.set(vm_cryptolayername.unexpandedString())
self._update_crypto_layer_path(node, index)
[docs] def _lock_parms(self, node, lock):
"""
Lock parms on the node is shotgun is enabled.
:param node: A :class:`hou.Node` instance.
:param bool lock: Lock parms if True.
"""
super(IfdNodeHandler, self)._lock_parms(node, lock)
parm_names = (self.VM_DCMFILENAME, self.VM_DSMFILENAME)
for parm_name in parm_names:
parm = node.parm(parm_name)
parm.lock(lock)
self._lock_crypto_parms(node, lock)
#############################################################################################
# Overriden methods
#############################################################################################
[docs] def _customise_parameter_group(self, node, parameter_group, sgtk_folder):
"""
Here is where you define where the sgtk folder is to be placed, but also
any other parameters that you wish to add to the node.
:param node: A :class:`hou.Node` instance.
:param parameter_group: The node's :class:`ParmGroup`.
:param sgtk_folder: A :class:`hou.ParmFolderTemplate` containing sgtk
parameters.
"""
index = parameter_group.index_of_template("images6")
parameter_group.insert_template(index, sgtk_folder)
# Insert sgtk folder before vm_picture
images_folder = parameter_group.get("images6")
# aovs
image_planes_folder = images_folder.get("output6_1")
vm_numaux = image_planes_folder.get(self.AOV_COUNT)
vm_numaux_template = vm_numaux.template
vm_numaux_template.setScriptCallback(
self.generate_callback_script_str("lock_aov_parms")
)
vm_numaux_template.setScriptCallbackLanguage(hou.scriptLanguage.Python)
index = vm_numaux.index_of_template(self.AOV_NAME_TMPL.format("#"))
sgtk_aov_name = hou.StringParmTemplate(
self.SGTK_AOV_NAME_TMPL.format("#"),
"Channel Name",
1,
script_callback=self.generate_callback_script_str("update_aov_path"),
script_callback_language=hou.scriptLanguage.Python,
)
sgtk_aov_name.setConditional(
hou.parmCondType.DisableWhen,
'{ vm_disable_plane# == 1 } { vm_variable_plane# == "" }',
)
sgtk_aov_name.setConditional(hou.parmCondType.HideWhen, "{ use_sgtk != 1 }")
vm_numaux.insert_template(index, sgtk_aov_name)
vm_channel_plane = vm_numaux.get(self.AOV_NAME_TMPL.format("#"))
vm_channel_plane_template = vm_channel_plane.template
vm_channel_plane_template.setConditional(
hou.parmCondType.HideWhen, "{ use_sgtk == 1 }"
)
vm_filename_plane = vm_numaux.get(self.AOV_FILE_TMPL.format("#"))
vm_filename_plane_template = vm_filename_plane.template
vm_filename_plane_template.setDefaultValue((self.AOV_ERROR,))
# deep
deep_folder = images_folder.get("output6_2")
index = deep_folder.index_of_template(self.VM_DCMFILENAME)
deep_template_name = self.extra_args.get(self.DCM_WORK_TEMPLATE)
deep_template = self.parent.get_template_by_name(deep_template_name)
choices = deep_template.keys["extension"].labelled_choices
sgtk_deep_ext = hou.MenuParmTemplate(
self.SGTK_DEEP_EXT,
"Extension (sgtk only)",
choices.keys(),
menu_labels=choices.values(),
script_callback=self.generate_callback_script_str("update_deep_paths"),
script_callback_language=hou.scriptLanguage.Python,
)
sgtk_deep_ext.setConditional(
hou.parmCondType.DisableWhen,
"{ use_sgtk != 1 } { vm_deepresolver == null }",
)
deep_folder.insert_template(index, sgtk_deep_ext)
# cryptomatte
cryptomatte_folder = images_folder.get("output6_3")
vm_cryptolayers = cryptomatte_folder.get(self.VM_CRYPTOLAYERS)
vm_cryptolayers_template = vm_cryptolayers.template
vm_cryptolayers_template.setScriptCallback(
self.generate_callback_script_str("lock_crypto_parms")
)
vm_cryptolayers_template.setScriptCallbackLanguage(hou.scriptLanguage.Python)
vm_cryptolayername = vm_cryptolayers.get(
self.VM_CRYPTOLAYERNAME_TMPL.format("#")
)
vm_cryptolayername_template = vm_cryptolayername.template
vm_cryptolayername_template.setConditional(
hou.parmCondType.HideWhen, "{ use_sgtk == 1 }"
)
vm_cryptolayeroutput = vm_cryptolayers.get(
self.VM_CRYPTOLAYEROUTPUT_TMPL.format("#")
)
vm_cryptolayeroutput_template = vm_cryptolayeroutput.template
vm_cryptolayeroutput_template.setDefaultValue((self.AOV_ERROR,))
vm_cryptolayersidecar = vm_cryptolayers.get(
self.VM_CRYPTOLAYERSIDECAR_TMPL.format("#")
)
vm_cryptolayersidecar_template = vm_cryptolayersidecar.template
vm_cryptolayersidecar_template.setDefaultValue((self.AOV_ERROR,))
index = vm_cryptolayers.index_of_template(
self.VM_CRYPTOLAYERNAME_TMPL.format("#")
)
sgtk_cryptolayername = hou.StringParmTemplate(
self.SGTK_CRYPTOLAYERNAME_TMPL.format("#"),
"Channel Name",
1,
default_value=("CryptoMaterial",),
script_callback=self.generate_callback_script_str(
"update_crypto_layer_path"
),
script_callback_language=hou.scriptLanguage.Python,
)
sgtk_cryptolayername.setConditional(
hou.parmCondType.HideWhen, "{ use_sgtk != 1 }"
)
vm_cryptolayers.insert_template(index, sgtk_cryptolayername)
[docs] def _refresh_file_path(self, node):
"""
Refresh the file paths generated by the node handler.
:param node: A :class:`hou.Node` instance.
"""
super(IfdNodeHandler, self)._refresh_file_path(node)
vm_cryptolayers = node.parm(self.VM_CRYPTOLAYERS)
count = vm_cryptolayers.eval() + 1
for index in range(1, count):
self._update_crypto_layer_path(node, index)
self._update_deep_paths(node)
#############################################################################################
# Deep Output
#############################################################################################
[docs] def _update_deep_paths(self, node):
"""
Update the output path for deep images.
:param node: A :class:`hou.Node` instance.
"""
sgtk_deep_ext = node.parm(self.SGTK_DEEP_EXT)
extension = sgtk_deep_ext.evalAsString()
additional_fields = {"extension": extension}
parm_names = (self.VM_DCMFILENAME, self.VM_DSMFILENAME)
template_names = (self.DCM_WORK_TEMPLATE, self.DSM_WORK_TEMPLATE)
for parm_name, template_name in zip(parm_names, template_names):
self.update_file_path(
node, parm_name, template_name, additional_fields=additional_fields
)
[docs] def update_deep_paths(self, kwargs):
"""
Callback to update the output path for deep images.
"""
node = kwargs["node"]
self._update_deep_paths(node)
#############################################################################################
# Cryptomatte
#############################################################################################
[docs] def _update_crypto_layer_path(self, node, index):
"""
Update cryptomatte later output paths.
:param node: A :class:`hou.Node` instance.
:param int index: The index of the aov parm.
:raises: :class:`FieldInputError` on invalid input.
"""
parm = node.parm(self.SGTK_CRYPTOLAYERNAME_TMPL.format(index))
vm_cryptolayername = node.parm(self.VM_CRYPTOLAYERNAME_TMPL.format(index))
vm_cryptolayeroutput = node.parm(self.VM_CRYPTOLAYEROUTPUT_TMPL.format(index))
vm_cryptolayersidecar = node.parm(self.VM_CRYPTOLAYERSIDECAR_TMPL.format(index))
try:
self._validate_parm(parm)
except Exception:
vm_cryptolayername.set("")
vm_cryptolayeroutput.set(self.AOV_ERROR)
vm_cryptolayersidecar.set(self.AOV_ERROR)
raise
aov = parm.evalAsString()
vm_cryptolayername.set(parm.unexpandedString())
channel_path = self.generate_aov_path(node, aov, self.AOV_WORK_TEMPLATE)
vm_cryptolayeroutput.lock(False)
vm_cryptolayeroutput.set(channel_path)
vm_cryptolayeroutput.lock(True)
sidecar_path = self.generate_aov_path(node, aov, self.MANIFEST_NAME_TEMPLATE)
vm_cryptolayersidecar.lock(False)
vm_cryptolayersidecar.set(sidecar_path)
vm_cryptolayersidecar.lock(True)
[docs] def update_crypto_layer_path(self, kwargs):
"""
Callback to update cryptomatte later output paths.
"""
node = kwargs["node"]
index = kwargs["script_multiparm_index"]
self._update_crypto_layer_path(node, index)
[docs] def _lock_crypto_parms(self, node, lock):
"""
Lock the cryptomatte output path parms.
:param node: A :class:`hou.Node` instance.
:param bool lock: Lock parms if True.
"""
vm_cryptolayers = node.parm(self.VM_CRYPTOLAYERS)
count = vm_cryptolayers.eval() + 1
for index in range(1, count):
vm_cryptolayeroutput = node.parm(
self.VM_CRYPTOLAYEROUTPUT_TMPL.format(index)
)
vm_cryptolayeroutput.lock(lock)
vm_cryptolayersidecar = node.parm(
self.VM_CRYPTOLAYERSIDECAR_TMPL.format(index)
)
vm_cryptolayersidecar.lock(lock)
[docs] def lock_crypto_parms(self, kwargs):
"""
Callback to lock the cryptomatte output path parms.
:param node: A :class:`hou.Node` instance.
:param bool lock: Lock parms if True.
"""
node = kwargs["node"]
self._lock_crypto_parms(node, True)
#############################################################################################
# Utilities
#############################################################################################
[docs] def _remove_sgtk_items_from_parm_group(self, parameter_group):
"""
Remove all sgtk parameters from the node's parameter template group.
:param ParmGroup parameter_group: The parameter group containing sgtk parameters.
"""
index = parameter_group.index_of_template(self.SGTK_FOLDER)
parameter_group.pop_template(index)
images_folder = parameter_group.get("images6")
image_planes_folder = images_folder.get("output6_1")
vm_numaux = image_planes_folder.get(self.AOV_COUNT)
vm_numaux_template = vm_numaux.template
vm_numaux_template.setScriptCallback("")
index = vm_numaux.index_of_template(self.SGTK_AOV_NAME_TMPL.format("#"))
vm_numaux.pop_template(index)
vm_filename_plane = vm_numaux.get(self.AOV_FILE_TMPL.format("#"))
vm_filename_plane_template = vm_filename_plane.template
vm_filename_plane_template.setDefaultValue(("",))
deep_folder = images_folder.get("output6_2")
index = deep_folder.index_of_template(self.SGTK_DEEP_EXT)
deep_folder.pop_template(index)
cryptomatte_folder = images_folder.get("output6_3")
vm_cryptolayers = cryptomatte_folder.get(self.VM_CRYPTOLAYERS)
vm_cryptolayers_template = vm_cryptolayers.template
vm_cryptolayers_template.setScriptCallback("")
index = vm_cryptolayers.index_of_template(
self.SGTK_CRYPTOLAYERNAME_TMPL.format("#")
)
vm_cryptolayers.pop_template(index)
vm_cryptolayeroutput = vm_cryptolayers.get(
self.VM_CRYPTOLAYEROUTPUT_TMPL.format("#")
)
vm_cryptolayeroutput_template = vm_cryptolayeroutput.template
vm_cryptolayeroutput_template.setDefaultValue(("$HIP/CryptoMaterial.exr",))
vm_cryptolayersidecar = vm_cryptolayers.get(
self.VM_CRYPTOLAYERSIDECAR_TMPL.format("#")
)
vm_cryptolayersidecar_template = vm_cryptolayersidecar.template
vm_cryptolayersidecar_template.setDefaultValue(("CryptoMaterial.json",))
[docs] def _populate_from_fields(self, node, fields):
"""
Populate the node from template fields.
:param node: A :class:`hou.Node` instance.
:param dict fields: The template fields.
"""
super(IfdNodeHandler, self)._populate_from_fields(node, fields)
self._populate_aov_names(
node,
self.VM_CRYPTOLAYERS,
self.VM_CRYPTOLAYERNAME_TMPL,
self.SGTK_CRYPTOLAYERNAME_TMPL,
)
[docs] def _restore_sgtk_parms(self, node):
"""
Restore any removed sgtk parameters onto the given node.
:param node: A :class:`hou.Node` instance containing sgtk parameters.
"""
vm_dcmfilename = node.parm(self.VM_DCMFILENAME)
dcm_file_path = vm_dcmfilename.evalAsString() if vm_dcmfilename else None
super(IfdNodeHandler, self)._restore_sgtk_parms(node)
dcm_template = self._get_template(self.DCM_WORK_TEMPLATE)
if dcm_file_path is not None:
dcm_fields = dcm_template.validate_and_get_fields(dcm_file_path)
if dcm_fields:
sgtk_deep_extension = node.parm(self.SGTK_DEEP_EXT)
entries = sgtk_deep_extension.menuItems()
ext = dcm_fields.get("extension", "rat")
index = entries.index(ext)
sgtk_deep_extension.set(index)
[docs] def _get_output_paths_and_templates(self, node):
"""
Go through the node's specified parameters and get the output paths,
work and publish templates.
Returns a list of dictionaries, each containing, at least:
- work template
- publish template
- file name
and optionally:
- any sequence paths
- whether the output is a deep image
:param node: A :class:`hou.Node` instance.
:rtype: list(dict)
"""
paths_and_templates = super(
IfdNodeHandler, self
)._get_output_paths_and_templates(node)
# get cryptomatte
self._get_multi_parm_output_paths_and_templates(
node,
self.VM_CRYPTOLAYERS,
self.VM_CRYPTOLAYEROUTPUTENABLE_TMPL,
self.VM_CRYPTOLAYEROUTPUT_TMPL,
self.AOV_WORK_TEMPLATE,
self.AOV_PUBLISH_TEMPLATE,
paths_and_templates,
)
# get deep outputs
deep_resolver = node.parm("vm_deepresolver")
result = deep_resolver.evalAsString()
if result != "null":
func_parms = {
"camera": (
node,
self.VM_DCMFILENAME,
self._get_template(self.DCM_WORK_TEMPLATE),
self._get_template(self.DCM_PUBLISH_TEMPLATE),
paths_and_templates,
True,
),
"shadow": (
node,
self.VM_DSMFILENAME,
self._get_template(self.DSM_WORK_TEMPLATE),
self._get_template(self.DSM_PUBLISH_TEMPLATE),
paths_and_templates,
True,
),
}
self._get_output_path_and_templates_for_parm(*func_parms[result])
# get ifd
if node.parm(self.ARCHIVE_ENABLED).eval():
self._get_output_path_and_templates_for_parm(
node,
self.ARCHIVE_OUTPUT,
self._get_template(self.ARCHIVE_WORK_TEMPLATE),
self._get_template(self.ARCHIVE_PUBLISH_TEMPLATE),
paths_and_templates,
)
return paths_and_templates