diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index eaaba94ed5..57ced9b61b 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -182,8 +182,11 @@ def get_output_parameter(node): return node.parm("filename") elif node_type == "comp": return node.parm("copoutput") - else: - raise TypeError("Node type '%s' not supported" % node_type) + elif node_type == "arnold": + if node.evalParm("ar_ass_export_enable"): + return node.parm("ar_ass_file") + + raise TypeError("Node type '%s' not supported" % node_type) @contextmanager diff --git a/openpype/hosts/houdini/plugins/create/create_arnold_ass.py b/openpype/hosts/houdini/plugins/create/create_arnold_ass.py new file mode 100644 index 0000000000..2af9a71cdd --- /dev/null +++ b/openpype/hosts/houdini/plugins/create/create_arnold_ass.py @@ -0,0 +1,53 @@ +from avalon import houdini + + +class CreateArnoldAss(houdini.Creator): + """Arnold .ass Archive""" + + label = "Arnold ASS" + family = "ass" + icon = "magic" + defaults = ["Main"] + + # Default extension: `.ass` or `.ass.gz` + ext = ".ass" + + def __init__(self, *args, **kwargs): + super(CreateArnoldAss, self).__init__(*args, **kwargs) + + # Remove the active, we are checking the bypass flag of the nodes + self.data.pop("active", None) + + self.data.update({"node_type": "arnold"}) + + def process(self): + node = super(CreateArnoldAss, self).process() + + basename = node.name() + node.setName(basename + "_ASS", unique_name=True) + + # Hide Properties Tab on Arnold ROP since that's used + # for rendering instead of .ass Archive Export + parm_template_group = node.parmTemplateGroup() + parm_template_group.hideFolder("Properties", True) + node.setParmTemplateGroup(parm_template_group) + + filepath = '$HIP/pyblish/`chs("subset")`.$F4{}'.format(self.ext) + parms = { + # Render frame range + "trange": 1, + + # Arnold ROP settings + "ar_ass_file": filepath, + "ar_ass_export_enable": 1 + } + node.setParms(parms) + + # Lock the ASS export attribute + node.parm("ar_ass_export_enable").lock(True) + + # Lock some Avalon attributes + to_lock = ["family", "id"] + for name in to_lock: + parm = node.parm(name) + parm.lock(True) diff --git a/openpype/hosts/houdini/plugins/publish/collect_frames.py b/openpype/hosts/houdini/plugins/publish/collect_frames.py index 32a5d3b969..fac40b4d2b 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_frames.py +++ b/openpype/hosts/houdini/plugins/publish/collect_frames.py @@ -6,12 +6,21 @@ import pyblish.api from openpype.hosts.houdini.api import lib +def splitext(name, allowed_multidot_extensions): + + for ext in allowed_multidot_extensions: + if name.endswith(ext): + return name[:-len(ext)], ext + + return os.path.splitext(name) + + class CollectFrames(pyblish.api.InstancePlugin): """Collect all frames which would be saved from the ROP nodes""" order = pyblish.api.CollectorOrder label = "Collect Frames" - families = ["vdbcache", "imagesequence"] + families = ["vdbcache", "imagesequence", "ass"] def process(self, instance): @@ -29,7 +38,8 @@ class CollectFrames(pyblish.api.InstancePlugin): self.log.warning("Using current frame: {}".format(hou.frame())) output = output_parm.eval() - _, ext = os.path.splitext(output) + _, ext = splitext(output, + allowed_multidot_extensions=[".ass.gz"]) file_name = os.path.basename(output) result = file_name diff --git a/openpype/hosts/houdini/plugins/publish/extract_ass.py b/openpype/hosts/houdini/plugins/publish/extract_ass.py new file mode 100644 index 0000000000..e56e40df85 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/extract_ass.py @@ -0,0 +1,55 @@ +import os + +import pyblish.api +import openpype.api +from openpype.hosts.houdini.api.lib import render_rop + + +class ExtractAss(openpype.api.Extractor): + + order = pyblish.api.ExtractorOrder + 0.1 + label = "Extract Ass" + families = ["ass"] + hosts = ["houdini"] + + def process(self, instance): + + ropnode = instance[0] + + # Get the filename from the filename parameter + # `.evalParm(parameter)` will make sure all tokens are resolved + output = ropnode.evalParm("ar_ass_file") + staging_dir = os.path.dirname(output) + instance.data["stagingDir"] = staging_dir + file_name = os.path.basename(output) + + # We run the render + self.log.info("Writing ASS '%s' to '%s'" % (file_name, staging_dir)) + + render_rop(ropnode) + + # Unfortunately user interrupting the extraction does not raise an + # error and thus still continues to the integrator. To capture that + # we make sure all files exist + files = instance.data["frames"] + missing = [fname for fname in files + if not os.path.exists(os.path.join(staging_dir, fname))] + if missing: + raise RuntimeError("Failed to complete Arnold ass extraction. " + "Missing output files: {}".format(missing)) + + if "representations" not in instance.data: + instance.data["representations"] = [] + + # Allow ass.gz extension as well + ext = "ass.gz" if file_name.endswith(".ass.gz") else "ass" + + representation = { + 'name': 'ass', + 'ext': ext, + "files": files, + "stagingDir": staging_dir, + "frameStart": instance.data["frameStart"], + "frameEnd": instance.data["frameEnd"], + } + instance.data["representations"].append(representation) diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 809c732d6f..911bf82d9b 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -1,5 +1,10 @@ { "create": { + "CreateArnoldAss": { + "enabled": true, + "defaults": [], + "ext": ".ass" + }, "CreateAlembicCamera": { "enabled": true, "defaults": [] diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json index 72b8032d4b..83e0cf789a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json @@ -4,6 +4,41 @@ "key": "create", "label": "Creator plugins", "children": [ + { + "type": "dict", + "collapsible": true, + "key": "CreateArnoldAss", + "label": "Create Arnold Ass", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "defaults", + "label": "Default Subsets", + "object_type": "text" + }, + { + "type": "enum", + "key": "ext", + "label": "Default Output Format (extension)", + "multiselection": false, + "enum_items": [ + { + ".ass": ".ass" + }, + { + ".ass.gz": ".ass.gz (gzipped)" + } + ] + } + ] + + }, { "type": "schema_template", "name": "template_create_plugin",