Skip to content

Commit

Permalink
Merge branch 'develop' into feature/AY-6061_Render-Setup-Workflow-alt…
Browse files Browse the repository at this point in the history
…ernative-for-Houdini
  • Loading branch information
MustafaJafar authored Sep 2, 2024
2 parents fea866b + 9eec28e commit 7fd74c3
Show file tree
Hide file tree
Showing 23 changed files with 312 additions and 172 deletions.
68 changes: 32 additions & 36 deletions client/ayon_houdini/api/colorspace.py
Original file line number Diff line number Diff line change
@@ -1,59 +1,55 @@
from typing import List

import attr
import hou
from ayon_houdini.api.lib import get_color_management_preferences
from ayon_core.pipeline.colorspace import get_display_view_colorspace_name
from ayon_core.pipeline.colorspace import (
get_display_view_colorspace_name,
get_ocio_config_colorspaces
)


@attr.s
class LayerMetadata(object):
"""Data class for Render Layer metadata."""
frameStart = attr.ib()
frameEnd = attr.ib()
products: "List[RenderProduct]" = attr.ib()


@attr.s
class RenderProduct(object):
"""Getting Colorspace as
Specific Render Product Parameter for submitting
publish job.
"""
"""Specific Render Product Parameter for submitting."""
colorspace = attr.ib() # colorspace
view = attr.ib()
productName = attr.ib(default=None)


class ARenderProduct(object):
"""This is the minimal data structure required to get
`ayon_core.pipeline.farm.pyblish_functions.create_instances_for_aov` to
work with deadline addon's job submissions."""
# TODO: The exact data structure should actually be defined in core for all
# addons to align.
def __init__(self, aov_names: List[str]):
colorspace = get_scene_linear_colorspace()
products = [
RenderProduct(colorspace=colorspace, productName=aov_name)
for aov_name in aov_names
]
self.layer_data = LayerMetadata(products=products)

def __init__(self):
"""Constructor."""
# Initialize
self.layer_data = self._get_layer_data()
self.layer_data.products = self.get_colorspace_data()

def _get_layer_data(self):
return LayerMetadata(
frameStart=int(hou.playbar.frameRange()[0]),
frameEnd=int(hou.playbar.frameRange()[1]),
)

def get_colorspace_data(self):
"""To be implemented by renderer class.

This should return a list of RenderProducts.
def get_scene_linear_colorspace():
"""Return colorspace name for Houdini's OCIO config scene linear role.
Returns:
list: List of RenderProduct
By default, renderers in Houdini render output images in the scene linear
role colorspace.
"""
data = get_color_management_preferences()
colorspace_data = [
RenderProduct(
colorspace=data["display"],
view=data["view"],
productName=""
)
]
return colorspace_data
Returns:
Optional[str]: The colorspace name for the 'scene_linear' role in
the OCIO config Houdini is currently set to.
"""
ocio_config_path = hou.Color.ocio_configPath()
colorspaces = get_ocio_config_colorspaces(ocio_config_path)
return colorspaces["roles"].get("scene_linear", {}).get("colorspace")


def get_default_display_view_colorspace():
Expand Down
26 changes: 19 additions & 7 deletions client/ayon_houdini/api/hda_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from ayon_core.style import load_stylesheet

from ayon_houdini.api import lib
from .usd import get_ayon_entity_uri_from_representation_context

from qtpy import QtCore, QtWidgets, QtGui
import hou
Expand Down Expand Up @@ -179,11 +180,19 @@ def set_representation(node, representation_id: str):

context = get_representation_context(project_name, repre_entity)
update_info(node, context)
path = get_representation_path_from_context(context)
# Load fails on UNC paths with backslashes and also
# fails to resolve @sourcename var with backslashed
# paths correctly. So we force forward slashes
path = path.replace("\\", "/")

if node.parm("use_ayon_entity_uri"):
use_ayon_entity_uri = node.evalParm("use_ayon_entity_uri")
else:
use_ayon_entity_uri = False
if use_ayon_entity_uri:
path = get_ayon_entity_uri_from_representation_context(context)
else:
path = get_representation_path_from_context(context)
# Load fails on UNC paths with backslashes and also
# fails to resolve @sourcename var with backslashed
# paths correctly. So we force forward slashes
path = path.replace("\\", "/")
with _unlocked_parm(file_parm):
file_parm.set(path)

Expand Down Expand Up @@ -255,14 +264,17 @@ def on_representation_id_changed(node):
set_representation(node, repre_id)


def on_representation_parms_changed(node):
def on_representation_parms_changed(node, force=False):
"""
Usually used as callback to the project, folder, product, version and
representation parms which on change - would result in a different
representation id to be resolved.
Args:
node (hou.Node): Node to update.
force (Optional[bool]): Whether to force the callback to retrigger
even if the representation id already matches. For example, when
needing to resolve the filepath in a different way.
"""
project_name = node.evalParm("project_name") or get_current_project_name()
representation_id = get_representation_id(
Expand All @@ -278,7 +290,7 @@ def on_representation_parms_changed(node):
else:
representation_id = str(representation_id)

if node.evalParm("representation") != representation_id:
if force or node.evalParm("representation") != representation_id:
node.parm("representation").set(representation_id)
node.parm("representation").pressButton() # trigger callback

Expand Down
31 changes: 30 additions & 1 deletion client/ayon_houdini/api/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -580,12 +580,41 @@ def replace(match):

def get_color_management_preferences():
"""Get default OCIO preferences"""
return {

preferences = {
"config": hou.Color.ocio_configPath(),
"display": hou.Color.ocio_defaultDisplay(),
"view": hou.Color.ocio_defaultView()
}

# Note: For whatever reason they are cases where `view` may be an empty
# string even though a valid default display is set where `PyOpenColorIO`
# does correctly return the values.
# Workaround to get the correct default view
if preferences["config"] and not preferences["view"]:
log.debug(
"Houdini `hou.Color.ocio_defaultView()` returned empty value."
" Falling back to `PyOpenColorIO` to get the default view.")
try:
import PyOpenColorIO
except ImportError:
log.warning(
"Unable to workaround empty return value of "
"`hou.Color.ocio_defaultView()` because `PyOpenColorIO` is "
"not available.")
return preferences

config_path = preferences["config"]
config = PyOpenColorIO.Config.CreateFromFile(config_path)
display = config.getDefaultDisplay()
assert display == preferences["display"], \
"Houdini default OCIO display must match config default display"
view = config.getDefaultView(display)
preferences["display"] = display
preferences["view"] = view

return preferences


def get_obj_node_output(obj_node):
"""Find output node.
Expand Down
8 changes: 8 additions & 0 deletions client/ayon_houdini/api/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from ayon_core.lib import BoolDef

from .lib import imprint, read, lsattr, add_self_publish_button
from .usd import get_ayon_entity_uri_from_representation_context


SETTINGS_CATEGORY = "houdini"
Expand Down Expand Up @@ -316,6 +317,13 @@ class HoudiniLoader(load.LoaderPlugin):

hosts = ["houdini"]
settings_category = SETTINGS_CATEGORY
use_ayon_entity_uri = False

def filepath_from_context(cls, context):
if cls.use_ayon_entity_uri:
return get_ayon_entity_uri_from_representation_context(context)

return super(HoudiniLoader, cls).filepath_from_context(context)


class HoudiniInstancePlugin(pyblish.api.InstancePlugin):
Expand Down
40 changes: 40 additions & 0 deletions client/ayon_houdini/api/usd.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import List

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

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -377,3 +378,42 @@ def get_schema_type_names(type_name: str) -> List[str]:
results.append(schema_type_name)

return results


def get_ayon_entity_uri_from_representation_context(context: dict) -> str:
"""Resolve AYON Entity URI from representation context.
Note:
The representation context is the `get_representation_context` dict
containing the `project`, `folder, `representation` and so forth.
It is not the representation entity `context` key.
Arguments:
context (dict): The representation context.
Raises:
RuntimeError: Unable to resolve to a single valid URI.
Returns:
str: The AYON entity URI.
"""
project_name = context["project"]["name"]
representation_id = context["representation"]["id"]
response = ayon_api.post(
f"projects/{project_name}/uris",
entityType="representation",
ids=[representation_id])
if response.status_code != 200:
raise RuntimeError(
f"Unable to resolve AYON entity URI for '{project_name}' "
f"representation id '{representation_id}': {response.text}"
)
uris = response.data["uris"]
if len(uris) != 1:
raise RuntimeError(
f"Unable to resolve AYON entity URI for '{project_name}' "
f"representation id '{representation_id}' to single URI. "
f"Received data: {response.data}"
)
return uris[0]["uri"]
11 changes: 4 additions & 7 deletions client/ayon_houdini/plugins/load/load_usd_layer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from ayon_core.pipeline import (
get_representation_path,
AVALON_CONTAINER_ID,
)
from ayon_houdini.api import (
Expand All @@ -22,14 +21,13 @@ class USDSublayerLoader(plugin.HoudiniLoader):
icon = "code-fork"
color = "orange"

def load(self, context, name=None, namespace=None, data=None):
use_ayon_entity_uri = False

import os
def load(self, context, name=None, namespace=None, data=None):
import hou

# Format file name, Houdini only wants forward slashes
file_path = self.filepath_from_context(context)
file_path = os.path.normpath(file_path)
file_path = file_path.replace("\\", "/")

# Get the root node
Expand Down Expand Up @@ -60,18 +58,17 @@ def load(self, context, name=None, namespace=None, data=None):
return container

def update(self, container, context):
repre_entity = context["representation"]
node = container["node"]

# Update the file path
file_path = get_representation_path(repre_entity)
file_path = self.filepath_from_context(context)
file_path = file_path.replace("\\", "/")

# Update attributes
node.setParms(
{
"filepath1": file_path,
"representation": repre_entity["id"],
"representation": context["representation"]["id"],
}
)

Expand Down
11 changes: 4 additions & 7 deletions client/ayon_houdini/plugins/load/load_usd_reference.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from ayon_core.pipeline import (
get_representation_path,
AVALON_CONTAINER_ID,
)
from ayon_houdini.api import (
Expand All @@ -22,14 +21,13 @@ class USDReferenceLoader(plugin.HoudiniLoader):
icon = "code-fork"
color = "orange"

def load(self, context, name=None, namespace=None, data=None):
use_ayon_entity_uri = False

import os
def load(self, context, name=None, namespace=None, data=None):
import hou

# Format file name, Houdini only wants forward slashes
file_path = self.filepath_from_context(context)
file_path = os.path.normpath(file_path)
file_path = file_path.replace("\\", "/")

# Get the root node
Expand Down Expand Up @@ -60,18 +58,17 @@ def load(self, context, name=None, namespace=None, data=None):
return container

def update(self, container, context):
repre_entity = context["representation"]
node = container["node"]

# Update the file path
file_path = get_representation_path(repre_entity)
file_path = self.filepath_from_context(context)
file_path = file_path.replace("\\", "/")

# Update attributes
node.setParms(
{
"filepath1": file_path,
"representation": repre_entity["id"],
"representation": context["representation"]["id"],
}
)

Expand Down
5 changes: 2 additions & 3 deletions client/ayon_houdini/plugins/load/load_usd_sop.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import os

from ayon_houdini.api import (
pipeline,
plugin
Expand All @@ -16,12 +14,13 @@ class SopUsdImportLoader(plugin.HoudiniLoader):
icon = "code-fork"
color = "orange"

use_ayon_entity_uri = False

def load(self, context, name=None, namespace=None, data=None):
import hou

# Format file name, Houdini only wants forward slashes
file_path = self.filepath_from_context(context)
file_path = os.path.normpath(file_path)
file_path = file_path.replace("\\", "/")

# Get the root node
Expand Down
Loading

0 comments on commit 7fd74c3

Please sign in to comment.