hound cleanup 🐶🧽🧺 II.

This commit is contained in:
Ondrej Samohel 2021-07-16 15:25:14 +02:00 committed by Ondřej Samohel
parent c85bd30e1b
commit 7b2932c9a7
No known key found for this signature in database
GPG key ID: 02376E18990A97C6
67 changed files with 652 additions and 542 deletions

View file

@ -23,7 +23,7 @@ class CreateAlembicCamera(plugin.Creator):
parms = {
"filename": "$HIP/pyblish/%s.abc" % self.name,
"use_sop_path": False
"use_sop_path": False,
}
if self.nodes:
@ -33,10 +33,7 @@ class CreateAlembicCamera(plugin.Creator):
# Split the node path into the first root and the remainder
# So we can set the root and objects parameters correctly
_, root, remainder = path.split("/", 2)
parms.update({
"root": "/" + root,
"objects": remainder
})
parms.update({"root": "/" + root, "objects": remainder})
instance.setParms(parms)

View file

@ -5,7 +5,7 @@ class CreateCompositeSequence(houdini.Creator):
"""Composite ROP to Image Sequence"""
label = "Composite (Image Sequence)"
family = "colorbleed.imagesequence"
family = "imagesequence"
icon = "gears"
def __init__(self, *args, **kwargs):

View file

@ -20,13 +20,15 @@ class CreatePointCache(plugin.Creator):
def process(self):
instance = super(CreatePointCache, self).process()
parms = {"use_sop_path": True, # Export single node from SOP Path
"build_from_path": True, # Direct path of primitive in output
"path_attrib": "path", # Pass path attribute for output
"prim_to_detail_pattern": "cbId",
"format": 2, # Set format to Ogawa
"facesets": 0, # No face sets (by default exclude them)
"filename": "$HIP/pyblish/%s.abc" % self.name}
parms = {
"use_sop_path": True, # Export single node from SOP Path
"build_from_path": True, # Direct path of primitive in output
"path_attrib": "path", # Pass path attribute for output
"prim_to_detail_pattern": "cbId",
"format": 2, # Set format to Ogawa
"facesets": 0, # No face sets (by default exclude them)
"filename": "$HIP/pyblish/%s.abc" % self.name,
}
if self.nodes:
node = self.nodes[0]

View file

@ -35,8 +35,9 @@ class CreateRedshiftROP(houdini.Creator):
instance.setName(basename + "_ROP", unique_name=True)
# Also create the linked Redshift IPR Rop
ipr_rop = self.parent.createNode("Redshift_IPR",
node_name=basename + "_IPR")
ipr_rop = self.parent.createNode(
"Redshift_IPR", node_name=basename + "_IPR"
)
# Move it to directly under the Redshift ROP
ipr_rop.setPosition(instance.position() + hou.Vector2(0, -1))
@ -48,11 +49,10 @@ class CreateRedshiftROP(houdini.Creator):
parms = {
# Render frame range
"trange": 1,
# Redshift ROP settings
"RS_outputFileNamePrefix": prefix,
"RS_outputMultilayerMode": 0, # no multi-layered exr
"RS_outputBeautyAOVSuffix": "beauty"
"RS_outputMultilayerMode": 0, # no multi-layered exr
"RS_outputBeautyAOVSuffix": "beauty",
}
instance.setParms(parms)

View file

@ -5,7 +5,7 @@ class CreateUSD(houdini.Creator):
"""Universal Scene Description"""
label = "USD"
family = "colorbleed.usd"
family = "usd"
icon = "gears"
def __init__(self, *args, **kwargs):
@ -21,7 +21,7 @@ class CreateUSD(houdini.Creator):
parms = {
"lopoutput": "$HIP/pyblish/%s.usd" % self.name,
"enableoutputprocessor_simplerelativepaths": False
"enableoutputprocessor_simplerelativepaths": False,
}
if self.nodes:
@ -31,9 +31,12 @@ class CreateUSD(houdini.Creator):
instance.setParms(parms)
# Lock any parameters in this list
to_lock = ["fileperframe",
# Lock some Avalon attributes
"family", "id"]
to_lock = [
"fileperframe",
# Lock some Avalon attributes
"family",
"id",
]
for name in to_lock:
parm = instance.parm(name)
parm.lock(True)

View file

@ -13,7 +13,7 @@ class CreateUSDModel(api.Creator):
def process(self):
node_type = "cb::author_model:1.0"
node_type = "op::author_model:1.0"
subset = self.data["subset"]
name = "author_{}".format(subset)
@ -24,10 +24,7 @@ class CreateUSDModel(api.Creator):
instance = stage.createNode(node_type, node_name=name)
instance.moveToGoodPosition(move_unconnected=True)
parms = {
"asset_name": self.data["asset"],
"variant_name": variant
}
parms = {"asset_name": self.data["asset"], "variant_name": variant}
# Set the Geo Path to the first selected node (if any)
selection = hou.selectedNodes()

View file

@ -31,9 +31,7 @@ class _USDWorkspace(api.Creator):
# With the Workspace HDAs there is no need to imprint the instance data
# since this data is pre-built into it. However, we do set the right
# asset as that can be defined by the user.
parms = {
"asset": self.data["asset"]
}
parms = {"asset": self.data["asset"]}
instance.setParms(parms)
return instance
@ -47,7 +45,7 @@ class USDCreateShadingWorkspace(_USDWorkspace):
label = "USD Shading Workspace"
family = "colorbleed.shade.usd"
node_type = "cb::shadingWorkspace::1.0"
node_type = "op::shadingWorkspace::1.0"
node_name = "shadingWorkspace"
step = "Shade"

View file

@ -7,7 +7,7 @@ class CreateUSDRender(houdini.Creator):
"""USD Render ROP in /stage"""
label = "USD Render"
family = "colorbleed.usdrender"
family = "usdrender"
icon = "magic"
def __init__(self, *args, **kwargs):

View file

@ -21,8 +21,10 @@ class CreateVDBCache(plugin.Creator):
def process(self):
instance = super(CreateVDBCache, self).process()
parms = {"sopoutput": "$HIP/pyblish/%s.$F4.vdb" % self.name,
"initsim": True}
parms = {
"sopoutput": "$HIP/pyblish/%s.$F4.vdb" % self.name,
"initsim": True,
}
if self.nodes:
node = self.nodes[0]

View file

@ -6,13 +6,15 @@ from avalon import api
class SetFrameRangeLoader(api.Loader):
"""Set Maya frame range"""
"""Set Houdini frame range"""
families = ["colorbleed.animation",
"colorbleed.camera",
"colorbleed.pointcache",
"colorbleed.vdbcache",
"colorbleed.usd"]
families = [
"animation",
"camera",
"pointcache",
"vdbcache",
"usd",
]
representations = ["abc", "vdb", "usd"]
label = "Set frame range"
@ -24,15 +26,17 @@ class SetFrameRangeLoader(api.Loader):
import hou
version = context['version']
version = context["version"]
version_data = version.get("data", {})
start = version_data.get("startFrame", None)
end = version_data.get("endFrame", None)
if start is None or end is None:
print("Skipping setting frame range because start or "
"end frame data is missing..")
print(
"Skipping setting frame range because start or "
"end frame data is missing.."
)
return
hou.playbar.setFrameRange(start, end)
@ -42,11 +46,13 @@ class SetFrameRangeLoader(api.Loader):
class SetFrameRangeWithHandlesLoader(api.Loader):
"""Set Maya frame range including pre- and post-handles"""
families = ["colorbleed.animation",
"colorbleed.camera",
"colorbleed.pointcache",
"colorbleed.vdbcache",
"colorbleed.usd"]
families = [
"animation",
"camera",
"pointcache",
"vdbcache",
"usd",
]
representations = ["abc", "vdb", "usd"]
label = "Set frame range (with handles)"
@ -58,15 +64,17 @@ class SetFrameRangeWithHandlesLoader(api.Loader):
import hou
version = context['version']
version = context["version"]
version_data = version.get("data", {})
start = version_data.get("startFrame", None)
end = version_data.get("endFrame", None)
if start is None or end is None:
print("Skipping setting frame range because start or "
"end frame data is missing..")
print(
"Skipping setting frame range because start or "
"end frame data is missing.."
)
return
# Include handles

View file

@ -6,10 +6,7 @@ from avalon.houdini import pipeline, lib
class AbcLoader(api.Loader):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["model",
"animation",
"pointcache",
"gpuCache"]
families = ["model", "animation", "pointcache", "gpuCache"]
label = "Load Alembic"
representations = ["abc"]
order = -10
@ -80,19 +77,22 @@ class AbcLoader(api.Loader):
self[:] = nodes
return pipeline.containerise(node_name,
namespace,
nodes,
context,
self.__class__.__name__,
suffix="")
return pipeline.containerise(
node_name,
namespace,
nodes,
context,
self.__class__.__name__,
suffix="",
)
def update(self, container, representation):
node = container["node"]
try:
alembic_node = next(n for n in node.children() if
n.type().name() == "alembic")
alembic_node = next(
n for n in node.children() if n.type().name() == "alembic"
)
except StopIteration:
self.log.error("Could not find node of type `alembic`")
return

View file

@ -2,24 +2,25 @@ from avalon import api
from avalon.houdini import pipeline, lib
ARCHIVE_EXPRESSION = '__import__("_alembic_hom_extensions").alembicGetCameraDict'
ARCHIVE_EXPRESSION = ('__import__("_alembic_hom_extensions")'
'.alembicGetCameraDict')
def transfer_non_default_values(src, dest, ignore=None):
"""Copy parm from src to dest.
Because the Alembic Archive rebuilds the entire node
hierarchy on triggering "Build Hierarchy" we want to
preserve any local tweaks made by the user on the camera
for ease of use. That could be a background image, a
resolution change or even Redshift camera parameters.
We try to do so by finding all Parms that exist on both
source and destination node, include only those that both
are not at their default value, they must be visible,
we exclude those that have the special "alembic archive"
channel expression and ignore certain Parm types.
"""
import hou

View file

@ -5,14 +5,15 @@ from avalon.houdini import pipeline, lib
import hou
def get_image_avalon_container():
"""The COP2 files must be in a COP2 network.
"""The COP2 files must be in a COP2 network.
So we maintain a single entry point within AVALON_CONTAINERS,
just for ease of use.
"""
path = pipeline.AVALON_CONTAINERS
avalon_container = hou.node(path)
if not avalon_container:
@ -20,18 +21,21 @@ def get_image_avalon_container():
# but make sure the pipeline still is built the
# way we anticipate it was built, asserting it.
assert path == "/obj/AVALON_CONTAINERS"
parent = hou.node("/obj")
avalon_container = parent.createNode("subnet",
node_name="AVALON_CONTAINERS")
avalon_container = parent.createNode(
"subnet", node_name="AVALON_CONTAINERS"
)
image_container = hou.node(path + "/IMAGES")
if not image_container:
image_container = avalon_container.createNode("cop2net", node_name="IMAGES")
image_container = avalon_container.createNode(
"cop2net", node_name="IMAGES"
)
image_container.moveToGoodPosition()
return image_container
class ImageLoader(api.Loader):
"""Specific loader of Alembic for the avalon.animation family"""
@ -57,12 +61,12 @@ class ImageLoader(api.Loader):
# Define node name
namespace = namespace if namespace else context["asset"]["name"]
node_name = "{}_{}".format(namespace, name) if namespace else name
node = parent.createNode("file", node_name=node_name)
node.moveToGoodPosition()
node.setParms({"filename1": file_path})
# Imprint it manually
data = {
"schema": "avalon-core:container-2.0",
@ -75,7 +79,7 @@ class ImageLoader(api.Loader):
# todo: add folder="Avalon"
lib.imprint(node, data)
return node
def update(self, container, representation):
@ -88,32 +92,32 @@ class ImageLoader(api.Loader):
file_path = self._get_file_sequence(file_path)
# Update attributes
node.setParms({
"filename1": file_path,
"representation": str(representation["_id"])
})
node.setParms(
{
"filename1": file_path,
"representation": str(representation["_id"]),
}
)
def remove(self, container):
node = container["node"]
# Let's clean up the IMAGES COP2 network
# if it ends up being empty and we deleted
# the last file node. Store the parent
# before we delete the node.
parent = node.parent()
node.destroy()
if not parent.children():
parent.destroy()
def _get_file_sequence(self, root):
files = sorted(os.listdir(root))
first_fname = files[0]
prefix, padding, suffix = first_fname.rsplit(".", 2)
fname = ".".join([prefix, "$F{}".format(len(padding)), suffix])
return os.path.join(root, fname).replace("\\", "/")
return os.path.join(root, fname).replace("\\", "/")

View file

@ -5,11 +5,13 @@ from avalon.houdini import pipeline, lib
class USDSublayerLoader(api.Loader):
"""Sublayer USD file in Solaris"""
families = ["colorbleed.usd",
"colorbleed.pointcache",
"colorbleed.animation",
"colorbleed.camera",
"usdCamera"]
families = [
"colorbleed.usd",
"colorbleed.pointcache",
"colorbleed.animation",
"colorbleed.camera",
"usdCamera",
]
label = "Sublayer USD"
representations = ["usd", "usda", "usdlc", "usdnc", "abc"]
order = 1
@ -62,8 +64,12 @@ class USDSublayerLoader(api.Loader):
file_path = file_path.replace("\\", "/")
# Update attributes
node.setParms({"filepath1": file_path,
"representation": str(representation["_id"])})
node.setParms(
{
"filepath1": file_path,
"representation": str(representation["_id"]),
}
)
# Reload files
node.parm("reload").pressButton()
@ -71,4 +77,4 @@ class USDSublayerLoader(api.Loader):
def remove(self, container):
node = container["node"]
node.destroy()
node.destroy()

View file

@ -5,11 +5,13 @@ from avalon.houdini import pipeline, lib
class USDReferenceLoader(api.Loader):
"""Reference USD file in Solaris"""
families = ["colorbleed.usd",
"colorbleed.pointcache",
"colorbleed.animation",
"colorbleed.camera",
"usdCamera"]
families = [
"colorbleed.usd",
"colorbleed.pointcache",
"colorbleed.animation",
"colorbleed.camera",
"usdCamera",
]
label = "Reference USD"
representations = ["usd", "usda", "usdlc", "usdnc", "abc"]
order = -8
@ -62,8 +64,12 @@ class USDReferenceLoader(api.Loader):
file_path = file_path.replace("\\", "/")
# Update attributes
node.setParms({"filepath1": file_path,
"representation": str(representation["_id"])})
node.setParms(
{
"filepath1": file_path,
"representation": str(representation["_id"]),
}
)
# Reload files
node.parm("reload").pressButton()

View file

@ -45,12 +45,14 @@ class VdbLoader(api.Loader):
nodes = [container, file_node]
self[:] = nodes
return pipeline.containerise(node_name,
namespace,
nodes,
context,
self.__class__.__name__,
suffix="")
return pipeline.containerise(
node_name,
namespace,
nodes,
context,
self.__class__.__name__,
suffix="",
)
def format_path(self, path):
"""Format file path correctly for single vdb or vdb sequence"""
@ -68,8 +70,10 @@ class VdbLoader(api.Loader):
files = sorted(os.listdir(path))
first = next((x for x in files if x.endswith(".vdb")), None)
if first is None:
raise RuntimeError("Couldn't find first .vdb file of "
"sequence in: %s" % path)
raise RuntimeError(
"Couldn't find first .vdb file of "
"sequence in: %s" % path
)
# Set <frame>.vdb to $F.vdb
first = re.sub(r"\.(\d+)\.vdb$", ".$F.vdb", first)
@ -85,8 +89,9 @@ class VdbLoader(api.Loader):
node = container["node"]
try:
file_node = next(n for n in node.children() if
n.type().name() == "file")
file_node = next(
n for n in node.children() if n.type().name() == "file"
)
except StopIteration:
self.log.error("Could not find node of type `alembic`")
return

View file

@ -40,5 +40,4 @@ class ShowInUsdview(api.Loader):
# Force string to avoid unicode issues
env = {str(key): str(value) for key, value in env.items()}
subprocess.Popen([usdview, filepath, "--renderer", "GL"],
env=env)
subprocess.Popen([usdview, filepath, "--renderer", "GL"], env=env)

View file

@ -1,5 +1,4 @@
import pyblish.api
import openpype.api
class CollectInstanceActiveState(pyblish.api.InstancePlugin):
@ -28,9 +27,11 @@ class CollectInstanceActiveState(pyblish.api.InstancePlugin):
active = not node.isBypassed()
# Set instance active state
instance.data.update({
"active": active,
# temporarily translation of `active` to `publish` till issue has
# been resolved: https://github.com/pyblish/pyblish-base/issues/307
"publish": active
})
instance.data.update(
{
"active": active,
# temporarily translation of `active` to `publish` till issue has
# been resolved: https://github.com/pyblish/pyblish-base/issues/307
"publish": active,
}
)

View file

@ -9,7 +9,7 @@ class CollectHoudiniCurrentFile(pyblish.api.ContextPlugin):
order = pyblish.api.CollectorOrder - 0.5
label = "Houdini Current File"
hosts = ['houdini']
hosts = ["houdini"]
def process(self, context):
"""Inject the current working file"""
@ -27,8 +27,10 @@ class CollectHoudiniCurrentFile(pyblish.api.ContextPlugin):
# could have existed already. We will allow it if the file exists,
# but show a warning for this edge case to clarify the potential
# false positive.
self.log.warning("Current file is 'untitled.hip' and we are "
"unable to detect whether the current scene is "
"saved correctly.")
self.log.warning(
"Current file is 'untitled.hip' and we are "
"unable to detect whether the current scene is "
"saved correctly."
)
context.data['currentFile'] = filepath
context.data["currentFile"] = filepath

View file

@ -10,8 +10,7 @@ class CollectFrames(pyblish.api.InstancePlugin):
order = pyblish.api.CollectorOrder
label = "Collect Frames"
families = ["vdbcache",
"imagesequence"]
families = ["vdbcache", "imagesequence"]
def process(self, instance):
@ -39,9 +38,9 @@ class CollectFrames(pyblish.api.InstancePlugin):
# Check if frames are bigger than 1 (file collection)
# override the result
if end_frame - start_frame > 1:
result = self.create_file_list(match,
int(start_frame),
int(end_frame))
result = self.create_file_list(
match, int(start_frame), int(end_frame)
)
# todo: `frames` currently conflicts with "explicit frames" for a
# for a custom frame list. So this should be refactored.
@ -67,12 +66,12 @@ class CollectFrames(pyblish.api.InstancePlugin):
# Get the parts of the filename surrounding the frame number
# so we can put our own frame numbers in.
span = match.span(1)
prefix = match.string[:span[0]]
prefix = match.string[: span[0]]
suffix = match.string[span[1]:]
# Generate filenames for all frames
result = []
for i in range(start_frame, end_frame+1):
for i in range(start_frame, end_frame + 1):
# Format frame number by the padding amount
str_frame = "{number:0{width}d}".format(number=i, width=padding)

View file

@ -1,6 +1,3 @@
import hou
import avalon.io as io
import avalon.api as api
import pyblish.api
@ -30,7 +27,7 @@ def collect_input_containers(nodes):
# and the contained children should be all we need. So we disregard
# checking for .references() on the nodes.
members = set(node.allSubChildren())
members.add(node) # include the node itself
members.add(node) # include the node itself
# If there's an intersection
if not lookup.isdisjoint(members):
@ -51,8 +48,9 @@ def iter_upstream(node):
"""
upstream = node.inputAncestors(include_ref_inputs=True,
follow_subnets=True)
upstream = node.inputAncestors(
include_ref_inputs=True, follow_subnets=True
)
# Initialize process queue with the node's ancestors itself
queue = list(upstream)
@ -73,8 +71,9 @@ def iter_upstream(node):
# Include the references' ancestors that have not been collected yet.
for reference in references:
ancestors = reference.inputAncestors(include_ref_inputs=True,
follow_subnets=True)
ancestors = reference.inputAncestors(
include_ref_inputs=True, follow_subnets=True
)
ancestors = [n for n in ancestors if n not in collected]
queue.extend(ancestors)
@ -103,8 +102,9 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin):
if output is None:
# If no valid output node is set then ignore it as validation
# will be checking those cases.
self.log.debug("No output node found, skipping "
"collecting of inputs..")
self.log.debug(
"No output node found, skipping " "collecting of inputs.."
)
return
# Collect all upstream parents

View file

@ -62,7 +62,7 @@ class CollectInstances(pyblish.api.ContextPlugin):
# Create nice name if the instance has a frame range.
label = data.get("name", node.name())
label += " (%s)" % data["asset"] # include asset in name
label += " (%s)" % data["asset"] # include asset in name
if "frameStart" in data and "frameEnd" in data:
frames = "[{frameStart} - {frameEnd}]".format(**data)

View file

@ -1,8 +1,5 @@
import os
import hou
import pyblish.api
from avalon import io
from avalon.houdini import lib
import openpype.hosts.houdini.api.usd as hou_usdlib
import openpype.lib.usdlib as usdlib

View file

@ -5,12 +5,14 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin):
"""Collect the out node's SOP/COP Path value."""
order = pyblish.api.CollectorOrder
families = ["pointcache",
"camera",
"vdbcache",
"imagesequence",
"usd",
"usdrender"]
families = [
"pointcache",
"camera",
"vdbcache",
"imagesequence",
"usd",
"usdrender",
]
hosts = ["houdini"]
label = "Collect Output Node Path"
@ -53,8 +55,9 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin):
out_node = node.parm("loppath").evalAsNode()
else:
raise ValueError("ROP node type '%s' is"
" not supported." % node_type)
raise ValueError(
"ROP node type '%s' is" " not supported." % node_type
)
if not out_node:
self.log.warning("No output node collected.")

View file

@ -7,11 +7,11 @@ import pyblish.api
def get_top_referenced_parm(parm):
processed = set() # disallow infinite loop
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()
@ -27,7 +27,7 @@ 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)
@ -38,7 +38,7 @@ def evalParmNoFrame(node, parm, pad_character="#"):
except hou.Error as exc:
print("Failed: %s" % parameter)
raise RuntimeError(exc)
def replace(match):
padding = 1
n = match.group(2)
@ -70,31 +70,32 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
def process(self, instance):
rop = instance[0]
# 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, "RS_outputFileNamePrefix")
beauty_suffix = rop.evalParm("RS_outputBeautyAOVSuffix")
render_products = []
# Default beauty AOV
beauty_product = self.get_render_product_name(prefix=default_prefix,
suffix=beauty_suffix)
beauty_product = self.get_render_product_name(
prefix=default_prefix, suffix=beauty_suffix
)
render_products.append(beauty_product)
num_aovs = rop.evalParm("RS_aov")
for index in range(num_aovs):
i = index + 1
# Skip disabled AOVs
if not rop.evalParm("RS_aovEnable_%s" % i):
continue
aov_suffix = rop.evalParm("RS_aovSuffix_%s" % i)
aov_prefix = evalParmNoFrame(rop, "RS_aovCustomPrefix_%s" % i)
if not aov_prefix:
@ -122,10 +123,7 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
# there is no suffix for the current product, for example:
# foo_%AOV% -> foo.exr
pattern = "%AOV%" if suffix else "[._-]?%AOV%"
product_name = re.sub(pattern,
suffix,
prefix,
flags=re.IGNORECASE)
product_name = re.sub(pattern, suffix, prefix, flags=re.IGNORECASE)
else:
if suffix:
# Add ".{suffix}" before the extension

View file

@ -10,9 +10,9 @@ class CollectRemotePublishSettings(pyblish.api.ContextPlugin):
order = pyblish.api.CollectorOrder
families = ["*"]
hosts = ['houdini']
hosts = ["houdini"]
targets = ["deadline"]
label = 'Remote Publish Submission Settings'
label = "Remote Publish Submission Settings"
actions = [openpype.api.RepairAction]
def process(self, context):

View file

@ -28,7 +28,7 @@ def get_var_changed(variable=None):
cmd = "varchange -V"
if variable:
cmd += " {0}".format(variable)
output, errors = hou.hscript(cmd)
output, _ = hou.hscript(cmd)
changed = {}
for line in output.split("Variable: "):
@ -44,7 +44,7 @@ def get_var_changed(variable=None):
class CollectRenderProducts(pyblish.api.InstancePlugin):
"""Collect USD Render Products"""
"""Collect USD Render Products."""
label = "Collect Render Products"
order = pyblish.api.CollectorOrder + 0.4
@ -56,14 +56,17 @@ class CollectRenderProducts(pyblish.api.InstancePlugin):
node = instance.data.get("output_node")
if not node:
rop_path = instance[0].path()
raise RuntimeError("No output node found. Make sure to connect an "
"input to the USD ROP: %s" % rop_path)
raise RuntimeError(
"No output node found. Make sure to connect an "
"input to the USD ROP: %s" % rop_path
)
# Workaround Houdini 18.0.391 bug where $HIPNAME doesn't automatically
# update after scene save.
if hou.applicationVersion() == (18, 0, 391):
self.log.debug("Checking for recook to workaround "
"$HIPNAME refresh bug...")
self.log.debug(
"Checking for recook to workaround " "$HIPNAME refresh bug..."
)
changed = get_var_changed("HIPNAME").get("HIPNAME")
if changed:
self.log.debug("Recooking for $HIPNAME refresh bug...")
@ -101,7 +104,7 @@ class CollectRenderProducts(pyblish.api.InstancePlugin):
# TODO: Confirm this actually is allowed USD stages and HUSK
# Substitute $F
def replace(match):
"""Replace $F4 with padded #"""
"""Replace $F4 with padded #."""
padding = int(match.group(2)) if match.group(2) else 1
return "#" * padding
@ -118,8 +121,10 @@ class CollectRenderProducts(pyblish.api.InstancePlugin):
filename = os.path.join(dirname, filename_base)
filename = filename.replace("\\", "/")
assert "#" in filename, "Couldn't resolve render product name " \
"with frame number: %s" % name
assert "#" in filename, (
"Couldn't resolve render product name "
"with frame number: %s" % name
)
filenames.append(filename)

View file

@ -25,8 +25,7 @@ class CollectUsdBootstrap(pyblish.api.InstancePlugin):
order = pyblish.api.CollectorOrder + 0.35
label = "Collect USD Bootstrap"
hosts = ["houdini"]
families = ["colorbleed.usd",
"colorbleed.usd.layered"]
families = ["usd", "usd.layered"]
def process(self, instance):
@ -35,7 +34,7 @@ class CollectUsdBootstrap(pyblish.api.InstancePlugin):
instance_subset = instance.data["subset"]
for name, layers in usdlib.PIPELINE.items():
if instance_subset in set(layers):
return name # e.g. "asset"
return name # e.g. "asset"
break
else:
return
@ -54,15 +53,11 @@ class CollectUsdBootstrap(pyblish.api.InstancePlugin):
self.log.debug("Add bootstrap for: %s" % bootstrap)
asset = io.find_one({"name": instance.data["asset"],
"type": "asset"})
asset = io.find_one({"name": instance.data["asset"], "type": "asset"})
assert asset, "Asset must exist: %s" % asset
# Check which are not about to be created and don't exist yet
required = {
"shot": ["usdShot"],
"asset": ["usdAsset"]
}.get(bootstrap)
required = {"shot": ["usdShot"], "asset": ["usdAsset"]}.get(bootstrap)
require_all_layers = instance.data.get("requireAllLayers", False)
if require_all_layers:
@ -78,18 +73,18 @@ class CollectUsdBootstrap(pyblish.api.InstancePlugin):
if self._subset_exists(instance, subset, asset):
continue
self.log.debug("Creating {0} USD bootstrap: {1} {2}".format(
bootstrap,
asset["name"],
subset
))
self.log.debug(
"Creating {0} USD bootstrap: {1} {2}".format(
bootstrap, asset["name"], subset
)
)
new = instance.context.create_instance(subset)
new.data["subset"] = subset
new.data["label"] = "{0} ({1})".format(subset, asset["name"])
new.data["family"] = "colorbleed.usd.bootstrap"
new.data["family"] = "usd.bootstrap"
new.data["comment"] = "Automated bootstrap USD file."
new.data["publishFamilies"] = ["colorbleed.usd"]
new.data["publishFamilies"] = ["usd"]
# Do not allow the user to toggle this instance
new.data["optional"] = False
@ -100,7 +95,6 @@ class CollectUsdBootstrap(pyblish.api.InstancePlugin):
def _subset_exists(self, instance, subset, asset):
"""Return whether subset exists in current context or in database."""
# Allow it to be created during this publish session
context = instance.context
for inst in context:
@ -112,6 +106,8 @@ class CollectUsdBootstrap(pyblish.api.InstancePlugin):
# Or, if they already exist in the database we can
# skip them too.
return bool(io.find_one({"name": subset,
"type": "subset",
"parent": asset["_id"]}))
return bool(
io.find_one(
{"name": subset, "type": "subset", "parent": asset["_id"]}
)
)

View file

@ -7,7 +7,6 @@ from avalon.houdini import lib
import openpype.hosts.houdini.lib.usd as usdlib
import hou
from pxr import Sdf
class CollectUsdLayers(pyblish.api.InstancePlugin):

View file

@ -3,7 +3,7 @@ import hou
class CollectWorksceneFPS(pyblish.api.ContextPlugin):
"""Get the FPS of the work scene"""
"""Get the FPS of the work scene."""
label = "Workscene FPS"
order = pyblish.api.CollectorOrder

View file

@ -11,7 +11,7 @@ class ExtractUSD(openpype.api.Extractor):
label = "Extract USD"
hosts = ["houdini"]
targets = ["local"]
families = ["colorbleed.usd",
families = ["usd",
"usdModel",
"usdSetDress"]

View file

@ -12,7 +12,7 @@ from openpype.hosts.houdini.api.lib import render_rop
class ExitStack(object):
"""Context manager for dynamic management of a stack of exit callbacks
"""Context manager for dynamic management of a stack of exit callbacks.
For example:
@ -23,6 +23,7 @@ class ExitStack(object):
# in the list raise an exception
"""
def __init__(self):
self._exit_callbacks = deque()
@ -35,8 +36,10 @@ class ExitStack(object):
def _push_cm_exit(self, cm, cm_exit):
"""Helper to correctly register callbacks to __exit__ methods"""
def _exit_wrapper(*exc_details):
return cm_exit(cm, *exc_details)
_exit_wrapper.__self__ = cm
self.push(_exit_wrapper)
@ -58,20 +61,22 @@ class ExitStack(object):
self._exit_callbacks.append(exit)
else:
self._push_cm_exit(exit, exit_method)
return exit # Allow use as a decorator
return exit # Allow use as a decorator
def callback(self, callback, *args, **kwds):
"""Registers an arbitrary callback and arguments.
Cannot suppress exceptions.
"""
def _exit_wrapper(exc_type, exc, tb):
callback(*args, **kwds)
# We changed the signature, so using @wraps is not appropriate, but
# setting __wrapped__ may still help with introspection
_exit_wrapper.__wrapped__ = callback
self.push(_exit_wrapper)
return callback # Allow use as a decorator
return callback # Allow use as a decorator
def enter_context(self, cm):
"""Enters the supplied context manager
@ -97,6 +102,7 @@ class ExitStack(object):
# We manipulate the exception state so it behaves as though
# we were actually nesting multiple with statements
frame_exc = sys.exc_info()[1]
def _fix_exception_context(new_exc, old_exc):
while 1:
exc_context = new_exc.__context__
@ -148,15 +154,11 @@ class ExtractUSDLayered(openpype.api.Extractor):
label = "Extract Layered USD"
hosts = ["houdini"]
targets = ["local"]
families = ["colorbleed.usd.layered",
"usdShade"]
families = ["colorbleed.usd.layered", "usdShade"]
# Force Output Processors so it will always save any file
# into our unique staging directory with processed Avalon paths
output_processors = [
"avalon_uri_processor",
"stagingdir_processor"
]
output_processors = ["avalon_uri_processor", "stagingdir_processor"]
def process(self, instance):
@ -168,8 +170,9 @@ class ExtractUSDLayered(openpype.api.Extractor):
# The individual rop nodes are collected as "publishDependencies"
dependencies = instance.data["publishDependencies"]
ropnodes = [dependency[0] for dependency in dependencies]
assert all(node.type().name() in {"usd", "usd_rop"}
for node in ropnodes)
assert all(
node.type().name() in {"usd", "usd_rop"} for node in ropnodes
)
# Main ROP node, either a USD Rop or ROP network with multiple USD ROPs
node = instance[0]
@ -177,9 +180,12 @@ class ExtractUSDLayered(openpype.api.Extractor):
# Collect any output dependencies that have not been processed yet
# during extraction of other instances
outputs = [fname]
active_dependencies = [dep for dep in dependencies if
dep.data.get("publish", True) and
not dep.data.get("_isExtracted", False)]
active_dependencies = [
dep
for dep in dependencies
if dep.data.get("publish", True)
and not dep.data.get("_isExtracted", False)
]
for dependency in active_dependencies:
outputs.append(dependency.data["usdFilename"])
@ -192,13 +198,11 @@ class ExtractUSDLayered(openpype.api.Extractor):
# This sets staging directory on the processor to force our
# output files to end up in the Staging Directory.
"stagingdiroutputprocessor_stagingDir": staging_dir,
# Force the Avalon URI Output Processor to refactor paths for
# references, payloads and layers to published paths.
"avalonurioutputprocessor_use_publish_paths": True,
# Only write out specific USD files based on our outputs
"savepattern": save_pattern
"savepattern": save_pattern,
}
overrides = list()
with ExitStack() as stack:
@ -207,7 +211,7 @@ class ExtractUSDLayered(openpype.api.Extractor):
manager = hou_usdlib.outputprocessors(
ropnode,
processors=self.output_processors,
disable_all_others=True
disable_all_others=True,
)
stack.enter_context(manager)
@ -216,8 +220,10 @@ class ExtractUSDLayered(openpype.api.Extractor):
# exist when the Output Processor is added to the ROP node.
for name, value in rop_overrides.items():
parm = ropnode.parm(name)
assert parm, "Parm not found: %s.%s" % (ropnode.path(),
name)
assert parm, "Parm not found: %s.%s" % (
ropnode.path(),
name,
)
overrides.append((parm, value))
stack.enter_context(parm_values(overrides))
@ -236,12 +242,13 @@ class ExtractUSDLayered(openpype.api.Extractor):
dependency_fname = dependency.data["usdFilename"]
filepath = os.path.join(staging_dir, dependency_fname)
similar = self._compare_with_latest_publish(dependency,
filepath)
similar = self._compare_with_latest_publish(dependency, filepath)
if similar:
# Deactivate this dependency
self.log.debug("Dependency matches previous publish version,"
" deactivating %s for publish" % dependency)
self.log.debug(
"Dependency matches previous publish version,"
" deactivating %s for publish" % dependency
)
dependency.data["publish"] = False
else:
self.log.debug("Extracted dependency: %s" % dependency)
@ -265,33 +272,35 @@ class ExtractUSDLayered(openpype.api.Extractor):
# Compare this dependency with the latest published version
# to detect whether we should make this into a new publish
# version. If not, skip it.
asset = io.find_one({
"name": dependency.data["asset"],
"type": "asset"
})
subset = io.find_one({
"name": dependency.data["subset"],
"type": "subset",
"parent": asset["_id"]
})
asset = io.find_one(
{"name": dependency.data["asset"], "type": "asset"}
)
subset = io.find_one(
{
"name": dependency.data["subset"],
"type": "subset",
"parent": asset["_id"],
}
)
if not subset:
# Subset doesn't exist yet. Definitely new file
self.log.debug("No existing subset..")
return False
version = io.find_one({
"type": "version",
"parent": subset["_id"],
}, sort=[("name", -1)])
version = io.find_one(
{"type": "version", "parent": subset["_id"], }, sort=[("name", -1)]
)
if not version:
self.log.debug("No existing version..")
return False
representation = io.find_one({
"name": ext.lstrip("."),
"type": "representation",
"parent": version["_id"]
})
representation = io.find_one(
{
"name": ext.lstrip("."),
"type": "representation",
"parent": version["_id"],
}
)
if not representation:
self.log.debug("No existing representation..")
return False

View file

@ -36,9 +36,9 @@ class ExtractVDBCache(openpype.api.Extractor):
instance.data["representations"] = []
representation = {
'name': 'mov',
'ext': 'mov',
'files': output,
"name": "mov",
"ext": "mov",
"files": output,
"stagingDir": staging_dir,
}
instance.data["representations"].append(representation)

View file

@ -15,8 +15,7 @@ class IncrementCurrentFile(pyblish.api.InstancePlugin):
label = "Increment current file"
order = pyblish.api.IntegratorOrder + 9.0
hosts = ["houdini"]
families = ["colorbleed.usdrender",
"redshift_rop"]
families = ["colorbleed.usdrender", "redshift_rop"]
targets = ["local"]
def process(self, instance):
@ -32,17 +31,21 @@ class IncrementCurrentFile(pyblish.api.InstancePlugin):
context = instance.context
errored_plugins = get_errored_plugins_from_data(context)
if any(plugin.__name__ == "HoudiniSubmitPublishDeadline"
for plugin in errored_plugins):
raise RuntimeError("Skipping incrementing current file because "
"submission to deadline failed.")
if any(
plugin.__name__ == "HoudiniSubmitPublishDeadline"
for plugin in errored_plugins
):
raise RuntimeError(
"Skipping incrementing current file because "
"submission to deadline failed."
)
# Filename must not have changed since collecting
host = avalon.api.registered_host()
current_file = host.current_file()
assert context.data['currentFile'] == current_file, (
"Collected filename from current scene name."
)
assert (
context.data["currentFile"] == current_file
), "Collected filename from current scene name."
new_filepath = version_up(current_file)
host.save(new_filepath)

View file

@ -1,6 +1,5 @@
import pyblish.api
import os
import hou
from openpype.api import version_up
from openpype.action import get_errored_plugins_from_data
@ -21,14 +20,16 @@ class IncrementCurrentFileDeadline(pyblish.api.ContextPlugin):
def process(self, context):
errored_plugins = get_errored_plugins_from_data(context)
if any(plugin.__name__ == "HoudiniSubmitPublishDeadline"
for plugin in errored_plugins):
raise RuntimeError("Skipping incrementing current file because "
"submission to deadline failed.")
if any(
plugin.__name__ == "HoudiniSubmitPublishDeadline"
for plugin in errored_plugins
):
raise RuntimeError(
"Skipping incrementing current file because "
"submission to deadline failed."
)
current_filepath = context.data["currentFile"]
new_filepath = version_up(current_filepath)
hou.hipFile.save(file_name=new_filepath,
save_to_recent_files=True)
hou.hipFile.save(file_name=new_filepath, save_to_recent_files=True)

View file

@ -8,7 +8,7 @@ class SaveCurrentScene(pyblish.api.InstancePlugin):
label = "Save current file"
order = pyblish.api.IntegratorOrder - 0.49
hosts = ["houdini"]
families = ["colorbleed.usdrender",
families = ["usdrender",
"redshift_rop"]
targets = ["local"]

View file

@ -12,9 +12,9 @@ class SaveCurrentSceneDeadline(pyblish.api.ContextPlugin):
def process(self, context):
import hou
assert context.data['currentFile'] == hou.hipFile.path(), (
"Collected filename from current scene name."
)
assert (
context.data["currentFile"] == hou.hipFile.path()
), "Collected filename from current scene name."
if hou.hipFile.hasUnsavedChanges():
self.log.info("Saving current file..")

View file

@ -11,7 +11,7 @@ import hou
class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin):
"""Submit Solaris USD Render ROPs to Deadline
"""Submit Solaris USD Render ROPs to Deadline.
Renders are submitted to a Deadline Web Service as
supplied via the environment variable AVALON_DEADLINE.
@ -26,7 +26,7 @@ class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin):
label = "Submit Render to Deadline"
order = pyblish.api.IntegratorOrder
hosts = ["houdini"]
families = ["colorbleed.usdrender",
families = ["usdrender",
"redshift_rop"]
targets = ["local"]
@ -50,9 +50,9 @@ class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin):
# StartFrame to EndFrame by byFrameStep
frames = "{start}-{end}x{step}".format(
start=int(instance.data["startFrame"]),
end=int(instance.data["endFrame"]),
step=int(instance.data["byFrameStep"]),
start=int(instance.data["startFrame"]),
end=int(instance.data["endFrame"]),
step=int(instance.data["byFrameStep"]),
)
# Documentation for keys available at:

View file

@ -31,12 +31,14 @@ class HoudiniSubmitPublishDeadline(pyblish.api.ContextPlugin):
def process(self, context):
# Ensure no errors so far
assert all(result["success"] for result in context.data["results"]), (
"Errors found, aborting integration..")
assert all(
result["success"] for result in context.data["results"]
), "Errors found, aborting integration.."
# Deadline connection
AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE",
"http://localhost:8082")
AVALON_DEADLINE = api.Session.get(
"AVALON_DEADLINE", "http://localhost:8082"
)
assert AVALON_DEADLINE, "Requires AVALON_DEADLINE"
# Note that `publish` data member might change in the future.
@ -45,8 +47,9 @@ class HoudiniSubmitPublishDeadline(pyblish.api.ContextPlugin):
instance_names = sorted(instance.name for instance in actives)
if not instance_names:
self.log.warning("No active instances found. "
"Skipping submission..")
self.log.warning(
"No active instances found. " "Skipping submission.."
)
return
scene = context.data["currentFile"]
@ -72,30 +75,24 @@ class HoudiniSubmitPublishDeadline(pyblish.api.ContextPlugin):
"BatchName": batch_name,
"Comment": context.data.get("comment", ""),
"Priority": 50,
"Frames": "1-1", # Always trigger a single frame
"Frames": "1-1", # Always trigger a single frame
"IsFrameDependent": False,
"Name": job_name,
"UserName": deadline_user,
# "Comment": instance.context.data.get("comment", ""),
# "InitialStatus": state
},
"PluginInfo": {
"Build": None, # Don't force build
"IgnoreInputs": True,
# Inputs
"SceneFile": scene,
"OutputDriver": "/out/REMOTE_PUBLISH",
# Mandatory for Deadline
"Version": version,
},
# Mandatory for Deadline, may be empty
"AuxFiles": []
"AuxFiles": [],
}
# Process submission per individual instance if the submission
@ -108,14 +105,14 @@ class HoudiniSubmitPublishDeadline(pyblish.api.ContextPlugin):
for instance in instance_names:
# Clarify job name per submission (include instance name)
payload["JobInfo"]["Name"] = job_name + " - %s" % instance
self.submit_job(payload,
instances=[instance],
deadline=AVALON_DEADLINE)
self.submit_job(
payload, instances=[instance], deadline=AVALON_DEADLINE
)
else:
# Submit a single job
self.submit_job(payload,
instances=instance_names,
deadline=AVALON_DEADLINE)
self.submit_job(
payload, instances=instance_names, deadline=AVALON_DEADLINE
)
def submit_job(self, payload, instances, deadline):
@ -130,16 +127,21 @@ class HoudiniSubmitPublishDeadline(pyblish.api.ContextPlugin):
"AVALON_TOOLS",
]
environment = dict({key: os.environ[key] for key in keys
if key in os.environ}, **api.Session)
environment = dict(
{key: os.environ[key] for key in keys if key in os.environ},
**api.Session
)
environment["PYBLISH_ACTIVE_INSTANCES"] = ",".join(instances)
payload["JobInfo"].update({
"EnvironmentKeyValue%d" % index: "{key}={value}".format(
key=key,
value=environment[key]
) for index, key in enumerate(environment)
})
payload["JobInfo"].update(
{
"EnvironmentKeyValue%d"
% index: "{key}={value}".format(
key=key, value=environment[key]
)
for index, key in enumerate(environment)
}
)
# Submit
self.log.info("Submitting..")

View file

@ -3,7 +3,7 @@ import openpype.api
class ValidateVDBInputNode(pyblish.api.InstancePlugin):
"""Validate that the node connected to the output node is of type VDB
"""Validate that the node connected to the output node is of type VDB.
Regardless of the amount of VDBs create the output will need to have an
equal amount of VDBs, points, primitives and vertices
@ -24,8 +24,9 @@ class ValidateVDBInputNode(pyblish.api.InstancePlugin):
def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError("Node connected to the output node is not"
"of type VDB!")
raise RuntimeError(
"Node connected to the output node is not" "of type VDB!"
)
@classmethod
def get_invalid(cls, instance):

View file

@ -16,15 +16,17 @@ class ValidateAbcPrimitiveToDetail(pyblish.api.InstancePlugin):
"""
order = openpype.api.ValidateContentsOrder + 0.1
families = ["colorbleed.pointcache"]
families = ["pointcache"]
hosts = ["houdini"]
label = "Validate Primitive to Detail (Abc)"
def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError("Primitives found with inconsistent primitive "
"to detail attributes. See log.")
raise RuntimeError(
"Primitives found with inconsistent primitive "
"to detail attributes. See log."
)
@classmethod
def get_invalid(cls, instance):
@ -34,21 +36,27 @@ class ValidateAbcPrimitiveToDetail(pyblish.api.InstancePlugin):
rop = instance[0]
pattern = rop.parm("prim_to_detail_pattern").eval().strip()
if not pattern:
cls.log.debug("Alembic ROP has no 'Primitive to Detail' pattern. "
"Validation is ignored..")
cls.log.debug(
"Alembic ROP has no 'Primitive to Detail' pattern. "
"Validation is ignored.."
)
return
build_from_path = rop.parm("build_from_path").eval()
if not build_from_path:
cls.log.debug("Alembic ROP has 'Build from Path' disabled. "
"Validation is ignored..")
cls.log.debug(
"Alembic ROP has 'Build from Path' disabled. "
"Validation is ignored.."
)
return
path_attr = rop.parm("path_attrib").eval()
if not path_attr:
cls.log.error("The Alembic ROP node has no Path Attribute"
"value set, but 'Build Hierarchy from Attribute'"
"is enabled.")
cls.log.error(
"The Alembic ROP node has no Path Attribute"
"value set, but 'Build Hierarchy from Attribute'"
"is enabled."
)
return [rop.path()]
# Let's assume each attribute is explicitly named for now and has no
@ -59,26 +67,32 @@ class ValidateAbcPrimitiveToDetail(pyblish.api.InstancePlugin):
# Check if the primitive attribute exists
frame = instance.data.get("startFrame", 0)
geo = output.geometryAtFrame(frame)
# If there are no primitives on the start frame then it might be
# something that is emitted over time. As such we can't actually
# validate whether the attributes exist, because they won't exist
# yet. In that case, just warn the user and allow it.
if len(geo.iterPrims()) == 0:
cls.log.warning("No primitives found on current frame. Validation"
" for Primitive to Detail will be skipped.")
cls.log.warning(
"No primitives found on current frame. Validation"
" for Primitive to Detail will be skipped."
)
return
attrib = geo.findPrimAttrib(path_attr)
if not attrib:
cls.log.info("Geometry Primitives are missing "
"path attribute: `%s`" % path_attr)
cls.log.info(
"Geometry Primitives are missing "
"path attribute: `%s`" % path_attr
)
return [output.path()]
# Ensure at least a single string value is present
if not attrib.strings():
cls.log.info("Primitive path attribute has no "
"string values: %s" % path_attr)
cls.log.info(
"Primitive path attribute has no "
"string values: %s" % path_attr
)
return [output.path()]
paths = None
@ -111,6 +125,8 @@ class ValidateAbcPrimitiveToDetail(pyblish.api.InstancePlugin):
# Primitive to Detail attribute then we consider it
# inconsistent and invalidate the ROP node's content.
if len(values) > 1:
cls.log.warning("Path has multiple values: %s (path: %s)"
% (list(values), path))
cls.log.warning(
"Path has multiple values: %s (path: %s)"
% (list(values), path)
)
return [output.path()]

View file

@ -4,21 +4,21 @@ import openpype.api
class ValidateAlembicROPFaceSets(pyblish.api.InstancePlugin):
"""Validate Face Sets are disabled for extraction to pointcache.
When groups are saved as Face Sets with the Alembic these show up
as shadingEngine connections in Maya - however, with animated groups
these connections in Maya won't work as expected, it won't update per
frame. Additionally, it can break shader assignments in some cases
frame. Additionally, it can break shader assignments in some cases
where it requires to first break this connection to allow a shader to
be assigned.
It is allowed to include Face Sets, so only an issue is logged to
identify that it could introduce issues down the pipeline.
"""
order = openpype.api.ValidateContentsOrder + 0.1
families = ["colorbleed.pointcache"]
families = ["pointcache"]
hosts = ["houdini"]
label = "Validate Alembic ROP Face Sets"
@ -26,10 +26,12 @@ class ValidateAlembicROPFaceSets(pyblish.api.InstancePlugin):
rop = instance[0]
facesets = rop.parm("facesets").eval()
# 0 = No Face Sets
# 1 = Save Non-Empty Groups as Face Sets
# 2 = Save All Groups As Face Sets
if facesets != 0:
self.log.warning("Alembic ROP saves 'Face Sets' for Geometry. "
"Are you sure you want this?")
if facesets != 0:
self.log.warning(
"Alembic ROP saves 'Face Sets' for Geometry. "
"Are you sure you want this?"
)

View file

@ -3,7 +3,7 @@ import colorbleed.api
class ValidateAlembicInputNode(pyblish.api.InstancePlugin):
"""Validate that the node connected to the output is correct
"""Validate that the node connected to the output is correct.
The connected node cannot be of the following types for Alembic:
- VDB
@ -12,22 +12,24 @@ class ValidateAlembicInputNode(pyblish.api.InstancePlugin):
"""
order = colorbleed.api.ValidateContentsOrder + 0.1
families = ["colorbleed.pointcache"]
families = ["pointcache"]
hosts = ["houdini"]
label = "Validate Input Node (Abc)"
def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError("Primitive types found that are not supported"
"for Alembic output.")
raise RuntimeError(
"Primitive types found that are not supported"
"for Alembic output."
)
@classmethod
def get_invalid(cls, instance):
invalid_prim_types = ["VDB", "Volume"]
node = instance.data["output_node"]
if not hasattr(node, "geometry"):
# In the case someone has explicitly set an Object
# node instead of a SOP node in Geometry context
@ -35,15 +37,16 @@ class ValidateAlembicInputNode(pyblish.api.InstancePlugin):
# export object transforms.
cls.log.warning("No geometry output node found, skipping check..")
return
frame = instance.data.get("startFrame", 0)
geo = node.geometryAtFrame(frame)
invalid = False
for prim_type in invalid_prim_types:
if geo.countPrimType(prim_type) > 0:
cls.log.error("Found a primitive which is of type '%s' !"
% prim_type)
cls.log.error(
"Found a primitive which is of type '%s' !" % prim_type
)
invalid = True
if invalid:

View file

@ -29,8 +29,9 @@ class ValidateAnimationSettings(pyblish.api.InstancePlugin):
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError("Output settings do no match for '%s'" %
instance)
raise RuntimeError(
"Output settings do no match for '%s'" % instance
)
@classmethod
def get_invalid(cls, instance):

View file

@ -1,5 +1,5 @@
import pyblish.api
import colorbleed.api
import openpype.api
class ValidateBypassed(pyblish.api.InstancePlugin):
@ -11,7 +11,7 @@ class ValidateBypassed(pyblish.api.InstancePlugin):
"""
order = colorbleed.api.ValidateContentsOrder - 0.1
order = openpype.api.ValidateContentsOrder - 0.1
families = ["*"]
hosts = ["houdini"]
label = "Validate ROP Bypass"
@ -27,8 +27,8 @@ class ValidateBypassed(pyblish.api.InstancePlugin):
if invalid:
rop = invalid[0]
raise RuntimeError(
"ROP node %s is set to bypass, publishing cannot continue.." %
rop.path()
"ROP node %s is set to bypass, publishing cannot continue.."
% rop.path()
)
@classmethod

View file

@ -6,9 +6,9 @@ class ValidateCameraROP(pyblish.api.InstancePlugin):
"""Validate Camera ROP settings."""
order = openpype.api.ValidateContentsOrder
families = ['colorbleed.camera']
hosts = ['houdini']
label = 'Camera ROP'
families = ["camera"]
hosts = ["houdini"]
label = "Camera ROP"
def process(self, instance):
@ -16,8 +16,10 @@ class ValidateCameraROP(pyblish.api.InstancePlugin):
node = instance[0]
if node.parm("use_sop_path").eval():
raise RuntimeError("Alembic ROP for Camera export should not be "
"set to 'Use Sop Path'. Please disable.")
raise RuntimeError(
"Alembic ROP for Camera export should not be "
"set to 'Use Sop Path'. Please disable."
)
# Get the root and objects parameter of the Alembic ROP node
root = node.parm("root").eval()
@ -35,7 +37,7 @@ class ValidateCameraROP(pyblish.api.InstancePlugin):
raise ValueError("Camera path does not exist: %s" % path)
if camera.type().name() != "cam":
raise ValueError("Object set in Alembic ROP is not a camera: "
"%s (type: %s)" % (camera, camera.type().name()))
raise ValueError(
"Object set in Alembic ROP is not a camera: "
"%s (type: %s)" % (camera, camera.type().name())
)

View file

@ -12,7 +12,7 @@ class ValidateCopOutputNode(pyblish.api.InstancePlugin):
"""
order = pyblish.api.ValidatorOrder
families = ["colorbleed.imagesequence"]
families = ["imagesequence"]
hosts = ["houdini"]
label = "Validate COP Output Node"
@ -20,8 +20,10 @@ class ValidateCopOutputNode(pyblish.api.InstancePlugin):
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError("Output node(s) `%s` are incorrect. "
"See plug-in log for details." % invalid)
raise RuntimeError(
"Output node(s) `%s` are incorrect. "
"See plug-in log for details." % invalid
)
@classmethod
def get_invalid(cls, instance):
@ -32,27 +34,27 @@ class ValidateCopOutputNode(pyblish.api.InstancePlugin):
if output_node is None:
node = instance[0]
cls.log.error("COP Output node in '%s' does not exist. "
"Ensure a valid COP output path is set."
% node.path())
cls.log.error(
"COP Output node in '%s' does not exist. "
"Ensure a valid COP output path is set." % node.path()
)
return [node.path()]
# Output node must be a Sop node.
if not isinstance(output_node, hou.CopNode):
cls.log.error("Output node %s is not a COP node. "
"COP Path must point to a COP node, "
"instead found category type: %s" % (
output_node.path(),
output_node.type().category().name()
)
)
cls.log.error(
"Output node %s is not a COP node. "
"COP Path must point to a COP node, "
"instead found category type: %s"
% (output_node.path(), output_node.type().category().name())
)
return [output_node.path()]
# For the sake of completeness also assert the category type
# is Cop2 to avoid potential edge case scenarios even though
# the isinstance check above should be stricter than this category
assert output_node.type().category().name() == "Cop2", (
"Output node %s is not of category Cop2. This is a bug.." %
output_node.path()
"Output node %s is not of category Cop2. This is a bug.."
% output_node.path()
)

View file

@ -15,24 +15,23 @@ class ValidateFileExtension(pyblish.api.InstancePlugin):
"""
order = pyblish.api.ValidatorOrder
families = ["pointcache",
"camera",
"vdbcache"]
families = ["pointcache", "camera", "vdbcache"]
hosts = ["houdini"]
label = "Output File Extension"
family_extensions = {
"pointcache": ".abc",
"camera": ".abc",
"vdbcache": ".vdb"
"vdbcache": ".vdb",
}
def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError("ROP node has incorrect "
"file extension: %s" % invalid)
raise RuntimeError(
"ROP node has incorrect " "file extension: %s" % invalid
)
@classmethod
def get_invalid(cls, instance):

View file

@ -4,7 +4,7 @@ from openpype.hosts.houdini.api import lib
class ValidateFrameToken(pyblish.api.InstancePlugin):
"""Validate if the unexpanded string contains the frame ('$F') token
"""Validate if the unexpanded string contains the frame ('$F') token.
This validator will *only* check the output parameter of the node if
the Valid Frame Range is not set to 'Render Current Frame'
@ -29,8 +29,9 @@ class ValidateFrameToken(pyblish.api.InstancePlugin):
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError("Output settings do no match for '%s'" %
instance)
raise RuntimeError(
"Output settings do no match for '%s'" % instance
)
@classmethod
def get_invalid(cls, instance):

View file

@ -24,5 +24,7 @@ class ValidateHoudiniCommercialLicense(pyblish.api.InstancePlugin):
license = hou.licenseCategory()
if license != hou.licenseCategoryType.Commercial:
raise RuntimeError("USD Publishing requires a full Commercial "
"license. You are on: %s" % license)
raise RuntimeError(
"USD Publishing requires a full Commercial "
"license. You are on: %s" % license
)

View file

@ -1,23 +1,23 @@
import pyblish.api
import colorbleed.api
import openpype.api
class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin):
"""Validate Create Intermediate Directories is enabled on ROP node."""
order = colorbleed.api.ValidateContentsOrder
families = ['colorbleed.pointcache',
'colorbleed.camera',
'colorbleed.vdbcache']
hosts = ['houdini']
label = 'Create Intermediate Directories Checked'
order = openpype.api.ValidateContentsOrder
families = ["pointcache", "camera", "vdbcache"]
hosts = ["houdini"]
label = "Create Intermediate Directories Checked"
def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError("Found ROP node with Create Intermediate "
"Directories turned off: %s" % invalid)
raise RuntimeError(
"Found ROP node with Create Intermediate "
"Directories turned off: %s" % invalid
)
@classmethod
def get_invalid(cls, instance):
@ -30,5 +30,3 @@ class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin):
result.append(node.path())
return result

View file

@ -10,7 +10,7 @@ def cook_in_range(node, start, end):
node.cook(force=False)
else:
node.cook(force=False, frame_range=(start, start))
def get_errors(node):
"""Get cooking errors.
@ -29,8 +29,8 @@ class ValidateNoErrors(pyblish.api.InstancePlugin):
"""Validate the Instance has no current cooking errors."""
order = openpype.api.ValidateContentsOrder
hosts = ['houdini']
label = 'Validate no errors'
hosts = ["houdini"]
label = "Validate no errors"
def process(self, instance):
@ -45,20 +45,21 @@ class ValidateNoErrors(pyblish.api.InstancePlugin):
for node in validate_nodes:
self.log.debug("Validating for errors: %s" % node.path())
errors = get_errors(node)
if errors:
# If there are current errors, then try an unforced cook
# to see whether the error will disappear.
self.log.debug("Recooking to revalidate error "
"is up to date for: %s" % node.path())
self.log.debug(
"Recooking to revalidate error "
"is up to date for: %s" % node.path()
)
current_frame = hou.intFrame()
start = instance.data.get("startFrame", current_frame)
end = instance.data.get("endFrame", current_frame)
start = instance.data.get("frameStart", current_frame)
end = instance.data.get("frameEnd", current_frame)
cook_in_range(node, start=start, end=end)
# Check for errors again after the forced recook
errors = get_errors(node)
if errors:
self.log.error(errors)
raise RuntimeError("Node has errors: %s" % node.path())

View file

@ -3,7 +3,7 @@ import openpype.api
class ValidatOutputNodeExists(pyblish.api.InstancePlugin):
"""Validate if node attribute Create intermediate Directories is turned on
"""Validate if node attribute Create intermediate Directories is turned on.
Rules:
* The node must have Create intermediate Directories turned on to
@ -13,7 +13,7 @@ class ValidatOutputNodeExists(pyblish.api.InstancePlugin):
order = openpype.api.ValidateContentsOrder
families = ["*"]
hosts = ['houdini']
hosts = ["houdini"]
label = "Output Node Exists"
def process(self, instance):

View file

@ -14,8 +14,7 @@ class ValidateOutputNode(pyblish.api.InstancePlugin):
"""
order = pyblish.api.ValidatorOrder
families = ["pointcache",
"vdbcache"]
families = ["pointcache", "vdbcache"]
hosts = ["houdini"]
label = "Validate Output Node"
@ -23,8 +22,10 @@ class ValidateOutputNode(pyblish.api.InstancePlugin):
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError("Output node(s) `%s` are incorrect. "
"See plug-in log for details." % invalid)
raise RuntimeError(
"Output node(s) `%s` are incorrect. "
"See plug-in log for details." % invalid
)
@classmethod
def get_invalid(cls, instance):
@ -35,39 +36,42 @@ class ValidateOutputNode(pyblish.api.InstancePlugin):
if output_node is None:
node = instance[0]
cls.log.error("SOP Output node in '%s' does not exist. "
"Ensure a valid SOP output path is set."
% node.path())
cls.log.error(
"SOP Output node in '%s' does not exist. "
"Ensure a valid SOP output path is set." % node.path()
)
return [node.path()]
# Output node must be a Sop node.
if not isinstance(output_node, hou.SopNode):
cls.log.error("Output node %s is not a SOP node. "
"SOP Path must point to a SOP node, "
"instead found category type: %s" % (
output_node.path(),
output_node.type().category().name()
)
)
cls.log.error(
"Output node %s is not a SOP node. "
"SOP Path must point to a SOP node, "
"instead found category type: %s"
% (output_node.path(), output_node.type().category().name())
)
return [output_node.path()]
# For the sake of completeness also assert the category type
# is Sop to avoid potential edge case scenarios even though
# the isinstance check above should be stricter than this category
assert output_node.type().category().name() == "Sop", (
"Output node %s is not of category Sop. This is a bug.." %
output_node.path()
"Output node %s is not of category Sop. This is a bug.."
% output_node.path()
)
# Check if output node has incoming connections
if not output_node.inputConnections():
cls.log.error("Output node `%s` has no incoming connections"
% output_node.path())
cls.log.error(
"Output node `%s` has no incoming connections"
% output_node.path()
)
return [output_node.path()]
# Ensure the output node has at least Geometry data
if not output_node.geometry():
cls.log.error("Output node `%s` has no geometry data."
% output_node.path())
cls.log.error(
"Output node `%s` has no geometry data." % output_node.path()
)
return [output_node.path()]

View file

@ -19,8 +19,9 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin):
def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError("See log for details. "
"Invalid nodes: {0}".format(invalid))
raise RuntimeError(
"See log for details. " "Invalid nodes: {0}".format(invalid)
)
@classmethod
def get_invalid(cls, instance):
@ -32,15 +33,19 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin):
rop = instance[0]
build_from_path = rop.parm("build_from_path").eval()
if not build_from_path:
cls.log.debug("Alembic ROP has 'Build from Path' disabled. "
"Validation is ignored..")
cls.log.debug(
"Alembic ROP has 'Build from Path' disabled. "
"Validation is ignored.."
)
return
path_attr = rop.parm("path_attrib").eval()
if not path_attr:
cls.log.error("The Alembic ROP node has no Path Attribute"
"value set, but 'Build Hierarchy from Attribute'"
"is enabled.")
cls.log.error(
"The Alembic ROP node has no Path Attribute"
"value set, but 'Build Hierarchy from Attribute'"
"is enabled."
)
return [rop.path()]
cls.log.debug("Checking for attribute: %s" % path_attr)
@ -54,22 +59,28 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin):
# warning that the check can't be done consistently and skip
# validation.
if len(geo.iterPrims()) == 0:
cls.log.warning("No primitives found on current frame. Validation"
" for primitive hierarchy paths will be skipped,"
" thus can't be validated.")
cls.log.warning(
"No primitives found on current frame. Validation"
" for primitive hierarchy paths will be skipped,"
" thus can't be validated."
)
return
# Check if there are any values for the primitives
attrib = geo.findPrimAttrib(path_attr)
if not attrib:
cls.log.info("Geometry Primitives are missing "
"path attribute: `%s`" % path_attr)
cls.log.info(
"Geometry Primitives are missing "
"path attribute: `%s`" % path_attr
)
return [output.path()]
# Ensure at least a single string value is present
if not attrib.strings():
cls.log.info("Primitive path attribute has no "
"string values: %s" % path_attr)
cls.log.info(
"Primitive path attribute has no "
"string values: %s" % path_attr
)
return [output.path()]
paths = geo.primStringAttribValues(path_attr)
@ -78,8 +89,8 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin):
invalid_prims = [i for i, path in enumerate(paths) if not path]
if invalid_prims:
num_prims = len(geo.iterPrims()) # faster than len(geo.prims())
cls.log.info("Prims have no value for attribute `%s` "
"(%s of %s prims)" % (path_attr,
len(invalid_prims),
num_prims))
cls.log.info(
"Prims have no value for attribute `%s` "
"(%s of %s prims)" % (path_attr, len(invalid_prims), num_prims)
)
return [output.path()]

View file

@ -11,9 +11,9 @@ class ValidateRemotePublishOutNode(pyblish.api.ContextPlugin):
order = pyblish.api.ValidatorOrder - 0.4
families = ["*"]
hosts = ['houdini']
hosts = ["houdini"]
targets = ["deadline"]
label = 'Remote Publish ROP node'
label = "Remote Publish ROP node"
actions = [openpype.api.RepairContextAction]
def process(self, context):
@ -30,14 +30,14 @@ class ValidateRemotePublishOutNode(pyblish.api.ContextPlugin):
assert node.type().name() == "shell", "Must be shell ROP node"
assert node.parm("command").eval() == "", "Must have no command"
assert not node.parm("shellexec").eval(), "Must not execute in shell"
assert node.parm("prerender").eval() == cmd, (
"REMOTE_PUBLISH node does not have correct prerender script."
)
assert node.parm("lprerender").eval() == "python", (
"REMOTE_PUBLISH node prerender script type not set to 'python'"
)
assert (
node.parm("prerender").eval() == cmd
), "REMOTE_PUBLISH node does not have correct prerender script."
assert (
node.parm("lprerender").eval() == "python"
), "REMOTE_PUBLISH node prerender script type not set to 'python'"
@classmethod
def repair(cls, context):
"""(Re)create the node if it fails to pass validation"""
"""(Re)create the node if it fails to pass validation."""
lib.create_remote_publish_node(force=True)

View file

@ -9,9 +9,9 @@ class ValidateRemotePublishEnabled(pyblish.api.ContextPlugin):
order = pyblish.api.ValidatorOrder - 0.39
families = ["*"]
hosts = ['houdini']
hosts = ["houdini"]
targets = ["deadline"]
label = 'Remote Publish ROP enabled'
label = "Remote Publish ROP enabled"
actions = [openpype.api.RepairContextAction]
def process(self, context):
@ -25,7 +25,7 @@ class ValidateRemotePublishEnabled(pyblish.api.ContextPlugin):
@classmethod
def repair(cls, context):
"""(Re)create the node if it fails to pass validation"""
"""(Re)create the node if it fails to pass validation."""
node = hou.node("/out/REMOTE_PUBLISH")
if not node:

View file

@ -14,8 +14,7 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin):
"""
order = pyblish.api.ValidatorOrder
families = ["pointcache",
"vdbcache"]
families = ["pointcache", "vdbcache"]
hosts = ["houdini"]
label = "Validate Output Node"
@ -23,8 +22,10 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin):
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError("Output node(s) `%s` are incorrect. "
"See plug-in log for details." % invalid)
raise RuntimeError(
"Output node(s) `%s` are incorrect. "
"See plug-in log for details." % invalid
)
@classmethod
def get_invalid(cls, instance):
@ -35,29 +36,29 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin):
if output_node is None:
node = instance[0]
cls.log.error("SOP Output node in '%s' does not exist. "
"Ensure a valid SOP output path is set."
% node.path())
cls.log.error(
"SOP Output node in '%s' does not exist. "
"Ensure a valid SOP output path is set." % node.path()
)
return [node.path()]
# Output node must be a Sop node.
if not isinstance(output_node, hou.SopNode):
cls.log.error("Output node %s is not a SOP node. "
"SOP Path must point to a SOP node, "
"instead found category type: %s" % (
output_node.path(),
output_node.type().category().name()
)
)
cls.log.error(
"Output node %s is not a SOP node. "
"SOP Path must point to a SOP node, "
"instead found category type: %s"
% (output_node.path(), output_node.type().category().name())
)
return [output_node.path()]
# For the sake of completeness also assert the category type
# is Sop to avoid potential edge case scenarios even though
# the isinstance check above should be stricter than this category
assert output_node.type().category().name() == "Sop", (
"Output node %s is not of category Sop. This is a bug.." %
output_node.path()
"Output node %s is not of category Sop. This is a bug.."
% output_node.path()
)
# Ensure the node is cooked and succeeds to cook so we can correctly
@ -73,6 +74,7 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin):
# Ensure the output node has at least Geometry data
if not output_node.geometry():
cls.log.error("Output node `%s` has no geometry data."
% output_node.path())
cls.log.error(
"Output node `%s` has no geometry data." % output_node.path()
)
return [output_node.path()]

View file

@ -17,10 +17,7 @@ class ValidateUSDLayerPathBackslashes(pyblish.api.InstancePlugin):
"""
order = pyblish.api.ValidatorOrder
families = ["usdSetDress",
"usdShade",
"usd",
"usdrender"]
families = ["usdSetDress", "usdShade", "usd", "usdrender"]
hosts = ["houdini"]
label = "USD Layer path backslashes"
optional = True
@ -47,5 +44,7 @@ class ValidateUSDLayerPathBackslashes(pyblish.api.InstancePlugin):
invalid.append(layer)
if invalid:
raise RuntimeError("Loaded layers have backslashes. "
"This is invalid for HUSK USD rendering.")
raise RuntimeError(
"Loaded layers have backslashes. "
"This is invalid for HUSK USD rendering."
)

View file

@ -11,7 +11,7 @@ def fullname(o):
module = o.__module__
if module is None or module == str.__module__:
return o.__name__
return module + '.' + o.__name__
return module + "." + o.__name__
class ValidateUsdModel(pyblish.api.InstancePlugin):
@ -32,7 +32,7 @@ class ValidateUsdModel(pyblish.api.InstancePlugin):
UsdRender.Settings,
UsdRender.Product,
UsdRender.Var,
UsdLux.Light
UsdLux.Light,
]
def process(self, instance):
@ -64,6 +64,7 @@ class ValidateUsdShade(ValidateUsdModel):
Disallow Render settings, products, vars and Lux lights.
"""
families = ["usdShade"]
label = "Validate USD Shade"
@ -71,5 +72,5 @@ class ValidateUsdShade(ValidateUsdModel):
UsdRender.Settings,
UsdRender.Product,
UsdRender.Var,
UsdLux.Light
UsdLux.Light,
]

View file

@ -12,7 +12,7 @@ class ValidateUSDOutputNode(pyblish.api.InstancePlugin):
"""
order = pyblish.api.ValidatorOrder
families = ["colorbleed.usd"]
families = ["usd"]
hosts = ["houdini"]
label = "Validate Output Node (USD)"
@ -20,8 +20,10 @@ class ValidateUSDOutputNode(pyblish.api.InstancePlugin):
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError("Output node(s) `%s` are incorrect. "
"See plug-in log for details." % invalid)
raise RuntimeError(
"Output node(s) `%s` are incorrect. "
"See plug-in log for details." % invalid
)
@classmethod
def get_invalid(cls, instance):
@ -32,19 +34,19 @@ class ValidateUSDOutputNode(pyblish.api.InstancePlugin):
if output_node is None:
node = instance[0]
cls.log.error("USD node '%s' LOP path does not exist. "
"Ensure a valid LOP path is set."
% node.path())
cls.log.error(
"USD node '%s' LOP path does not exist. "
"Ensure a valid LOP path is set." % node.path()
)
return [node.path()]
# Output node must be a Sop node.
if not isinstance(output_node, hou.LopNode):
cls.log.error("Output node %s is not a LOP node. "
"LOP Path must point to a LOP node, "
"instead found category type: %s" % (
output_node.path(),
output_node.type().category().name()
)
)
cls.log.error(
"Output node %s is not a LOP node. "
"LOP Path must point to a LOP node, "
"instead found category type: %s"
% (output_node.path(), output_node.type().category().name())
)
return [output_node.path()]

View file

@ -7,7 +7,7 @@ class ValidateUSDRenderProductNames(pyblish.api.InstancePlugin):
"""Validate USD Render Product names are correctly set absolute paths."""
order = pyblish.api.ValidatorOrder
families = ["colorbleed.usdrender"]
families = ["usdrender"]
hosts = ["houdini"]
label = "Validate USD Render Product Names"
optional = True
@ -21,8 +21,9 @@ class ValidateUSDRenderProductNames(pyblish.api.InstancePlugin):
invalid.append("Detected empty output filepath.")
if not os.path.isabs(filepath):
invalid.append("Output file path is not "
"absolute path: %s" % filepath)
invalid.append(
"Output file path is not " "absolute path: %s" % filepath
)
if invalid:
for message in invalid:

View file

@ -41,11 +41,14 @@ class ValidateUsdSetDress(pyblish.api.InstancePlugin):
break
else:
prim_path = node.GetPath()
self.log.error("%s is not referenced geometry." %
prim_path)
self.log.error(
"%s is not referenced geometry." % prim_path
)
invalid.append(node)
if invalid:
raise RuntimeError("SetDress contains local geometry. "
"This is not allowed, it must be an assembly "
"of referenced assets.")
raise RuntimeError(
"SetDress contains local geometry. "
"This is not allowed, it must be an assembly "
"of referenced assets."
)

View file

@ -10,9 +10,9 @@ class ValidateUSDShadeModelExists(pyblish.api.InstancePlugin):
"""Validate the Instance has no current cooking errors."""
order = openpype.api.ValidateContentsOrder
hosts = ['houdini']
hosts = ["houdini"]
families = ["usdShade"]
label = 'USD Shade model exists'
label = "USD Shade model exists"
def process(self, instance):
@ -23,14 +23,19 @@ class ValidateUSDShadeModelExists(pyblish.api.InstancePlugin):
shade_subset = subset.split(".", 1)[0]
model_subset = re.sub("^usdShade", "usdModel", shade_subset)
asset_doc = io.find_one({"name": asset,
"type": "asset"})
asset_doc = io.find_one({"name": asset, "type": "asset"})
if not asset_doc:
raise RuntimeError("Asset does not exist: %s" % asset)
subset_doc = io.find_one({"name": model_subset,
"type": "subset",
"parent": asset_doc["_id"]})
subset_doc = io.find_one(
{
"name": model_subset,
"type": "subset",
"parent": asset_doc["_id"],
}
)
if not subset_doc:
raise RuntimeError("USD Model subset not found: "
"%s (%s)" % (model_subset, asset))
raise RuntimeError(
"USD Model subset not found: "
"%s (%s)" % (model_subset, asset)
)

View file

@ -3,7 +3,6 @@ import re
import pyblish.api
import openpype.api
from avalon import io
import hou
@ -46,15 +45,21 @@ class ValidateUsdShadeWorkspace(pyblish.api.InstancePlugin):
highest = max(highest, other_version)
if version != highest:
raise RuntimeError("Shading Workspace is not the latest version."
" Found %s. Latest is %s." % (version, highest))
raise RuntimeError(
"Shading Workspace is not the latest version."
" Found %s. Latest is %s." % (version, highest)
)
# There were some issues with the editable node not having the right
# configured path. So for now let's assure that is correct to.from
value = ('avalon://`chs("../asset_name")`/'
'usdShade`chs("../model_variantname1")`.usd')
value = (
'avalon://`chs("../asset_name")`/'
'usdShade`chs("../model_variantname1")`.usd'
)
rop_value = rop.parm("lopoutput").rawValue()
if rop_value != value:
raise RuntimeError("Shading Workspace has invalid 'lopoutput'"
" parameter value. The Shading Workspace"
" needs to be reset to its default values.")
raise RuntimeError(
"Shading Workspace has invalid 'lopoutput'"
" parameter value. The Shading Workspace"
" needs to be reset to its default values."
)

View file

@ -3,7 +3,7 @@ import openpype.api
class ValidateVDBInputNode(pyblish.api.InstancePlugin):
"""Validate that the node connected to the output node is of type VDB
"""Validate that the node connected to the output node is of type VDB.
Regardless of the amount of VDBs create the output will need to have an
equal amount of VDBs, points, primitives and vertices
@ -24,8 +24,9 @@ class ValidateVDBInputNode(pyblish.api.InstancePlugin):
def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError("Node connected to the output node is not"
"of type VDB!")
raise RuntimeError(
"Node connected to the output node is not" "of type VDB!"
)
@classmethod
def get_invalid(cls, instance):

View file

@ -4,7 +4,7 @@ import hou
class ValidateVDBOutputNode(pyblish.api.InstancePlugin):
"""Validate that the node connected to the output node is of type VDB
"""Validate that the node connected to the output node is of type VDB.
Regardless of the amount of VDBs create the output will need to have an
equal amount of VDBs, points, primitives and vertices
@ -18,36 +18,41 @@ class ValidateVDBOutputNode(pyblish.api.InstancePlugin):
"""
order = openpype.api.ValidateContentsOrder + 0.1
families = ["colorbleed.vdbcache"]
families = ["vdbcache"]
hosts = ["houdini"]
label = "Validate Output Node (VDB)"
def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError("Node connected to the output node is not"
" of type VDB!")
raise RuntimeError(
"Node connected to the output node is not" " of type VDB!"
)
@classmethod
def get_invalid(cls, instance):
node = instance.data["output_node"]
if node is None:
cls.log.error("SOP path is not correctly set on "
"ROP node '%s'." % instance[0].path())
cls.log.error(
"SOP path is not correctly set on "
"ROP node '%s'." % instance[0].path()
)
return [instance]
frame = instance.data.get("startFrame", 0)
frame = instance.data.get("frameStart", 0)
geometry = node.geometryAtFrame(frame)
if geometry is None:
# No geometry data on this node, maybe the node hasn't cooked?
cls.log.error("SOP node has no geometry data. "
"Is it cooked? %s" % node.path())
cls.log.error(
"SOP node has no geometry data. "
"Is it cooked? %s" % node.path()
)
return [node]
prims = geometry.prims()
nr_of_prims = len(prims)
# All primitives must be hou.VDB
invalid_prim = False
for prim in prims: