mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-02 00:44:52 +01:00
add mantra and karma farm publishing for Houdini
This commit is contained in:
parent
89b87af509
commit
414cd0cce1
11 changed files with 666 additions and 110 deletions
114
openpype/hosts/houdini/plugins/create/create_karma_rop.py
Normal file
114
openpype/hosts/houdini/plugins/create/create_karma_rop.py
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Creator plugin to create Karma ROP."""
|
||||
from openpype.hosts.houdini.api import plugin
|
||||
from openpype.pipeline import CreatedInstance
|
||||
from openpype.lib import BoolDef, EnumDef, NumberDef
|
||||
|
||||
|
||||
class CreateKarmaROP(plugin.HoudiniCreator):
|
||||
"""Karma ROP"""
|
||||
identifier = "io.openpype.creators.houdini.karma_rop"
|
||||
label = "Karma ROP"
|
||||
family = "karma_rop"
|
||||
icon = "magic"
|
||||
defaults = ["master"]
|
||||
|
||||
def create(self, subset_name, instance_data, pre_create_data):
|
||||
import hou # noqa
|
||||
|
||||
instance_data.pop("active", None)
|
||||
instance_data.update({"node_type": "karma"})
|
||||
# Add chunk size attribute
|
||||
instance_data["chunkSize"] = 10
|
||||
|
||||
instance = super(CreateKarmaROP, self).create(
|
||||
subset_name,
|
||||
instance_data,
|
||||
pre_create_data) # type: CreatedInstance
|
||||
|
||||
instance_node = hou.node(instance.get("instance_node"))
|
||||
|
||||
ext = pre_create_data.get("image_format")
|
||||
|
||||
filepath = "{}{}".format(
|
||||
hou.text.expandString("$HIP/pyblish/"),
|
||||
"{}.$F4.{}".format(subset_name, ext)
|
||||
)
|
||||
checkpoint = "{}{}".format(
|
||||
hou.text.expandString("$HIP/pyblish/"),
|
||||
"{}.$F4.checkpoint".format(subset_name)
|
||||
)
|
||||
|
||||
usd_directory = "{}{}".format(
|
||||
hou.text.expandString("$HIP/pyblish/usd_renders/"),
|
||||
"{}_$RENDERID".format(subset_name)
|
||||
)
|
||||
|
||||
parms = {
|
||||
# Render Frame Range
|
||||
"trange": 1,
|
||||
# Karma ROP Setting
|
||||
"picture": filepath,
|
||||
# Karma Checkpoint Setting
|
||||
"productName": checkpoint,
|
||||
# USD Output Directory
|
||||
"savetodirectory": usd_directory,
|
||||
}
|
||||
|
||||
res_x = pre_create_data.get("res_x")
|
||||
res_y = pre_create_data.get("res_y")
|
||||
|
||||
if self.selected_nodes:
|
||||
# If camera found in selection
|
||||
# we will use as render camera
|
||||
camera = None
|
||||
for node in self.selected_nodes:
|
||||
if node.type().name() == "cam":
|
||||
camera = node.path()
|
||||
camera_node = hou.node(camera)
|
||||
has_camera = pre_create_data.get("cam_res")
|
||||
if has_camera:
|
||||
res_x = camera_node.evalParm("resx")
|
||||
res_y = camera_node.evalParm("resy")
|
||||
|
||||
if not camera:
|
||||
self.log.warning("No render camera found in selection")
|
||||
|
||||
|
||||
parms.update({
|
||||
"camera": camera or "",
|
||||
"resolutionx": res_x,
|
||||
"resolutiony": res_y,
|
||||
})
|
||||
|
||||
instance_node.setParms(parms)
|
||||
|
||||
# Lock some Avalon attributes
|
||||
to_lock = ["family", "id"]
|
||||
self.lock_parameters(instance_node, to_lock)
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
attrs = super(CreateKarmaROP, 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 + [
|
||||
EnumDef("image_format",
|
||||
image_format_enum,
|
||||
default="exr",
|
||||
label="Image Format Options"),
|
||||
NumberDef("res_x",
|
||||
label="width",
|
||||
default=1920,
|
||||
decimals=0),
|
||||
NumberDef("res_y",
|
||||
label="height",
|
||||
default=720,
|
||||
decimals=0),
|
||||
BoolDef("cam_res",
|
||||
label="Camera Resolution",
|
||||
default=False)
|
||||
]
|
||||
78
openpype/hosts/houdini/plugins/create/create_mantra_rop.py
Normal file
78
openpype/hosts/houdini/plugins/create/create_mantra_rop.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Creator plugin to create Mantra ROP."""
|
||||
from openpype.hosts.houdini.api import plugin
|
||||
from openpype.pipeline import CreatedInstance
|
||||
from openpype.lib import EnumDef
|
||||
|
||||
|
||||
class CreateMantraROP(plugin.HoudiniCreator):
|
||||
"""Mantra ROP"""
|
||||
identifier = "io.openpype.creators.houdini.mantra_rop"
|
||||
label = "Mantra ROP"
|
||||
family = "mantra_rop"
|
||||
icon = "magic"
|
||||
defaults = ["master"]
|
||||
|
||||
def create(self, subset_name, instance_data, pre_create_data):
|
||||
import hou # noqa
|
||||
|
||||
instance_data.pop("active", None)
|
||||
instance_data.update({"node_type": "ifd"})
|
||||
# Add chunk size attribute
|
||||
instance_data["chunkSize"] = 10
|
||||
|
||||
instance = super(CreateMantraROP, self).create(
|
||||
subset_name,
|
||||
instance_data,
|
||||
pre_create_data) # type: CreatedInstance
|
||||
|
||||
instance_node = hou.node(instance.get("instance_node"))
|
||||
|
||||
ext = pre_create_data.get("image_format")
|
||||
|
||||
filepath = "{}{}".format(
|
||||
hou.text.expandString("$HIP/pyblish/"),
|
||||
"{}.$F4.{}".format(subset_name, ext)
|
||||
)
|
||||
|
||||
parms = {
|
||||
# Render Frame Range
|
||||
"trange": 1,
|
||||
# Mantra ROP Setting
|
||||
"vm_picture": filepath,
|
||||
}
|
||||
|
||||
if self.selected_nodes:
|
||||
# If camera found in selection
|
||||
# we will use as render camera
|
||||
camera = None
|
||||
for node in self.selected_nodes:
|
||||
if node.type().name() == "cam":
|
||||
camera = node.path()
|
||||
|
||||
if not camera:
|
||||
self.log.warning("No render camera found in selection")
|
||||
|
||||
parms.update({"camera": camera or ""})
|
||||
|
||||
instance_node.setParms(parms)
|
||||
|
||||
# Lock some Avalon attributes
|
||||
to_lock = ["family", "id"]
|
||||
self.lock_parameters(instance_node, to_lock)
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
attrs = super(CreateMantraROP, 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 + [
|
||||
EnumDef("image_format",
|
||||
image_format_enum,
|
||||
default="exr",
|
||||
label="Image Format Options")
|
||||
]
|
||||
# Extract Import Plane parameters(Should be in the setting)
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import hou
|
||||
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class CollectInstanceNodeFrameRange(pyblish.api.InstancePlugin):
|
||||
"""Collect time range frame data for the instance node."""
|
||||
|
||||
order = pyblish.api.CollectorOrder + 0.001
|
||||
label = "Instance Node Frame Range"
|
||||
hosts = ["houdini"]
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
node_path = instance.data.get("instance_node")
|
||||
node = hou.node(node_path) if node_path else None
|
||||
if not node_path or not node:
|
||||
self.log.debug("No instance node found for instance: "
|
||||
"{}".format(instance))
|
||||
return
|
||||
|
||||
frame_data = self.get_frame_data(node)
|
||||
if not frame_data:
|
||||
return
|
||||
|
||||
self.log.info("Collected time data: {}".format(frame_data))
|
||||
instance.data.update(frame_data)
|
||||
|
||||
def get_frame_data(self, node):
|
||||
"""Get the frame data: start frame, end frame and steps
|
||||
Args:
|
||||
node(hou.Node)
|
||||
|
||||
Returns:
|
||||
dict
|
||||
|
||||
"""
|
||||
|
||||
data = {}
|
||||
|
||||
if node.parm("trange") is None:
|
||||
self.log.debug("Node has no 'trange' parameter: "
|
||||
"{}".format(node.path()))
|
||||
return data
|
||||
|
||||
if node.evalParm("trange") == 0:
|
||||
# Ignore 'render current frame'
|
||||
self.log.debug("Node '{}' has 'Render current frame' set. "
|
||||
"Time range data ignored.".format(node.path()))
|
||||
return data
|
||||
|
||||
data["frameStart"] = node.evalParm("f1")
|
||||
data["frameEnd"] = node.evalParm("f2")
|
||||
data["byFrameStep"] = node.evalParm("f3")
|
||||
|
||||
return data
|
||||
|
|
@ -116,6 +116,6 @@ class CollectInstances(pyblish.api.ContextPlugin):
|
|||
|
||||
data["frameStart"] = node.evalParm("f1")
|
||||
data["frameEnd"] = node.evalParm("f2")
|
||||
data["steps"] = node.evalParm("f3")
|
||||
data["byFrameStep"] = node.evalParm("f3")
|
||||
|
||||
return data
|
||||
|
|
|
|||
136
openpype/hosts/houdini/plugins/publish/collect_karma_rop.py
Normal file
136
openpype/hosts/houdini/plugins/publish/collect_karma_rop.py
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
import re
|
||||
import os
|
||||
|
||||
import hou
|
||||
import pyblish.api
|
||||
|
||||
|
||||
def get_top_referenced_parm(parm):
|
||||
|
||||
processed = set() # disallow infinite loop
|
||||
while True:
|
||||
if parm.path() in processed:
|
||||
raise RuntimeError("Parameter references result in cycle.")
|
||||
|
||||
processed.add(parm.path())
|
||||
|
||||
ref = parm.getReferencedParm()
|
||||
if ref.path() == parm.path():
|
||||
# It returns itself when it doesn't reference
|
||||
# another parameter
|
||||
return ref
|
||||
else:
|
||||
parm = ref
|
||||
|
||||
|
||||
def evalParmNoFrame(node, parm, pad_character="#"):
|
||||
|
||||
parameter = node.parm(parm)
|
||||
assert parameter, "Parameter does not exist: %s.%s" % (node, parm)
|
||||
|
||||
# If the parameter has a parameter reference, then get that
|
||||
# parameter instead as otherwise `unexpandedString()` fails.
|
||||
parameter = get_top_referenced_parm(parameter)
|
||||
|
||||
# Substitute out the frame numbering with padded characters
|
||||
try:
|
||||
raw = parameter.unexpandedString()
|
||||
except hou.Error as exc:
|
||||
print("Failed: %s" % parameter)
|
||||
raise RuntimeError(exc)
|
||||
|
||||
def replace(match):
|
||||
padding = 1
|
||||
n = match.group(2)
|
||||
if n and int(n):
|
||||
padding = int(n)
|
||||
return pad_character * padding
|
||||
|
||||
expression = re.sub(r"(\$F([0-9]*))", replace, raw)
|
||||
|
||||
with hou.ScriptEvalContext(parameter):
|
||||
return hou.expandStringAtFrame(expression, 0)
|
||||
|
||||
|
||||
class CollectKarmaROPRenderProducts(pyblish.api.InstancePlugin):
|
||||
"""Collect Karma Render Products
|
||||
|
||||
Collects the instance.data["files"] for the multipart render product.
|
||||
|
||||
Provides:
|
||||
instance -> files
|
||||
|
||||
"""
|
||||
|
||||
label = "Karma ROP Render Products"
|
||||
order = pyblish.api.CollectorOrder + 0.4
|
||||
hosts = ["houdini"]
|
||||
families = ["karma_rop"]
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
rop = hou.node(instance.data.get("instance_node"))
|
||||
|
||||
# Collect chunkSize
|
||||
chunk_size_parm = rop.parm("chunkSize")
|
||||
if chunk_size_parm:
|
||||
chunk_size = int(chunk_size_parm.eval())
|
||||
instance.data["chunkSize"] = chunk_size
|
||||
self.log.debug("Chunk Size: %s" % chunk_size)
|
||||
|
||||
default_prefix = evalParmNoFrame(rop, "picture")
|
||||
render_products = []
|
||||
|
||||
# Default beauty AOV
|
||||
beauty_product = self.get_render_product_name(
|
||||
prefix=default_prefix, suffix=None
|
||||
)
|
||||
render_products.append(beauty_product)
|
||||
|
||||
files_by_aov = {
|
||||
"beauty": self.generate_expected_files(instance,
|
||||
beauty_product)
|
||||
}
|
||||
|
||||
filenames = list(render_products)
|
||||
instance.data["files"] = filenames
|
||||
|
||||
for product in render_products:
|
||||
self.log.debug("Found render product: %s" % product)
|
||||
|
||||
if "expectedFiles" not in instance.data:
|
||||
instance.data["expectedFiles"] = list()
|
||||
instance.data["expectedFiles"].append(files_by_aov)
|
||||
|
||||
def get_render_product_name(self, prefix, suffix):
|
||||
if suffix:
|
||||
# Add ".{suffix}" before the extension
|
||||
prefix_base, ext = os.path.splitext(prefix)
|
||||
product_name = prefix_base + "." + suffix + ext
|
||||
else:
|
||||
product_name = prefix
|
||||
|
||||
return product_name
|
||||
|
||||
def generate_expected_files(self, instance, path):
|
||||
"""Create expected files in instance data"""
|
||||
|
||||
dir = os.path.dirname(path)
|
||||
file = os.path.basename(path)
|
||||
|
||||
if "#" in file:
|
||||
pparts = file.split("#")
|
||||
padding = "%0{}d".format(len(pparts) - 1)
|
||||
file = pparts[0] + padding + pparts[-1]
|
||||
|
||||
if "%" not in file:
|
||||
return path
|
||||
|
||||
expected_files = []
|
||||
start = instance.data["frameStart"]
|
||||
end = instance.data["frameEnd"]
|
||||
for i in range(int(start), (int(end) + 1)):
|
||||
expected_files.append(
|
||||
os.path.join(dir, (file % i)).replace("\\", "/"))
|
||||
|
||||
return expected_files
|
||||
158
openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py
Normal file
158
openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
import re
|
||||
import os
|
||||
|
||||
import hou
|
||||
import pyblish.api
|
||||
|
||||
|
||||
def get_top_referenced_parm(parm):
|
||||
|
||||
processed = set() # disallow infinite loop
|
||||
while True:
|
||||
if parm.path() in processed:
|
||||
raise RuntimeError("Parameter references result in cycle.")
|
||||
|
||||
processed.add(parm.path())
|
||||
|
||||
ref = parm.getReferencedParm()
|
||||
if ref.path() == parm.path():
|
||||
# It returns itself when it doesn't reference
|
||||
# another parameter
|
||||
return ref
|
||||
else:
|
||||
parm = ref
|
||||
|
||||
|
||||
def evalParmNoFrame(node, parm, pad_character="#"):
|
||||
|
||||
parameter = node.parm(parm)
|
||||
assert parameter, "Parameter does not exist: %s.%s" % (node, parm)
|
||||
|
||||
# If the parameter has a parameter reference, then get that
|
||||
# parameter instead as otherwise `unexpandedString()` fails.
|
||||
parameter = get_top_referenced_parm(parameter)
|
||||
|
||||
# Substitute out the frame numbering with padded characters
|
||||
try:
|
||||
raw = parameter.unexpandedString()
|
||||
except hou.Error as exc:
|
||||
print("Failed: %s" % parameter)
|
||||
raise RuntimeError(exc)
|
||||
|
||||
def replace(match):
|
||||
padding = 1
|
||||
n = match.group(2)
|
||||
if n and int(n):
|
||||
padding = int(n)
|
||||
return pad_character * padding
|
||||
|
||||
expression = re.sub(r"(\$F([0-9]*))", replace, raw)
|
||||
|
||||
with hou.ScriptEvalContext(parameter):
|
||||
return hou.expandStringAtFrame(expression, 0)
|
||||
|
||||
|
||||
class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin):
|
||||
"""Collect Mantra Render Products
|
||||
|
||||
Collects the instance.data["files"] for the render products.
|
||||
|
||||
Provides:
|
||||
instance -> files
|
||||
|
||||
"""
|
||||
|
||||
label = "Mantra ROP Render Products"
|
||||
order = pyblish.api.CollectorOrder + 0.4
|
||||
hosts = ["houdini"]
|
||||
families = ["mantra_rop"]
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
rop = hou.node(instance.data.get("instance_node"))
|
||||
|
||||
# Collect chunkSize
|
||||
chunk_size_parm = rop.parm("chunkSize")
|
||||
if chunk_size_parm:
|
||||
chunk_size = int(chunk_size_parm.eval())
|
||||
instance.data["chunkSize"] = chunk_size
|
||||
self.log.debug("Chunk Size: %s" % chunk_size)
|
||||
|
||||
default_prefix = evalParmNoFrame(rop, "vm_picture")
|
||||
render_products = []
|
||||
|
||||
# Default beauty AOV
|
||||
beauty_product = self.get_render_product_name(
|
||||
prefix=default_prefix, suffix=None
|
||||
)
|
||||
render_products.append(beauty_product)
|
||||
|
||||
files_by_aov = {
|
||||
"beauty": self.generate_expected_files(instance,
|
||||
beauty_product)
|
||||
}
|
||||
|
||||
aov_numbers = rop.evalParm("vm_numaux")
|
||||
if aov_numbers > 0:
|
||||
# get the filenames of the AOVs
|
||||
for i in range(1, aov_numbers + 1):
|
||||
var = rop.evalParm("vm_variable_plane%d" % i)
|
||||
if var:
|
||||
aov_name = "vm_filename_plane%d" % i
|
||||
has_aov_path = rop.evalParm(aov_name)
|
||||
if has_aov_path:
|
||||
aov_prefix = evalParmNoFrame(rop, aov_name)
|
||||
aov_product = self.get_render_product_name(
|
||||
prefix=aov_prefix, suffix=None
|
||||
)
|
||||
render_products.append(aov_product)
|
||||
|
||||
files_by_aov[var] = self.generate_expected_files(instance,
|
||||
aov_product)
|
||||
for product in render_products:
|
||||
self.log.debug("Found render product: %s" % product)
|
||||
filenames = list(render_products)
|
||||
instance.data["files"] = filenames
|
||||
|
||||
# For now by default do NOT try to publish the rendered output
|
||||
instance.data["publishJobState"] = "Suspended"
|
||||
instance.data["attachTo"] = [] # stub required data
|
||||
|
||||
if "expectedFiles" not in instance.data:
|
||||
instance.data["expectedFiles"] = list()
|
||||
instance.data["expectedFiles"].append(files_by_aov)
|
||||
self.log.debug("expectedFiles: %s" % files_by_aov)
|
||||
|
||||
|
||||
def get_render_product_name(self, prefix, suffix):
|
||||
if suffix:
|
||||
# Add ".{suffix}" before the extension
|
||||
prefix_base, ext = os.path.splitext(prefix)
|
||||
product_name = prefix_base + "." + suffix + ext
|
||||
else:
|
||||
product_name = prefix
|
||||
|
||||
return product_name
|
||||
|
||||
def generate_expected_files(self, instance, path):
|
||||
"""Create expected files in instance data"""
|
||||
|
||||
dir = os.path.dirname(path)
|
||||
file = os.path.basename(path)
|
||||
|
||||
if "#" in file:
|
||||
pparts = file.split("#")
|
||||
padding = "%0{}d".format(len(pparts) - 1)
|
||||
file = pparts[0] + padding + pparts[-1]
|
||||
|
||||
if "%" not in file:
|
||||
return path
|
||||
|
||||
expected_files = []
|
||||
start = instance.data["frameStart"]
|
||||
end = instance.data["frameEnd"]
|
||||
for i in range(int(start), (int(end) + 1)):
|
||||
expected_files.append(
|
||||
os.path.join(dir, (file % i)).replace("\\", "/"))
|
||||
|
||||
return expected_files
|
||||
|
|
@ -87,6 +87,10 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
|
|||
prefix=default_prefix, suffix=beauty_suffix
|
||||
)
|
||||
render_products.append(beauty_product)
|
||||
files_by_aov = {
|
||||
"beauty": self.generate_expected_files(instance,
|
||||
beauty_product)
|
||||
}
|
||||
|
||||
num_aovs = rop.evalParm("RS_aov")
|
||||
for index in range(num_aovs):
|
||||
|
|
@ -104,11 +108,21 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
|
|||
aov_product = self.get_render_product_name(aov_prefix, aov_suffix)
|
||||
render_products.append(aov_product)
|
||||
|
||||
files_by_aov[aov_suffix] = self.generate_expected_files(instance,
|
||||
aov_product)
|
||||
|
||||
for product in render_products:
|
||||
self.log.debug("Found render product: %s" % product)
|
||||
filenames = list(render_products)
|
||||
instance.data["files"] = filenames
|
||||
|
||||
filenames = list(render_products)
|
||||
instance.data["files"] = filenames
|
||||
# For now by default do NOT try to publish the rendered output
|
||||
instance.data["publishJobState"] = "Suspended"
|
||||
instance.data["attachTo"] = [] # stub required data
|
||||
|
||||
if "expectedFiles" not in instance.data:
|
||||
instance.data["expectedFiles"] = list()
|
||||
instance.data["expectedFiles"].append(files_by_aov)
|
||||
|
||||
def get_render_product_name(self, prefix, suffix):
|
||||
"""Return the output filename using the AOV prefix and suffix"""
|
||||
|
|
@ -133,3 +147,26 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
|
|||
product_name = prefix
|
||||
|
||||
return product_name
|
||||
|
||||
def generate_expected_files(self, instance, path):
|
||||
"""Create expected files in instance data"""
|
||||
|
||||
dir = os.path.dirname(path)
|
||||
file = os.path.basename(path)
|
||||
|
||||
if "#" in file:
|
||||
pparts = file.split("#")
|
||||
padding = "%0{}d".format(len(pparts) - 1)
|
||||
file = pparts[0] + padding + pparts[-1]
|
||||
|
||||
if "%" not in file:
|
||||
return path
|
||||
|
||||
expected_files = []
|
||||
start = instance.data["frameStart"]
|
||||
end = instance.data["frameEnd"]
|
||||
for i in range(int(start), (int(end) + 1)):
|
||||
expected_files.append(
|
||||
os.path.join(dir, (file % i)).replace("\\", "/"))
|
||||
|
||||
return expected_files
|
||||
|
|
|
|||
|
|
@ -14,7 +14,12 @@ class IncrementCurrentFile(pyblish.api.ContextPlugin):
|
|||
label = "Increment current file"
|
||||
order = pyblish.api.IntegratorOrder + 9.0
|
||||
hosts = ["houdini"]
|
||||
families = ["workfile"]
|
||||
families = ["workfile",
|
||||
"redshift_rop",
|
||||
"arnold_rop",
|
||||
"mantra_rop",
|
||||
"karma_rop",
|
||||
"usdrender"]
|
||||
optional = True
|
||||
|
||||
def process(self, context):
|
||||
|
|
|
|||
|
|
@ -663,7 +663,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin):
|
|||
|
||||
# test if there is instance of workfile waiting
|
||||
# to be published.
|
||||
assert i.data["publish"] is True, (
|
||||
assert i.data.get("publish", True) is True, (
|
||||
"Workfile (scene) must be published along")
|
||||
|
||||
return i
|
||||
|
|
|
|||
|
|
@ -1,19 +1,27 @@
|
|||
import hou
|
||||
|
||||
import os
|
||||
import json
|
||||
import attr
|
||||
import getpass
|
||||
from datetime import datetime
|
||||
|
||||
import requests
|
||||
import pyblish.api
|
||||
|
||||
# import hou ???
|
||||
|
||||
from openpype.pipeline import legacy_io
|
||||
from openpype.tests.lib import is_in_tests
|
||||
from openpype_modules.deadline import abstract_submit_deadline
|
||||
from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo
|
||||
from openpype.lib import is_running_from_build
|
||||
|
||||
|
||||
class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin):
|
||||
@attr.s
|
||||
class DeadlinePluginInfo():
|
||||
SceneFile = attr.ib(default=None)
|
||||
OutputDriver = attr.ib(default=None)
|
||||
Version = attr.ib(default=None)
|
||||
IgnoreInputs = attr.ib(default=True)
|
||||
|
||||
|
||||
class HoudiniSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
|
||||
"""Submit Solaris USD Render ROPs to Deadline.
|
||||
|
||||
Renders are submitted to a Deadline Web Service as
|
||||
|
|
@ -30,83 +38,55 @@ class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin):
|
|||
order = pyblish.api.IntegratorOrder
|
||||
hosts = ["houdini"]
|
||||
families = ["usdrender",
|
||||
"redshift_rop"]
|
||||
"redshift_rop",
|
||||
"mantra_rop",
|
||||
"karma_rop"]
|
||||
targets = ["local"]
|
||||
use_published = True
|
||||
|
||||
def process(self, instance):
|
||||
def get_job_info(self):
|
||||
job_info = DeadlineJobInfo(Plugin="Houdini")
|
||||
|
||||
instance = self._instance
|
||||
context = instance.context
|
||||
code = context.data["code"]
|
||||
|
||||
filepath = context.data["currentFile"]
|
||||
filename = os.path.basename(filepath)
|
||||
comment = context.data.get("comment", "")
|
||||
deadline_user = context.data.get("deadlineUser", getpass.getuser())
|
||||
jobname = "%s - %s" % (filename, instance.name)
|
||||
|
||||
# Support code prefix label for batch name
|
||||
batch_name = filename
|
||||
if code:
|
||||
batch_name = "{0} - {1}".format(code, batch_name)
|
||||
job_info.Name = "%s - %s" % (filename, instance.name)
|
||||
job_info.BatchName = filename
|
||||
job_info.Plugin = "Houdini"
|
||||
job_info.UserName = context.data.get(
|
||||
"deadlineUser", getpass.getuser())
|
||||
|
||||
if is_in_tests():
|
||||
batch_name += datetime.now().strftime("%d%m%Y%H%M%S")
|
||||
job_info.BatchName += datetime.now().strftime("%d%m%Y%H%M%S")
|
||||
|
||||
# Output driver to render
|
||||
driver = instance[0]
|
||||
|
||||
# StartFrame to EndFrame by byFrameStep
|
||||
# Deadline requires integers in frame range
|
||||
frames = "{start}-{end}x{step}".format(
|
||||
start=int(instance.data["frameStart"]),
|
||||
end=int(instance.data["frameEnd"]),
|
||||
step=int(instance.data["byFrameStep"]),
|
||||
)
|
||||
job_info.Frames = frames
|
||||
|
||||
# Documentation for keys available at:
|
||||
# https://docs.thinkboxsoftware.com
|
||||
# /products/deadline/8.0/1_User%20Manual/manual
|
||||
# /manual-submission.html#job-info-file-options
|
||||
payload = {
|
||||
"JobInfo": {
|
||||
# Top-level group name
|
||||
"BatchName": batch_name,
|
||||
job_info.Pool = instance.data.get("primaryPool")
|
||||
job_info.SecondaryPool = instance.data.get("secondaryPool")
|
||||
job_info.ChunkSize = instance.data.get("chunkSize", 10)
|
||||
job_info.Comment = context.data.get("comment")
|
||||
|
||||
# Job name, as seen in Monitor
|
||||
"Name": jobname,
|
||||
|
||||
# Arbitrary username, for visualisation in Monitor
|
||||
"UserName": deadline_user,
|
||||
|
||||
"Plugin": "Houdini",
|
||||
"Pool": instance.data.get("primaryPool"),
|
||||
"secondaryPool": instance.data.get("secondaryPool"),
|
||||
"Frames": frames,
|
||||
|
||||
"ChunkSize": instance.data.get("chunkSize", 10),
|
||||
|
||||
"Comment": comment
|
||||
},
|
||||
"PluginInfo": {
|
||||
# Input
|
||||
"SceneFile": filepath,
|
||||
"OutputDriver": driver.path(),
|
||||
|
||||
# Mandatory for Deadline
|
||||
# Houdini version without patch number
|
||||
"Version": hou.applicationVersionString().rsplit(".", 1)[0],
|
||||
|
||||
"IgnoreInputs": True
|
||||
},
|
||||
|
||||
# Mandatory for Deadline, may be empty
|
||||
"AuxFiles": []
|
||||
}
|
||||
|
||||
# Include critical environment variables with submission + api.Session
|
||||
keys = [
|
||||
# Submit along the current Avalon tool setup that we launched
|
||||
# this application with so the Render Slave can build its own
|
||||
# similar environment using it, e.g. "maya2018;vray4.x;yeti3.1.9"
|
||||
"AVALON_TOOLS"
|
||||
"FTRACK_API_KEY",
|
||||
"FTRACK_API_USER",
|
||||
"FTRACK_SERVER",
|
||||
"OPENPYPE_SG_USER",
|
||||
"AVALON_PROJECT",
|
||||
"AVALON_ASSET",
|
||||
"AVALON_TASK",
|
||||
"AVALON_APP_NAME",
|
||||
"OPENPYPE_DEV",
|
||||
"OPENPYPE_LOG_NO_COLORS",
|
||||
"OPENPYPE_VERSION"
|
||||
]
|
||||
|
||||
# Add OpenPype version if we are running from build.
|
||||
|
|
@ -114,61 +94,50 @@ class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin):
|
|||
keys.append("OPENPYPE_VERSION")
|
||||
|
||||
# Add mongo url if it's enabled
|
||||
if context.data.get("deadlinePassMongoUrl"):
|
||||
if self._instance.context.data.get("deadlinePassMongoUrl"):
|
||||
keys.append("OPENPYPE_MONGO")
|
||||
|
||||
environment = dict({key: os.environ[key] for key in keys
|
||||
if key in os.environ}, **legacy_io.Session)
|
||||
for key in keys:
|
||||
value = environment.get(key)
|
||||
if value:
|
||||
job_info.EnvironmentKeyValue[key] = value
|
||||
|
||||
payload["JobInfo"].update({
|
||||
"EnvironmentKeyValue%d" % index: "{key}={value}".format(
|
||||
key=key,
|
||||
value=environment[key]
|
||||
) for index, key in enumerate(environment)
|
||||
})
|
||||
# to recognize job from PYPE for turning Event On/Off
|
||||
job_info.EnvironmentKeyValue["OPENPYPE_RENDER_JOB"] = "1"
|
||||
|
||||
# Include OutputFilename entries
|
||||
# The first entry also enables double-click to preview rendered
|
||||
# frames from Deadline Monitor
|
||||
output_data = {}
|
||||
for i, filepath in enumerate(instance.data["files"]):
|
||||
dirname = os.path.dirname(filepath)
|
||||
fname = os.path.basename(filepath)
|
||||
output_data["OutputDirectory%d" % i] = dirname.replace("\\", "/")
|
||||
output_data["OutputFilename%d" % i] = fname
|
||||
job_info.OutputDirectory += dirname.replace("\\", "/")
|
||||
job_info.OutputFilename += fname
|
||||
|
||||
# For now ensure destination folder exists otherwise HUSK
|
||||
# will fail to render the output image. This is supposedly fixed
|
||||
# in new production builds of Houdini
|
||||
# TODO Remove this workaround with Houdini 18.0.391+
|
||||
if not os.path.exists(dirname):
|
||||
self.log.info("Ensuring output directory exists: %s" %
|
||||
dirname)
|
||||
os.makedirs(dirname)
|
||||
return job_info
|
||||
|
||||
payload["JobInfo"].update(output_data)
|
||||
def get_plugin_info(self):
|
||||
|
||||
self.submit(instance, payload)
|
||||
instance = self._instance
|
||||
context = instance.context
|
||||
|
||||
def submit(self, instance, payload):
|
||||
# Output driver to render
|
||||
driver = hou.node(instance.data["instance_node"])
|
||||
hou_major_minor = hou.applicationVersionString().rsplit(".", 1)[0]
|
||||
|
||||
AVALON_DEADLINE = legacy_io.Session.get("AVALON_DEADLINE",
|
||||
"http://localhost:8082")
|
||||
assert AVALON_DEADLINE, "Requires AVALON_DEADLINE"
|
||||
plugin_info = DeadlinePluginInfo(
|
||||
SceneFile=context.data["currentFile"],
|
||||
OutputDriver=driver.path(),
|
||||
Version=hou_major_minor,
|
||||
IgnoreInputs=True
|
||||
)
|
||||
|
||||
plugin = payload["JobInfo"]["Plugin"]
|
||||
self.log.info("Using Render Plugin : {}".format(plugin))
|
||||
return attr.asdict(plugin_info)
|
||||
|
||||
self.log.info("Submitting..")
|
||||
self.log.debug(json.dumps(payload, indent=4, sort_keys=True))
|
||||
|
||||
# E.g. http://192.168.0.1:8082/api/jobs
|
||||
url = "{}/api/jobs".format(AVALON_DEADLINE)
|
||||
response = requests.post(url, json=payload)
|
||||
if not response.ok:
|
||||
raise Exception(response.text)
|
||||
def process(self, instance):
|
||||
super(HoudiniSubmitDeadline, self).process(instance)
|
||||
|
||||
# TODO: Avoid the need for this logic here, needed for submit publish
|
||||
# Store output dir for unified publisher (filesequence)
|
||||
output_dir = os.path.dirname(instance.data["files"][0])
|
||||
instance.data["outputDir"] = output_dir
|
||||
instance.data["deadlineSubmissionJob"] = response.json()
|
||||
instance.data["toBeRenderedOn"] = "deadline"
|
||||
|
|
|
|||
|
|
@ -122,7 +122,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
"celaction", "aftereffects", "harmony"]
|
||||
|
||||
families = ["render.farm", "prerender.farm",
|
||||
"renderlayer", "imagesequence", "maxrender", "vrayscene"]
|
||||
"renderlayer", "imagesequence",
|
||||
"maxrender", "vrayscene",
|
||||
"mantra_rop", "karma_rop"]
|
||||
|
||||
aov_filter = {"maya": [r".*([Bb]eauty).*"],
|
||||
"aftereffects": [r".*"], # for everything from AE
|
||||
|
|
@ -140,7 +142,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
"FTRACK_SERVER",
|
||||
"AVALON_APP_NAME",
|
||||
"OPENPYPE_USERNAME",
|
||||
"OPENPYPE_SG_USER",
|
||||
"OPENPYPE_VERSION",
|
||||
"OPENPYPE_SG_USER"
|
||||
]
|
||||
|
||||
# Add OpenPype version if we are running from build.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue