Skip to content

Commit

Permalink
fixes after rebase
Browse files Browse the repository at this point in the history
  • Loading branch information
iLLiCiTiT committed Jul 2, 2024
1 parent 67300fb commit 9560d47
Show file tree
Hide file tree
Showing 10 changed files with 380 additions and 38 deletions.
22 changes: 22 additions & 0 deletions client/ayon_houdini/api/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,28 @@ def maintained_selection():
node.setSelected(on=True)


@contextmanager
def parm_values(overrides):
"""Override Parameter values during the context.
Arguments:
overrides (List[Tuple[hou.Parm, Any]]): The overrides per parm
that should be applied during context.
"""

originals = []
try:
for parm, value in overrides:
originals.append((parm, parm.eval()))
parm.set(value)
yield
finally:
for parm, value in originals:
# Parameter might not exist anymore so first
# check whether it's still valid
if hou.parm(parm.path()):
parm.set(value)


def reset_framerange(fps=True, frame_range=True):
"""Set frame range and FPS to current folder."""

Expand Down
18 changes: 18 additions & 0 deletions client/ayon_houdini/api/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ def create(self, product_name, instance_data, pre_create_data):

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,
Expand Down Expand Up @@ -182,6 +183,7 @@ def collect_instances(self):
node_path = instance.path()
node_data["instance_id"] = node_path
node_data["instance_node"] = node_path
node_data["families"] = self.get_publish_families()
if "AYON_productName" in node_data:
node_data["productName"] = node_data.pop("AYON_productName")

Expand Down Expand Up @@ -211,6 +213,7 @@ def imprint(self, node, values, update=False):
values["AYON_productName"] = values.pop("productName")
values.pop("instance_node", None)
values.pop("instance_id", None)
values.pop("families", None)
imprint(node, values, update=update)

def remove_instances(self, instances):
Expand Down Expand Up @@ -252,6 +255,21 @@ def customize_node_look(
node.setUserData('nodeshape', shape)
node.setColor(color)

def get_publish_families(self):
"""Return families for the instances of this creator.
Allow a Creator to define multiple families so that a creator can
e.g. specify `usd` and `usdrop`.
There is no need to override this method if you only have the
primary family defined by the `product_type` property as that will
always be set.
Returns:
List[str]: families for instances of this creator
"""
return []

def get_network_categories(self):
"""Return in which network view type this creator should show.
Expand Down
186 changes: 182 additions & 4 deletions client/ayon_houdini/api/usd.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

import contextlib
import logging
import json
import itertools
from typing import List

from pxr import Sdf

import hou
from pxr import Usd, Sdf, Tf, Vt, UsdRender

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -119,11 +122,13 @@ def get_usd_rop_loppath(node):
return node.parm("loppath").evalAsNode()


def get_layer_save_path(layer):
def get_layer_save_path(layer, expand_string=True):
"""Get custom HoudiniLayerInfo->HoudiniSavePath from SdfLayer.
Args:
layer (pxr.Sdf.Layer): The Layer to retrieve the save pah data from.
expand_string (bool): Whether to expand any houdini vars in the save
path before computing the absolute path.
Returns:
str or None: Path to save to when data exists.
Expand All @@ -136,6 +141,8 @@ def get_layer_save_path(layer):
save_path = hou_layer_info.customData.get("HoudiniSavePath", None)
if save_path:
# Unfortunately this doesn't actually resolve the full absolute path
if expand_string:
save_path = hou.text.expandString(save_path)
return layer.ComputeAbsolutePath(save_path)


Expand Down Expand Up @@ -181,7 +188,18 @@ def iter_layer_recursive(layer):
yield layer


def get_configured_save_layers(usd_rop):
def get_configured_save_layers(usd_rop, strip_above_layer_break=True):
"""Retrieve the layer save paths from a USD ROP.
Arguments:
usdrop (hou.RopNode): USD Rop Node
strip_above_layer_break (Optional[bool]): Whether to exclude any
layers that are above layer breaks. This defaults to True.
Returns:
List[Sdf.Layer]: The layers with configured save paths.
"""

lop_node = get_usd_rop_loppath(usd_rop)
stage = lop_node.stage(apply_viewport_overrides=False)
Expand All @@ -192,10 +210,170 @@ def get_configured_save_layers(usd_rop):

root_layer = stage.GetRootLayer()

if strip_above_layer_break:
layers_above_layer_break = set(lop_node.layersAboveLayerBreak())
else:
layers_above_layer_break = set()

save_layers = []
for layer in iter_layer_recursive(root_layer):
if (
strip_above_layer_break and
layer.identifier in layers_above_layer_break
):
continue

save_path = get_layer_save_path(layer)
if save_path is not None:
save_layers.append(layer)

return save_layers


def setup_lop_python_layer(layer, node, savepath=None,
apply_file_format_args=True):
"""Set up Sdf.Layer with HoudiniLayerInfo prim for metadata.
This is the same as `loputils.createPythonLayer` but can be run on top
of `pxr.Sdf.Layer` instances that are already created in a Python LOP node.
That's useful if your layer creation itself is built to be DCC agnostic,
then we just need to run this after per layer to make it explicitly
stored for houdini.
By default, Houdini doesn't apply the FileFormatArguments supplied to
the created layer; however it does support USD's file save suffix
of `:SDF_FORMAT_ARGS:` to supply them. With `apply_file_format_args` any
file format args set on the layer's creation will be added to the
save path through that.
Note: The `node.addHeldLayer` call will only work from a LOP python node
whenever `node.editableStage()` or `node.editableLayer()` was called.
Arguments:
layer (Sdf.Layer): An existing layer (most likely just created
in the current runtime)
node (hou.LopNode): The Python LOP node to attach the layer to so
it does not get garbage collected/mangled after the downstream.
savepath (Optional[str]): When provided the HoudiniSaveControl
will be set to Explicit with HoudiniSavePath to this path.
apply_file_format_args (Optional[bool]): When enabled any
FileFormatArgs defined for the layer on creation will be set
in the HoudiniSavePath so Houdini USD ROP will use them top.
Returns:
Sdf.PrimSpec: The Created HoudiniLayerInfo prim spec.
"""
# Add a Houdini Layer Info prim where we can put the save path.
p = Sdf.CreatePrimInLayer(layer, '/HoudiniLayerInfo')
p.specifier = Sdf.SpecifierDef
p.typeName = 'HoudiniLayerInfo'
if savepath:
if apply_file_format_args:
args = layer.GetFileFormatArguments()
savepath = Sdf.Layer.CreateIdentifier(savepath, args)

p.customData['HoudiniSavePath'] = savepath
p.customData['HoudiniSaveControl'] = 'Explicit'
# Let everyone know what node created this layer.
p.customData['HoudiniCreatorNode'] = node.sessionId()
p.customData['HoudiniEditorNodes'] = Vt.IntArray([node.sessionId()])
node.addHeldLayer(layer.identifier)

return p


@contextlib.contextmanager
def remap_paths(rop_node, mapping):
"""Enable the AyonRemapPaths output processor with provided `mapping`"""
from ayon_houdini.api.lib import parm_values

if not mapping:
# Do nothing
yield
return

# Houdini string parms need to escape backslashes due to the support
# of expressions - as such we do so on the json data
value = json.dumps(mapping).replace("\\", "\\\\")
with outputprocessors(
rop_node,
processors=["ayon_remap_paths"],
disable_all_others=True,
):
with parm_values([
(rop_node.parm("ayon_remap_paths_remap_json"), value)
]):
yield


def get_usd_render_rop_rendersettings(rop_node, stage=None, logger=None):
"""Return the chosen UsdRender.Settings from the stage (if any).
Args:
rop_node (hou.Node): The Houdini USD Render ROP node.
stage (pxr.Usd.Stage): The USD stage to find the render settings
in. This is usually the stage from the LOP path the USD Render
ROP node refers to.
logger (logging.Logger): Logger to log warnings to if no render
settings were find in stage.
Returns:
Optional[UsdRender.Settings]: Render Settings.
"""
if logger is None:
logger = log

if stage is None:
lop_node = get_usd_rop_loppath(rop_node)
stage = lop_node.stage()

path = rop_node.evalParm("rendersettings")
if not path:
# Default behavior
path = "/Render/rendersettings"

prim = stage.GetPrimAtPath(path)
if not prim:
logger.warning("No render settings primitive found at: %s", path)
return

render_settings = UsdRender.Settings(prim)
if not render_settings:
logger.warning("Prim at %s is not a valid RenderSettings prim.", path)
return

return render_settings


def get_schema_type_names(type_name: str) -> List[str]:
"""Return schema type name for type name and its derived types
This can be useful for checking whether a `Sdf.PrimSpec`'s type name is of
a given type or any of its derived types.
Args:
type_name (str): The type name, like e.g. 'UsdGeomMesh'
Returns:
List[str]: List of schema type names and their derived types.
"""
schema_registry = Usd.SchemaRegistry
type_ = Tf.Type.FindByName(type_name)

if type_ == Tf.Type.Unknown:
type_ = schema_registry.GetTypeFromSchemaTypeName(type_name)
if type_ == Tf.Type.Unknown:
# Type not found
return []

results = []
derived = type_.GetAllDerivedTypes()
for derived_type in itertools.chain([type_], derived):
schema_type_name = schema_registry.GetSchemaTypeName(derived_type)
if schema_type_name:
results.append(schema_type_name)

return results
6 changes: 5 additions & 1 deletion client/ayon_houdini/plugins/create/create_usd.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
class CreateUSD(plugin.HoudiniCreator):
"""Universal Scene Description"""
identifier = "io.openpype.creators.houdini.usd"
label = "USD (experimental)"
label = "USD"
product_type = "usd"
icon = "cubes"
enabled = False
description = "Create USD"

def create(self, product_name, instance_data, pre_create_data):

Expand Down Expand Up @@ -49,3 +50,6 @@ def get_network_categories(self):
hou.ropNodeTypeCategory(),
hou.lopNodeTypeCategory()
]

def get_publish_families(self):
return ["usd", "usdrop"]
Loading

0 comments on commit 9560d47

Please sign in to comment.