Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Houdini: Camera Loader fix mismatch for Maya cameras #5584

47 changes: 47 additions & 0 deletions openpype/hosts/houdini/api/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -649,3 +649,50 @@ def get_color_management_preferences():
"display": hou.Color.ocio_defaultDisplay(),
"view": hou.Color.ocio_defaultView()
}


def get_resolution_from_doc(doc):
"""Get resolution from the given asset document. """

if not doc or "data" not in doc:
print("Entered document is not valid. \"{}\"".format(str(doc)))
return None

resolution_width = doc["data"].get("resolutionWidth")
resolution_height = doc["data"].get("resolutionHeight")

# Make sure both width and height are set
if resolution_width is None or resolution_height is None:
print("No resolution information found for \"{}\"".format(doc["name"]))
return None

return int(resolution_width), int(resolution_height)


def set_camera_resolution(camera, asset_doc=None):
"""Apply resolution to camera from asset document of the publish"""

if not asset_doc:
asset_doc = get_current_project_asset()

resolution = get_resolution_from_doc(asset_doc)

if resolution:
print("Setting camera resolution: {} -> {}x{}".format(
camera.name(), resolution[0], resolution[1]
))
camera.parm("resx").set(resolution[0])
camera.parm("resy").set(resolution[1])


def get_camera_from_container(container):
"""Get camera from container node. """

cameras = container.recursiveGlob(
"*",
filter=hou.nodeTypeFilter.ObjCamera,
include_subnets=False
)

assert len(cameras) == 1, "Camera instance must have only one camera"
return cameras[0]
2 changes: 2 additions & 0 deletions openpype/hosts/houdini/api/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from openpype.pipeline import (
register_creator_plugin_path,
register_loader_plugin_path,
register_inventory_action_path,
AVALON_CONTAINER_ID,
)
from openpype.pipeline.load import any_outdated_containers
Expand Down Expand Up @@ -55,6 +56,7 @@ def install(self):
pyblish.api.register_plugin_path(PUBLISH_PATH)
register_loader_plugin_path(LOAD_PATH)
register_creator_plugin_path(CREATE_PATH)
register_inventory_action_path(INVENTORY_PATH)

log.info("Installing callbacks ... ")
# register_event_callback("init", on_init)
Expand Down
26 changes: 26 additions & 0 deletions openpype/hosts/houdini/plugins/inventory/set_camera_resolution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from openpype.pipeline import InventoryAction
from openpype.hosts.houdini.api.lib import (
get_camera_from_container,
set_camera_resolution
)
from openpype.pipeline.context_tools import get_current_project_asset


class SetCameraResolution(InventoryAction):

label = "Set Camera Resolution"
icon = "desktop"
color = "orange"

@staticmethod
def is_compatible(container):
return (
container.get("loader") == "CameraLoader"
)

def process(self, containers):
asset_doc = get_current_project_asset()
for container in containers:
node = container["node"]
camera = get_camera_from_container(node)
set_camera_resolution(camera, asset_doc)
81 changes: 47 additions & 34 deletions openpype/hosts/houdini/plugins/load/load_camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
)
from openpype.hosts.houdini.api import pipeline

from openpype.hosts.houdini.api.lib import (
set_camera_resolution,
get_camera_from_container
)

import hou


ARCHIVE_EXPRESSION = ('__import__("_alembic_hom_extensions")'
'.alembicGetCameraDict')
Expand All @@ -25,7 +32,15 @@ def transfer_non_default_values(src, dest, ignore=None):
channel expression and ignore certain Parm types.

"""
import hou

ignore_types = {
hou.parmTemplateType.Toggle,
hou.parmTemplateType.Menu,
hou.parmTemplateType.Button,
hou.parmTemplateType.FolderSet,
hou.parmTemplateType.Separator,
hou.parmTemplateType.Label,
}

src.updateParmStates()

Expand Down Expand Up @@ -62,14 +77,6 @@ def transfer_non_default_values(src, dest, ignore=None):
continue

# Ignore folders, separators, etc.
ignore_types = {
hou.parmTemplateType.Toggle,
hou.parmTemplateType.Menu,
hou.parmTemplateType.Button,
hou.parmTemplateType.FolderSet,
hou.parmTemplateType.Separator,
hou.parmTemplateType.Label,
}
if parm.parmTemplate().type() in ignore_types:
continue

Expand All @@ -90,13 +97,8 @@ class CameraLoader(load.LoaderPlugin):

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

import os
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("\\", "/")
file_path = self.filepath_from_context(context).replace("\\", "/")

# Get the root node
obj = hou.node("/obj")
Expand All @@ -106,19 +108,21 @@ def load(self, context, name=None, namespace=None, data=None):
node_name = "{}_{}".format(namespace, name) if namespace else name

# Create a archive node
container = self.create_and_connect(obj, "alembicarchive", node_name)
node = self.create_and_connect(obj, "alembicarchive", node_name)

# TODO: add FPS of project / asset
container.setParms({"fileName": file_path,
"channelRef": True})
node.setParms({"fileName": file_path, "channelRef": True})

# Apply some magic
container.parm("buildHierarchy").pressButton()
container.moveToGoodPosition()
node.parm("buildHierarchy").pressButton()
node.moveToGoodPosition()

# Create an alembic xform node
nodes = [container]
nodes = [node]

camera = get_camera_from_container(node)
self._match_maya_render_mask(camera)
set_camera_resolution(camera, asset_doc=context["asset"])
self[:] = nodes

return pipeline.containerise(node_name,
Expand All @@ -143,37 +147,31 @@ def update(self, container, representation):
# Store the cam temporarily next to the Alembic Archive
# so that we can preserve parm values the user set on it
# after build hierarchy was triggered.
old_camera = self._get_camera(node)
old_camera = get_camera_from_container(node)
temp_camera = old_camera.copyTo(node.parent())

# Rebuild
node.parm("buildHierarchy").pressButton()

# Apply values to the new camera
new_camera = self._get_camera(node)
new_camera = get_camera_from_container(node)
transfer_non_default_values(temp_camera,
new_camera,
# The hidden uniform scale attribute
# gets a default connection to
# "icon_scale" just skip that completely
ignore={"scale"})

self._match_maya_render_mask(new_camera)
set_camera_resolution(new_camera)

temp_camera.destroy()

def remove(self, container):

node = container["node"]
node.destroy()

def _get_camera(self, node):
import hou
cameras = node.recursiveGlob("*",
filter=hou.nodeTypeFilter.ObjCamera,
include_subnets=False)

assert len(cameras) == 1, "Camera instance must have only one camera"
return cameras[0]

def create_and_connect(self, node, node_type, name=None):
"""Create a node within a node which and connect it to the input

Expand All @@ -194,5 +192,20 @@ def create_and_connect(self, node, node_type, name=None):
new_node.moveToGoodPosition()
return new_node

def switch(self, container, representation):
self.update(container, representation)
def _match_maya_render_mask(self, camera):
"""Workaround to match Maya render mask in Houdini"""

# print("Setting match maya render mask ")
parm = camera.parm("aperture")
expression = parm.expression()
expression = expression.replace("return ", "aperture = ")
expression += """
# Match maya render mask (logic from Houdini's own FBX importer)
node = hou.pwd()
resx = node.evalParm('resx')
resy = node.evalParm('resy')
aspect = node.evalParm('aspect')
aperture *= min(1, (resx / resy * aspect) / 1.5)
return aperture
"""
parm.setExpression(expression, language=hou.exprLanguage.Python)