From b3bdf12fffffd65507e9a66c83e7382cb66dba9c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 28 Jan 2022 17:39:09 +0100 Subject: [PATCH 1/4] Implement Arnold .ass standin extraction from Houdini (also support .ass.gz) --- openpype/hosts/houdini/api/lib.py | 7 ++- .../plugins/create/create_arnold_ass.py | 51 +++++++++++++++++++ .../houdini/plugins/publish/collect_frames.py | 14 ++++- .../houdini/plugins/publish/extract_ass.py | 47 +++++++++++++++++ 4 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 openpype/hosts/houdini/plugins/create/create_arnold_ass.py create mode 100644 openpype/hosts/houdini/plugins/publish/extract_ass.py diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 53f0e59ea9..cfb276628a 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -180,8 +180,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..4c87212a98 --- /dev/null +++ b/openpype/hosts/houdini/plugins/create/create_arnold_ass.py @@ -0,0 +1,51 @@ +import hou + +from avalon import houdini + + +class CreateArnoldAss(houdini.Creator): + """Arnold .ass Archive""" + + label = "Arnold ASS" + family = "ass" + icon = "magic" + defaults = ["Main"] + + 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) + + parms = { + # Render frame range + "trange": 1, + + # Arnold ROP settings + "ar_ass_file": '$HIP/pyblish/`chs("subset")`.$F4.ass.gz', + "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 8d21794c1b..9abbd97826 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_frames.py +++ b/openpype/hosts/houdini/plugins/publish/collect_frames.py @@ -5,12 +5,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): @@ -19,7 +28,8 @@ class CollectFrames(pyblish.api.InstancePlugin): output_parm = lib.get_output_parameter(ropnode) 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..c7c5ed6430 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/extract_ass.py @@ -0,0 +1,47 @@ +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): + + import hou + + 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) + + 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": instance.data["frames"], + "stagingDir": staging_dir, + "frameStart": instance.data["frameStart"], + "frameEnd": instance.data["frameEnd"], + } + instance.data["representations"].append(representation) From 6f19012ae2f3215d35cfa79692868fbc242d8f35 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 28 Jan 2022 17:51:29 +0100 Subject: [PATCH 2/4] Remove unused imports of 'hou' --- openpype/hosts/houdini/plugins/create/create_arnold_ass.py | 2 -- openpype/hosts/houdini/plugins/publish/extract_ass.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_arnold_ass.py b/openpype/hosts/houdini/plugins/create/create_arnold_ass.py index 4c87212a98..a87136c442 100644 --- a/openpype/hosts/houdini/plugins/create/create_arnold_ass.py +++ b/openpype/hosts/houdini/plugins/create/create_arnold_ass.py @@ -1,5 +1,3 @@ -import hou - from avalon import houdini diff --git a/openpype/hosts/houdini/plugins/publish/extract_ass.py b/openpype/hosts/houdini/plugins/publish/extract_ass.py index c7c5ed6430..239d0ebec3 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_ass.py +++ b/openpype/hosts/houdini/plugins/publish/extract_ass.py @@ -14,8 +14,6 @@ class ExtractAss(openpype.api.Extractor): def process(self, instance): - import hou - ropnode = instance[0] # Get the filename from the filename parameter From dd92fe1e4ee9a195b85d7aa6b4234fba2829dec4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 1 Feb 2022 21:38:33 +0100 Subject: [PATCH 3/4] Use .ass extension as default (and add settings to choose for .ass.gz) --- .../plugins/create/create_arnold_ass.py | 6 +++- .../defaults/project_settings/houdini.json | 5 +++ .../schemas/schema_houdini_create.json | 35 +++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/create/create_arnold_ass.py b/openpype/hosts/houdini/plugins/create/create_arnold_ass.py index a87136c442..2af9a71cdd 100644 --- a/openpype/hosts/houdini/plugins/create/create_arnold_ass.py +++ b/openpype/hosts/houdini/plugins/create/create_arnold_ass.py @@ -9,6 +9,9 @@ class CreateArnoldAss(houdini.Creator): icon = "magic" defaults = ["Main"] + # Default extension: `.ass` or `.ass.gz` + ext = ".ass" + def __init__(self, *args, **kwargs): super(CreateArnoldAss, self).__init__(*args, **kwargs) @@ -29,12 +32,13 @@ class CreateArnoldAss(houdini.Creator): 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": '$HIP/pyblish/`chs("subset")`.$F4.ass.gz', + "ar_ass_file": filepath, "ar_ass_export_enable": 1 } node.setParms(parms) 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", From 3804980a4f975f222415d59a67e485fcb668263d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 1 Feb 2022 22:28:38 +0100 Subject: [PATCH 4/4] Correctly fail extraction if user "interrupts" the extraction --- .../hosts/houdini/plugins/publish/extract_ass.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/extract_ass.py b/openpype/hosts/houdini/plugins/publish/extract_ass.py index 239d0ebec3..e56e40df85 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_ass.py +++ b/openpype/hosts/houdini/plugins/publish/extract_ass.py @@ -28,6 +28,16 @@ class ExtractAss(openpype.api.Extractor): 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"] = [] @@ -37,7 +47,7 @@ class ExtractAss(openpype.api.Extractor): representation = { 'name': 'ass', 'ext': ext, - "files": instance.data["frames"], + "files": files, "stagingDir": staging_dir, "frameStart": instance.data["frameStart"], "frameEnd": instance.data["frameEnd"],