mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
Merge pull request #3839 from BigRoy/houdini_opengl
This commit is contained in:
commit
75b183c917
13 changed files with 349 additions and 11 deletions
|
|
@ -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")
|
||||
|
|
|
|||
125
openpype/hosts/houdini/plugins/create/create_review.py
Normal file
125
openpype/hosts/houdini/plugins/create/create_review.py
Normal 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)
|
||||
]
|
||||
|
|
@ -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):
|
||||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
58
openpype/hosts/houdini/plugins/publish/extract_opengl.py
Normal file
58
openpype/hosts/houdini/plugins/publish/extract_opengl.py
Normal 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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
|||
"nuke",
|
||||
"maya",
|
||||
"blender",
|
||||
"houdini",
|
||||
"shell",
|
||||
"hiero",
|
||||
"premiere",
|
||||
|
|
|
|||
|
|
@ -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]}",
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||

|
||||
|
||||
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**.
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
|
|
|||
BIN
website/docs/assets/houdini_review_create_attrs.png
Normal file
BIN
website/docs/assets/houdini_review_create_attrs.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
|
|
@ -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}"`)
|
||||
|
|
|
|||
|
|
@ -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}"`)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue