Skip to content

Commit

Permalink
Merge pull request #163 from BigRoy/enhancement/147-ay-7047_publishin…
Browse files Browse the repository at this point in the history
…g-component-builder-usd-while-maintaining-hierarchical-structure

Publish component builder LOP usd while maintaining hierarchical structure
  • Loading branch information
antirotor authored Dec 11, 2024
2 parents 0d64612 + c68ee57 commit 072ffcb
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 2 deletions.
70 changes: 70 additions & 0 deletions client/ayon_houdini/plugins/create/create_usd_componentbuilder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import inspect

from ayon_houdini.api import plugin
from ayon_core.pipeline import CreatedInstance, CreatorError

import hou


class CreateUSDComponentBuilder(plugin.HoudiniCreator):
identifier = "io.ayon.creators.houdini.componentbuilder"
label = "USD Component Builder LOPs"
product_type = "usd"
icon = "cubes"
description = "Create USD from Component Builder LOPs"

def get_detail_description(self):
return inspect.cleandoc("""
Creates a USD publish from a Component Output LOP that is part of
a solaris component builder network.
The created USD will contain the component builder LOPs and all its
dependencies inside the single product.
To use it, select a Component Output LOP and click "Create" for
this creator. It will generate an instance for each selected
Component Output LOP.
""")

def create(self, product_name, instance_data, pre_create_data):
nodes = hou.selectedNodes()
builders = [
node for node in nodes
if node.type().nameWithCategory() == "Lop/componentoutput"
]
for builder in builders:
self.create_for_instance_node(product_name, instance_data, builder)

def create_for_instance_node(
self, product_name, instance_data, instance_node):

try:
self.customize_node_look(instance_node)
instance_data["instance_node"] = instance_node.path()
instance_data["instance_id"] = instance_node.path()
instance_data["families"] = self.get_publish_families()
instance = CreatedInstance(
self.product_type,
product_name,
instance_data,
self)
self._add_instance_to_context(instance)
self.imprint(instance_node, instance.data_to_store())
except hou.Error as er:
raise CreatorError("Creator error: {}".format(er)) from er

# Lock any parameters in this list
to_lock = [
# Lock some AYON attributes
"productType",
"id",
]
self.lock_parameters(instance_node, to_lock)

def get_network_categories(self):
# Do not expose via tab menu because currently it does not create any
# node, but only 'imprints' on an existing node.
return []

def get_publish_families(self):
return ["usd", "componentbuilder"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import os
from typing import List, Tuple
from pathlib import Path

import pyblish.api

from ayon_core.pipeline import AYONPyblishPluginMixin, PublishError
from ayon_houdini.api import plugin

import hou
from pxr import Sdf, UsdUtils


def compute_all_dependencies(
filepath: str) -> Tuple[list[Sdf.Layer], list[str], list[str]]:
"""Compute all dependencies for the given USD file."""
# Only separated here for better type hints on returned values
return UsdUtils.ComputeAllDependencies(filepath)


class CollectComponentBuilderLOPs(plugin.HoudiniInstancePlugin,
AYONPyblishPluginMixin):

# Run after `CollectResourcesPath`
order = pyblish.api.CollectorOrder + 0.496
families = ["componentbuilder"]
label = "Collect Componentbuilder LOPs"

def process(self, instance):

node = hou.node(instance.data["instance_node"])

# Render the component builder LOPs
# TODO: Do we want this? or use existing frames? Usually a Collector
# should not 'extract' but in this case we need the resulting USD
# file.
node.cook(force=True) # required to clear existing errors
node.parm("execute").pressButton()

errors = node.errors()
if errors:
for error in errors:
self.log.error(error)
raise PublishError(f"Failed to save to disk '{node.path()}'")

# Define the main asset usd file
filepath = node.evalParm("lopoutput")
representations = instance.data.setdefault("representations", [])
representations.append({
"name": "usd",
"ext": "usd",
"files": os.path.basename(filepath),
"stagingDir": os.path.dirname(filepath),
})

# Get all its files and dependencies
# TODO: Ignore any files that are not 'relative' to the USD file
layers, assets, unresolved_paths = compute_all_dependencies(filepath)
paths: List[str] = []
paths.extend(layer.realPath for layer in layers)
paths.extend(assets)

# Skip unresolved paths, but warn about them
for unresolved in unresolved_paths:
self.log.warning(f"Cannot be resolved: {unresolved}")

self.log.debug(f"Collecting USD: {filepath}")
src_root_dir = os.path.dirname(filepath)

# Used to compare resolved paths against
filepath = Path(filepath)

# We keep the relative paths to the USD file
transfers = instance.data.setdefault("transfers", [])
publish_root = instance.data["publishDir"]
for src in paths:

if filepath == Path(src):
continue

relative_path = os.path.relpath(src, start=src_root_dir)
self.log.debug(f"Collected dependency: {relative_path}")
dest = os.path.normpath(os.path.join(publish_root, relative_path))
transfers.append((src, dest))
2 changes: 0 additions & 2 deletions client/ayon_houdini/plugins/publish/collect_output_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ class CollectOutputSOPPath(plugin.HoudiniInstancePlugin):
"camera",
"vdbcache",
"imagesequence",
"usd",
"usdrender",
"redshiftproxy",
"staticMesh",
"model",
Expand Down

0 comments on commit 072ffcb

Please sign in to comment.