Merge pull request #3839 from BigRoy/houdini_opengl

This commit is contained in:
Ondřej Samohel 2023-04-12 15:15:40 +02:00 committed by GitHub
commit 75b183c917
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 349 additions and 11 deletions

View file

@ -127,6 +127,8 @@ def get_output_parameter(node):
return node.parm("filename")
elif node_type == "comp":
return node.parm("copoutput")
elif node_type == "opengl":
return node.parm("picture")
elif node_type == "arnold":
if node.evalParm("ar_ass_export_enable"):
return node.parm("ar_ass_file")

View file

@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating openGL reviews."""
from openpype.hosts.houdini.api import plugin
from openpype.lib import EnumDef, BoolDef, NumberDef
class CreateReview(plugin.HoudiniCreator):
"""Review with OpenGL ROP"""
identifier = "io.openpype.creators.houdini.review"
label = "Review"
family = "review"
icon = "video-camera"
def create(self, subset_name, instance_data, pre_create_data):
import hou
instance_data.pop("active", None)
instance_data.update({"node_type": "opengl"})
instance_data["imageFormat"] = pre_create_data.get("imageFormat")
instance_data["keepImages"] = pre_create_data.get("keepImages")
instance = super(CreateReview, self).create(
subset_name,
instance_data,
pre_create_data)
instance_node = hou.node(instance.get("instance_node"))
frame_range = hou.playbar.frameRange()
filepath = "{root}/{subset}/{subset}.$F4.{ext}".format(
root=hou.text.expandString("$HIP/pyblish"),
subset="`chs(\"subset\")`", # keep dynamic link to subset
ext=pre_create_data.get("image_format") or "png"
)
parms = {
"picture": filepath,
"trange": 1,
# Unlike many other ROP nodes the opengl node does not default
# to expression of $FSTART and $FEND so we preserve that behavior
# but do set the range to the frame range of the playbar
"f1": frame_range[0],
"f2": frame_range[1],
}
override_resolution = pre_create_data.get("override_resolution")
if override_resolution:
parms.update({
"tres": override_resolution,
"res1": pre_create_data.get("resx"),
"res2": pre_create_data.get("resy"),
"aspect": pre_create_data.get("aspect"),
})
if self.selected_nodes:
# The first camera found in selection we will use as camera
# Other node types we set in force objects
camera = None
force_objects = []
for node in self.selected_nodes:
path = node.path()
if node.type().name() == "cam":
if camera:
continue
camera = path
else:
force_objects.append(path)
if not camera:
self.log.warning("No camera found in selection.")
parms.update({
"camera": camera or "",
"scenepath": "/obj",
"forceobjects": " ".join(force_objects),
"vobjects": "" # clear candidate objects from '*' value
})
instance_node.setParms(parms)
to_lock = ["id", "family"]
self.lock_parameters(instance_node, to_lock)
def get_pre_create_attr_defs(self):
attrs = super(CreateReview, self).get_pre_create_attr_defs()
image_format_enum = [
"bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png",
"rad", "rat", "rta", "sgi", "tga", "tif",
]
return attrs + [
BoolDef("keepImages",
label="Keep Image Sequences",
default=False),
EnumDef("imageFormat",
image_format_enum,
default="png",
label="Image Format Options"),
BoolDef("override_resolution",
label="Override resolution",
tooltip="When disabled the resolution set on the camera "
"is used instead.",
default=True),
NumberDef("resx",
label="Resolution Width",
default=1280,
minimum=2,
decimals=0),
NumberDef("resy",
label="Resolution Height",
default=720,
minimum=2,
decimals=0),
NumberDef("aspect",
label="Aspect Ratio",
default=1.0,
minimum=0.0001,
decimals=3)
]

View file

@ -14,7 +14,7 @@ class CollectFrames(pyblish.api.InstancePlugin):
order = pyblish.api.CollectorOrder
label = "Collect Frames"
families = ["vdbcache", "imagesequence", "ass", "redshiftproxy"]
families = ["vdbcache", "imagesequence", "ass", "redshiftproxy", "review"]
def process(self, instance):

View file

@ -0,0 +1,52 @@
import hou
import pyblish.api
class CollectHoudiniReviewData(pyblish.api.InstancePlugin):
"""Collect Review Data."""
label = "Collect Review Data"
order = pyblish.api.CollectorOrder + 0.1
hosts = ["houdini"]
families = ["review"]
def process(self, instance):
# This fixes the burnin having the incorrect start/end timestamps
# because without this it would take it from the context instead
# which isn't the actual frame range that this instance renders.
instance.data["handleStart"] = 0
instance.data["handleEnd"] = 0
# Get the camera from the rop node to collect the focal length
ropnode_path = instance.data["instance_node"]
ropnode = hou.node(ropnode_path)
camera_path = ropnode.parm("camera").eval()
camera_node = hou.node(camera_path)
if not camera_node:
raise RuntimeError("No valid camera node found on review node: "
"{}".format(camera_path))
# Collect focal length.
focal_length_parm = camera_node.parm("focal")
if not focal_length_parm:
self.log.warning("No 'focal' (focal length) parameter found on "
"camera: {}".format(camera_path))
return
if focal_length_parm.isTimeDependent():
start = instance.data["frameStart"]
end = instance.data["frameEnd"] + 1
focal_length = [
focal_length_parm.evalAsFloatAtFrame(t)
for t in range(int(start), int(end))
]
else:
focal_length = focal_length_parm.evalAsFloat()
# Store focal length in `burninDataMembers`
burnin_members = instance.data.setdefault("burninDataMembers", {})
burnin_members["focalLength"] = focal_length
instance.data.setdefault("families", []).append('ftrack')

View file

@ -0,0 +1,58 @@
import os
import pyblish.api
from openpype.pipeline import (
publish,
OptionalPyblishPluginMixin
)
from openpype.hosts.houdini.api.lib import render_rop
import hou
class ExtractOpenGL(publish.Extractor,
OptionalPyblishPluginMixin):
order = pyblish.api.ExtractorOrder - 0.01
label = "Extract OpenGL"
families = ["review"]
hosts = ["houdini"]
optional = True
def process(self, instance):
if not self.is_active(instance.data):
return
ropnode = hou.node(instance.data.get("instance_node"))
output = ropnode.evalParm("picture")
staging_dir = os.path.normpath(os.path.dirname(output))
instance.data["stagingDir"] = staging_dir
file_name = os.path.basename(output)
self.log.info("Extracting '%s' to '%s'" % (file_name,
staging_dir))
render_rop(ropnode)
output = instance.data["frames"]
tags = ["review"]
if not instance.data.get("keepImages"):
tags.append("delete")
representation = {
"name": instance.data["imageFormat"],
"ext": instance.data["imageFormat"],
"files": output,
"stagingDir": staging_dir,
"frameStart": instance.data["frameStart"],
"frameEnd": instance.data["frameEnd"],
"tags": tags,
"preview": True,
"camera_name": instance.data.get("review_camera")
}
if "representations" not in instance.data:
instance.data["representations"] = []
instance.data["representations"].append(representation)

View file

@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
import pyblish.api
from openpype.pipeline import PublishValidationError
import hou
class ValidateSceneReview(pyblish.api.InstancePlugin):
"""Validator Some Scene Settings before publishing the review
1. Scene Path
2. Resolution
"""
order = pyblish.api.ValidatorOrder
families = ["review"]
hosts = ["houdini"]
label = "Scene Setting for review"
def process(self, instance):
invalid = self.get_invalid_scene_path(instance)
report = []
if invalid:
report.append(
"Scene path does not exist: '%s'" % invalid[0],
)
invalid = self.get_invalid_resolution(instance)
if invalid:
report.extend(invalid)
if report:
raise PublishValidationError(
"\n\n".join(report),
title=self.label)
def get_invalid_scene_path(self, instance):
node = hou.node(instance.data.get("instance_node"))
scene_path_parm = node.parm("scenepath")
scene_path_node = scene_path_parm.evalAsNode()
if not scene_path_node:
return [scene_path_parm.evalAsString()]
def get_invalid_resolution(self, instance):
node = hou.node(instance.data.get("instance_node"))
# The resolution setting is only used when Override Camera Resolution
# is enabled. So we skip validation if it is disabled.
override = node.parm("tres").eval()
if not override:
return
invalid = []
res_width = node.parm("res1").eval()
res_height = node.parm("res2").eval()
if res_width == 0:
invalid.append("Override Resolution width is set to zero.")
if res_height == 0:
invalid.append("Override Resolution height is set to zero")
return invalid

View file

@ -34,6 +34,25 @@ class ExtractBurnin(publish.Extractor):
order = pyblish.api.ExtractorOrder + 0.03
families = ["review", "burnin"]
hosts = [
"nuke",
"maya",
"shell",
"hiero",
"premiere",
"traypublisher",
"standalonepublisher",
"harmony",
"fusion",
"aftereffects",
"tvpaint",
"webpublisher",
"aftereffects",
"photoshop",
"flame",
"houdini"
# "resolve"
]
optional = True

View file

@ -44,6 +44,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
"nuke",
"maya",
"blender",
"houdini",
"shell",
"hiero",
"premiere",

View file

@ -253,13 +253,14 @@
{
"families": [],
"hosts": [
"maya"
"maya",
"houdini"
],
"task_types": [],
"task_names": [],
"subsets": [],
"burnins": {
"maya_burnin": {
"focal_length_burnin": {
"TOP_LEFT": "{yy}-{mm}-{dd}",
"TOP_CENTERED": "{focalLength:.2f} mm",
"TOP_RIGHT": "{anatomy[version]}",

View file

@ -14,20 +14,29 @@ sidebar_label: Houdini
- [Library Loader](artist_tools_library-loader)
## Publishing Alembic Cameras
You can publish baked camera in Alembic format. Select your camera and go **OpenPype -> Create** and select **Camera (abc)**.
You can publish baked camera in Alembic format.
Select your camera and go **OpenPype -> Create** and select **Camera (abc)**.
This will create Alembic ROP in **out** with path and frame range already set. This node will have a name you've
assigned in the **Creator** menu. For example if you name the subset `Default`, output Alembic Driver will be named
`cameraDefault`. After that, you can **OpenPype -> Publish** and after some validations your camera will be published
to `abc` file.
## Publishing Composites - Image Sequences
You can publish image sequence directly from Houdini. You can use any `cop` network you have and publish image
sequence generated from it. For example I've created simple **cop** graph to generate some noise:
You can publish image sequences directly from Houdini's image COP networks.
You can use any COP node and publish the image sequence generated from it. For example this simple graph to generate some noise:
![Noise COP](assets/houdini_imagesequence_cop.png)
If I want to publish it, I'll select node I like - in this case `radialblur1` and go **OpenPype -> Create** and
select **Composite (Image Sequence)**. This will create `/out/imagesequenceNoise` Composite ROP (I've named my subset
*Noise*) with frame range set. When you hit **Publish** it will render image sequence from selected node.
To publish the output of the `radialblur1` go to **OpenPype -> Create** and
select **Composite (Image Sequence)**. If you name the variant *Noise* this will create the `/out/imagesequenceNoise` Composite ROP with the frame range set.
When you hit **Publish** it will render image sequence from selected node.
:::info Use selection
With *Use selection* is enabled on create the node you have selected when creating will be the node used for published. (It set the Composite ROP node's COP path to it). If you don't do this you'll have to manually set the path as needed on e.g. `/out/imagesequenceNoise` to ensure it outputs what you want.
:::
## Publishing Point Caches (alembic)
Publishing point caches in alembic format is pretty straightforward, but it is by default enforcing better compatibility
@ -46,6 +55,16 @@ you handle `path` attribute is up to you, this is just an example.*
Now select the `output0` node and go **OpenPype -> Create** and select **Point Cache**. It will create
Alembic ROP `/out/pointcacheStrange`
## Publishing Reviews (OpenGL)
To generate a review output from Houdini you need to create a **review** instance.
Go to **OpenPype -> Create** and select **Review**.
![Houdini Create Review](assets/houdini_review_create_attrs.png)
On create, with the **Use Selection** checkbox enabled it will set up the first
camera found in your selection as the camera for the OpenGL ROP node and other
non-cameras are set in **Force Objects**. It will then render those even if
their display flag is disabled in your scene.
## Redshift
:::note Work in progress

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View file

@ -255,7 +255,7 @@ suffix is **"client"** then the final suffix is **"h264_client"**.
| resolution_height | Resolution height. |
| fps | Fps of an output. |
| timecode | Timecode by frame start and fps. |
| focalLength | **Only available in Maya**<br /><br />Camera focal length per frame. Use syntax `{focalLength:.2f}` for decimal truncating. Eg. `35.234985` with `{focalLength:.2f}` would produce `35.23`, whereas `{focalLength:.0f}` would produce `35`. |
| focalLength | **Only available in Maya and Houdini**<br /><br />Camera focal length per frame. Use syntax `{focalLength:.2f}` for decimal truncating. Eg. `35.234985` with `{focalLength:.2f}` would produce `35.23`, whereas `{focalLength:.0f}` would produce `35`. |
:::warning
`timecode` is a specific key that can be **only at the end of content**. (`"BOTTOM_RIGHT": "TC: {timecode}"`)

View file

@ -304,7 +304,7 @@ If source representation has suffix **"h264"** and burnin suffix is **"client"**
| resolution_height | Resolution height. |
| fps | Fps of an output. |
| timecode | Timecode by frame start and fps. |
| focalLength | **Only available in Maya**<br /><br />Camera focal length per frame. Use syntax `{focalLength:.2f}` for decimal truncating. Eg. `35.234985` with `{focalLength:.2f}` would produce `35.23`, whereas `{focalLength:.0f}` would produce `35`. |
| focalLength | **Only available in Maya and Houdini**<br /><br />Camera focal length per frame. Use syntax `{focalLength:.2f}` for decimal truncating. Eg. `35.234985` with `{focalLength:.2f}` would produce `35.23`, whereas `{focalLength:.0f}` would produce `35`. |
:::warning
`timecode` is specific key that can be **only at the end of content**. (`"BOTTOM_RIGHT": "TC: {timecode}"`)