diff --git a/openpype/hosts/houdini/plugins/create/convert_legacy.py b/openpype/hosts/houdini/plugins/create/convert_legacy.py index e549c9dc26b..86103e33693 100644 --- a/openpype/hosts/houdini/plugins/create/convert_legacy.py +++ b/openpype/hosts/houdini/plugins/create/convert_legacy.py @@ -69,6 +69,8 @@ def convert(self): "creator_identifier": self.family_to_id[family], "instance_node": subset.path() } + if family == "pointcache": + data["families"] = ["abc"] self.log.info("Converting {} to {}".format( subset.path(), self.family_to_id[family])) imprint(subset, data) diff --git a/openpype/hosts/houdini/plugins/create/create_bgeo.py b/openpype/hosts/houdini/plugins/create/create_bgeo.py new file mode 100644 index 00000000000..a1101fd0452 --- /dev/null +++ b/openpype/hosts/houdini/plugins/create/create_bgeo.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +"""Creator plugin for creating pointcache bgeo files.""" +from openpype.hosts.houdini.api import plugin +from openpype.pipeline import CreatedInstance, CreatorError +from openpype.lib import EnumDef + + +class CreateBGEO(plugin.HoudiniCreator): + """BGEO pointcache creator.""" + identifier = "io.openpype.creators.houdini.bgeo" + label = "BGEO PointCache" + family = "pointcache" + icon = "gears" + + def create(self, subset_name, instance_data, pre_create_data): + import hou + + instance_data.pop("active", None) + + instance_data.update({"node_type": "geometry"}) + + instance = super(CreateBGEO, self).create( + subset_name, + instance_data, + pre_create_data) # type: CreatedInstance + + instance_node = hou.node(instance.get("instance_node")) + + file_path = "{}{}".format( + hou.text.expandString("$HIP/pyblish/"), + "{}.$F4.{}".format( + subset_name, + pre_create_data.get("bgeo_type") or "bgeo.sc") + ) + parms = { + "sopoutput": file_path + } + + instance_node.parm("trange").set(1) + if self.selected_nodes: + # if selection is on SOP level, use it + if isinstance(self.selected_nodes[0], hou.SopNode): + parms["soppath"] = self.selected_nodes[0].path() + else: + # try to find output node with the lowest index + outputs = [ + child for child in self.selected_nodes[0].children() + if child.type().name() == "output" + ] + if not outputs: + instance_node.setParms(parms) + raise CreatorError(( + "Missing output node in SOP level for the selection. " + "Please select correct SOP path in created instance." + )) + outputs.sort(key=lambda output: output.evalParm("outputidx")) + parms["soppath"] = outputs[0].path() + + instance_node.setParms(parms) + + def get_pre_create_attr_defs(self): + attrs = super().get_pre_create_attr_defs() + bgeo_enum = [ + { + "value": "bgeo", + "label": "uncompressed bgeo (.bgeo)" + }, + { + "value": "bgeosc", + "label": "BLOSC compressed bgeo (.bgeosc)" + }, + { + "value": "bgeo.sc", + "label": "BLOSC compressed bgeo (.bgeo.sc)" + }, + { + "value": "bgeo.gz", + "label": "GZ compressed bgeo (.bgeo.gz)" + }, + { + "value": "bgeo.lzma", + "label": "LZMA compressed bgeo (.bgeo.lzma)" + }, + { + "value": "bgeo.bz2", + "label": "BZip2 compressed bgeo (.bgeo.bz2)" + } + ] + + return attrs + [ + EnumDef("bgeo_type", bgeo_enum, label="BGEO Options"), + ] diff --git a/openpype/hosts/houdini/plugins/publish/collect_frames.py b/openpype/hosts/houdini/plugins/publish/collect_frames.py index 91a3d9d170e..01df809d4c7 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_frames.py +++ b/openpype/hosts/houdini/plugins/publish/collect_frames.py @@ -13,7 +13,8 @@ class CollectFrames(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.01 label = "Collect Frames" - families = ["vdbcache", "imagesequence", "ass", "redshiftproxy", "review"] + families = ["vdbcache", "imagesequence", "ass", + "redshiftproxy", "review", "bgeo"] def process(self, instance): @@ -32,9 +33,9 @@ def process(self, instance): output = output_parm.eval() _, ext = lib.splitext( - output, - allowed_multidot_extensions=[".ass.gz"] - ) + output, allowed_multidot_extensions=[ + ".ass.gz", ".bgeo.sc", ".bgeo.gz", + ".bgeo.lzma", ".bgeo.bz2"]) file_name = os.path.basename(output) result = file_name @@ -76,7 +77,7 @@ def create_file_list(match, start_frame, end_frame): frame = match.group(1) padding = len(frame) - # Get the parts of the filename surrounding the frame number + # Get the parts of the filename surrounding the frame number, # so we can put our own frame numbers in. span = match.span(1) prefix = match.string[: span[0]] diff --git a/openpype/hosts/houdini/plugins/publish/collect_pointcache_type.py b/openpype/hosts/houdini/plugins/publish/collect_pointcache_type.py new file mode 100644 index 00000000000..6c527377e0a --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/collect_pointcache_type.py @@ -0,0 +1,21 @@ +"""Collector for pointcache types. + +This will add additional family to pointcache instance based on +the creator_identifier parameter. +""" +import pyblish.api + + +class CollectPointcacheType(pyblish.api.InstancePlugin): + """Collect data type for pointcache instance.""" + + order = pyblish.api.CollectorOrder + hosts = ["houdini"] + families = ["pointcache"] + label = "Collect type of pointcache" + + def process(self, instance): + if instance.data["creator_identifier"] == "io.openpype.creators.houdini.bgeo": # noqa: E501 + instance.data["families"] += ["bgeo"] + elif instance.data["creator_identifier"] == "io.openpype.creators.houdini.alembic": # noqa: E501 + instance.data["families"] += ["abc"] diff --git a/openpype/hosts/houdini/plugins/publish/extract_alembic.py b/openpype/hosts/houdini/plugins/publish/extract_alembic.py index cb2d4ef424a..bdd19b23d4d 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_alembic.py +++ b/openpype/hosts/houdini/plugins/publish/extract_alembic.py @@ -13,7 +13,7 @@ class ExtractAlembic(publish.Extractor): order = pyblish.api.ExtractorOrder label = "Extract Alembic" hosts = ["houdini"] - families = ["pointcache", "camera"] + families = ["abc", "camera"] def process(self, instance): diff --git a/openpype/hosts/houdini/plugins/publish/extract_bgeo.py b/openpype/hosts/houdini/plugins/publish/extract_bgeo.py new file mode 100644 index 00000000000..c9625ec8803 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/extract_bgeo.py @@ -0,0 +1,53 @@ +import os + +import pyblish.api + +from openpype.pipeline import publish +from openpype.hosts.houdini.api.lib import render_rop +from openpype.hosts.houdini.api import lib + +import hou + + +class ExtractBGEO(publish.Extractor): + + order = pyblish.api.ExtractorOrder + label = "Extract BGEO" + hosts = ["houdini"] + families = ["bgeo"] + + def process(self, instance): + + ropnode = hou.node(instance.data["instance_node"]) + + # Get the filename from the filename parameter + output = ropnode.evalParm("sopoutput") + staging_dir, file_name = os.path.split(output) + instance.data["stagingDir"] = staging_dir + + # We run the render + self.log.info("Writing bgeo files '{}' to '{}'.".format( + file_name, staging_dir)) + + # write files + render_rop(ropnode) + + output = instance.data["frames"] + + _, ext = lib.splitext( + output[0], allowed_multidot_extensions=[ + ".ass.gz", ".bgeo.sc", ".bgeo.gz", + ".bgeo.lzma", ".bgeo.bz2"]) + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + "name": "bgeo", + "ext": ext.lstrip("."), + "files": output, + "stagingDir": staging_dir, + "frameStart": instance.data["frameStart"], + "frameEnd": instance.data["frameEnd"] + } + instance.data["representations"].append(representation) diff --git a/openpype/hosts/houdini/plugins/publish/validate_abc_primitive_to_detail.py b/openpype/hosts/houdini/plugins/publish/validate_abc_primitive_to_detail.py index bef8db45a47..af9e0804662 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_abc_primitive_to_detail.py +++ b/openpype/hosts/houdini/plugins/publish/validate_abc_primitive_to_detail.py @@ -17,7 +17,7 @@ class ValidateAbcPrimitiveToDetail(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder + 0.1 - families = ["pointcache"] + families = ["abc"] hosts = ["houdini"] label = "Validate Primitive to Detail (Abc)" diff --git a/openpype/hosts/houdini/plugins/publish/validate_alembic_face_sets.py b/openpype/hosts/houdini/plugins/publish/validate_alembic_face_sets.py index 44d58cfa363..40114bc40e2 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_alembic_face_sets.py +++ b/openpype/hosts/houdini/plugins/publish/validate_alembic_face_sets.py @@ -18,7 +18,7 @@ class ValidateAlembicROPFaceSets(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder + 0.1 - families = ["pointcache"] + families = ["abc"] hosts = ["houdini"] label = "Validate Alembic ROP Face Sets" diff --git a/openpype/hosts/houdini/plugins/publish/validate_alembic_input_node.py b/openpype/hosts/houdini/plugins/publish/validate_alembic_input_node.py index b0cf4cdc58e..47c47e4ea26 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_alembic_input_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_alembic_input_node.py @@ -14,7 +14,7 @@ class ValidateAlembicInputNode(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder + 0.1 - families = ["pointcache"] + families = ["abc"] hosts = ["houdini"] label = "Validate Input Node (Abc)" diff --git a/openpype/hosts/houdini/plugins/publish/validate_bgeo_file_sop_path.py b/openpype/hosts/houdini/plugins/publish/validate_bgeo_file_sop_path.py new file mode 100644 index 00000000000..22746aabb03 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/validate_bgeo_file_sop_path.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +"""Validator plugin for SOP Path in bgeo isntance.""" +import pyblish.api +from openpype.pipeline import PublishValidationError + + +class ValidateNoSOPPath(pyblish.api.InstancePlugin): + """Validate if SOP Path in BGEO instance exists.""" + + order = pyblish.api.ValidatorOrder + families = ["bgeo"] + label = "Validate BGEO SOP Path" + + def process(self, instance): + + import hou + + node = hou.node(instance.data.get("instance_node")) + sop_path = node.evalParm("soppath") + if not sop_path: + raise PublishValidationError( + ("Empty SOP Path ('soppath' parameter) found in " + f"the BGEO instance Geometry - {node.path()}")) + if not isinstance(hou.node(sop_path), hou.SopNode): + raise PublishValidationError( + "SOP path is not pointing to valid SOP node.") diff --git a/openpype/hosts/houdini/plugins/publish/validate_file_extension.py b/openpype/hosts/houdini/plugins/publish/validate_file_extension.py index 4584e78f4fe..6594d108516 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_file_extension.py +++ b/openpype/hosts/houdini/plugins/publish/validate_file_extension.py @@ -19,12 +19,11 @@ class ValidateFileExtension(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder - families = ["pointcache", "camera", "vdbcache"] + families = ["camera", "vdbcache"] hosts = ["houdini"] label = "Output File Extension" family_extensions = { - "pointcache": ".abc", "camera": ".abc", "vdbcache": ".vdb", } diff --git a/openpype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py b/openpype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py index 0d84aa7db81..ca06617ab00 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py +++ b/openpype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py @@ -24,7 +24,7 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): """ order = ValidateContentsOrder + 0.1 - families = ["pointcache"] + families = ["abc"] hosts = ["houdini"] label = "Validate Prims Hierarchy Path" actions = [AddDefaultPathAction] diff --git a/website/docs/artist_hosts_houdini.md b/website/docs/artist_hosts_houdini.md index 04717653653..940d5ac3514 100644 --- a/website/docs/artist_hosts_houdini.md +++ b/website/docs/artist_hosts_houdini.md @@ -132,3 +132,25 @@ switch versions between different hda types. When you load hda, it will install its type in your hip file and add published version as its definition file. When you switch version via Scene Manager, it will add its definition and set it as preferred. + +## Publishing and loading BGEO caches + +There is a simple support for publishing and loading **BGEO** files in all supported compression variants. + +### Creating BGEO instances + +Select your SOP node to be exported as BGEO. If your selection is in the object level, OpenPype will try to find if there is an `output` node inside, the one with the lowest index will be used: + +![BGEO output node](assets/houdini_bgeo_output_node.png) + +Then you can open Publisher, in Create you select **BGEO PointCache**: + +![BGEO Publisher](assets/houdini_bgeo-publisher.png) + +You can select compression type and if the current selection should be connected to ROPs SOP path parameter. Publishing will produce sequence of files based on your timeline settings. + +### Loading BGEO + +Select your published BGEO subsets in Loader, right click and load them in: + +![BGEO Publisher](assets/houdini_bgeo-loading.png) diff --git a/website/docs/assets/houdini_bgeo-loading.png b/website/docs/assets/houdini_bgeo-loading.png new file mode 100644 index 00000000000..e8aad66f439 Binary files /dev/null and b/website/docs/assets/houdini_bgeo-loading.png differ diff --git a/website/docs/assets/houdini_bgeo-publisher.png b/website/docs/assets/houdini_bgeo-publisher.png new file mode 100644 index 00000000000..5c3534077f3 Binary files /dev/null and b/website/docs/assets/houdini_bgeo-publisher.png differ diff --git a/website/docs/assets/houdini_bgeo_output_node.png b/website/docs/assets/houdini_bgeo_output_node.png new file mode 100644 index 00000000000..160f0a259b9 Binary files /dev/null and b/website/docs/assets/houdini_bgeo_output_node.png differ