Merge branch 'develop' into feature/global-collect-audio-plugin

This commit is contained in:
Jakub Jezek 2022-09-08 15:39:40 +02:00
commit 7668fb830c
No known key found for this signature in database
GPG key ID: 730D7C02726179A7
578 changed files with 42504 additions and 4815 deletions

View file

@ -2,10 +2,10 @@
"""
from openpype.api import Logger
from openpype.lib import Logger
from openpype.pipeline import load
log = Logger().get_logger(__name__)
log = Logger.get_logger(__name__)
class SetFrameRangeLoader(load.LoaderPlugin):

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<error id="main">
<title>Shot/Asset mame</title>
<description>
## Invalid Shot/Asset name in subset
Following Node with name `{node_name}`:
Is in context of `{correct_name}` but Node _asset_ knob is set as `{wrong_name}`.
### How to repair?
1. Either use Repair or Select button.
2. If you chose Select then rename asset knob to correct name.
3. Hit Reload button on the publisher.
</description>
</error>
</root>

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<error id="multiple_outputs">
<title>Found multiple outputs</title>
<description>
## Invalid output amount
Backdrop is having more than one outgoing connections.
### How to repair?
1. Use button `Center node in node graph` and navigate to the backdrop.
2. Reorganize nodes the way only one outgoing connection is present.
3. Hit reload button on the publisher.
</description>
<detail>
### How could this happen?
More than one node, which are found above the backdrop, are linked downstream or more output connections from a node also linked downstream.
</detail>
</error>
<error id="no_nodes">
<title>Empty backdrop</title>
<description>
## Invalid empty backdrop
Backdrop is empty and no nodes are found above it.
### How to repair?
1. Use button `Center node in node graph` and navigate to the backdrop.
2. Add any node above it or delete it.
3. Hit reload button on the publisher.
</description>
</error>
</root>

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<error id="multiple_outputs">
<title>Found multiple outputs</title>
<description>
## Invalid amount of Output nodes
Group node `{node_name}` is having more than one Output node.
### How to repair?
1. Use button `Open Group`.
2. Remove redundant Output node.
3. Hit reload button on the publisher.
</description>
<detail>
### How could this happen?
Perhaps you had created exciently more than one Output node.
</detail>
</error>
<error id="no_inputs">
<title>Missing Input nodes</title>
<description>
## Missing Input nodes
Make sure there is at least one connected Input node inside the group node with name `{node_name}`
### How to repair?
1. Use button `Open Group`.
2. Add at least one Input node and connect to other nodes.
3. Hit reload button on the publisher.
</description>
</error>
</root>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<error id="main">
<title>Knobs value</title>
<description>
## Invalid node's knobs values
Following node knobs needs to be repaired:
{invalid_items}
### How to repair?
1. Use Repair button.
2. Hit Reload button on the publisher.
</description>
</error>
</root>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<error id="main">
<title>Output format</title>
<description>
## Invalid format setting
Either the Reformat node inside of the render group is missing or the Reformat node output format knob is not set to `root.format`.
### How to repair?
1. Use Repair button.
2. Hit Reload button on the publisher.
</description>
</error>
</root>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<error id="main">
<title>Proxy mode</title>
<description>
## Invalid proxy mode value
Nuke is set to use Proxy. This is not supported by publisher.
### How to repair?
1. Use Repair button.
2. Hit Reload button on the publisher.
</description>
</error>
</root>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<error id="main">
<title>Rendered Frames</title>
<description>
## Missing Rendered Frames
Render node "{node_name}" is set to "Use existing frames", but frames are missing.
### How to repair?
1. Use Repair button.
2. Set different target.
2. Hit Reload button on the publisher.
</description>
</error>
</root>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<error id="main">
<title>Script attributes</title>
<description>
## Invalid Script attributes
Following script root attributes need to be fixed:
{failed_attributes}
### How to repair?
1. Use Repair.
2. Hit Reload button on the publisher.
</description>
</error>
</root>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<error id="main">
<title>Knobs values</title>
<description>
## Invalid node's knobs values
Following write node knobs needs to be repaired:
{xml_msg}
### How to repair?
1. Use Repair button.
2. Hit Reload button on the publisher.
</description>
</error>
</root>

View file

@ -8,6 +8,7 @@ from openpype.hosts.nuke.api.lib import (
add_publish_knob,
get_avalon_knob_data
)
from openpype.pipeline import KnownPublishError
class CollectWorkfile(pyblish.api.ContextPlugin):
@ -22,6 +23,12 @@ class CollectWorkfile(pyblish.api.ContextPlugin):
current_file = os.path.normpath(nuke.root().name())
if current_file.lower() == "root":
raise KnownPublishError(
"Workfile is not correct file name. \n"
"Use workfile tool to manage the name correctly."
)
knob_data = get_avalon_knob_data(root)
add_publish_knob(root)

View file

@ -3,20 +3,20 @@
from __future__ import absolute_import
import nuke
import pyblish.api
import openpype.api
from openpype.hosts.nuke.api.lib import (
recreate_instance,
reset_selection,
select_nodes
import openpype.hosts.nuke.api.lib as nlib
import openpype.hosts.nuke.api as nuke_api
from openpype.pipeline.publish import (
ValidateContentsOrder,
PublishXmlValidationError,
)
class SelectInvalidInstances(pyblish.api.Action):
"""Select invalid instances in Outliner."""
label = "Select Instances"
label = "Select"
icon = "briefcase"
on = "failed"
@ -39,6 +39,7 @@ class SelectInvalidInstances(pyblish.api.Action):
instances = pyblish.api.instances_by_plugin(failed, plugin)
if instances:
self.deselect()
self.log.info(
"Selecting invalid nodes: %s" % ", ".join(
[str(x) for x in instances]
@ -50,12 +51,12 @@ class SelectInvalidInstances(pyblish.api.Action):
self.deselect()
def select(self, instances):
select_nodes(
nlib.select_nodes(
[nuke.toNode(str(x)) for x in instances]
)
def deselect(self):
reset_selection()
nlib.reset_selection()
class RepairSelectInvalidInstances(pyblish.api.Action):
@ -85,12 +86,12 @@ class RepairSelectInvalidInstances(pyblish.api.Action):
context_asset = context.data["assetEntity"]["name"]
for instance in instances:
origin_node = instance[0]
recreate_instance(
nuke_api.lib.recreate_instance(
origin_node, avalon_data={"asset": context_asset}
)
class ValidateInstanceInContext(pyblish.api.InstancePlugin):
class ValidateCorrectAssetName(pyblish.api.InstancePlugin):
"""Validator to check if instance asset match context asset.
When working in per-shot style you always publish data in context of
@ -99,15 +100,31 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin):
Action on this validator will select invalid instances in Outliner.
"""
order = openpype.api.ValidateContentsOrder
label = "Instance in same Context"
order = ValidateContentsOrder
label = "Validate correct asset name"
hosts = ["nuke"]
actions = [SelectInvalidInstances, RepairSelectInvalidInstances]
actions = [
SelectInvalidInstances,
RepairSelectInvalidInstances
]
optional = True
def process(self, instance):
asset = instance.data.get("asset")
context_asset = instance.context.data["assetEntity"]["name"]
msg = "{} has asset {}".format(instance.name, asset)
assert asset == context_asset, msg
msg = (
"Instance `{}` has wrong shot/asset name:\n"
"Correct: `{}` | Wrong: `{}`").format(
instance.name, asset, context_asset)
self.log.debug(msg)
if asset != context_asset:
raise PublishXmlValidationError(
self, msg, formatting_data={
"node_name": instance[0]["name"].value(),
"wrong_name": asset,
"correct_name": context_asset
}
)

View file

@ -1,6 +1,7 @@
import nuke
import pyblish
from openpype.hosts.nuke.api.lib import maintained_selection
from openpype.pipeline import PublishXmlValidationError
class SelectCenterInNodeGraph(pyblish.api.Action):
@ -47,8 +48,9 @@ class SelectCenterInNodeGraph(pyblish.api.Action):
@pyblish.api.log
class ValidateBackdrop(pyblish.api.InstancePlugin):
"""Validate amount of nodes on backdrop node in case user
forgotten to add nodes above the publishing backdrop node"""
""" Validate amount of nodes on backdrop node in case user
forgoten to add nodes above the publishing backdrop node.
"""
order = pyblish.api.ValidatorOrder
optional = True
@ -63,8 +65,25 @@ class ValidateBackdrop(pyblish.api.InstancePlugin):
msg_multiple_outputs = (
"Only one outcoming connection from "
"\"{}\" is allowed").format(instance.data["name"])
assert len(connections_out.keys()) <= 1, msg_multiple_outputs
msg_no_content = "No content on backdrop node: \"{}\"".format(
if len(connections_out.keys()) > 1:
raise PublishXmlValidationError(
self,
msg_multiple_outputs,
"multiple_outputs"
)
msg_no_nodes = "No content on backdrop node: \"{}\"".format(
instance.data["name"])
assert len(instance) > 1, msg_no_content
self.log.debug(
"Amount of nodes on instance: {}".format(
len(instance))
)
if len(instance) == 1:
raise PublishXmlValidationError(
self,
msg_no_nodes,
"no_nodes"
)

View file

@ -1,6 +1,7 @@
import nuke
import pyblish
from openpype.hosts.nuke.api.lib import maintained_selection
from openpype.pipeline import PublishXmlValidationError
from openpype.hosts.nuke.api import maintained_selection
import nuke
class OpenFailedGroupNode(pyblish.api.Action):
@ -8,7 +9,7 @@ class OpenFailedGroupNode(pyblish.api.Action):
Centering failed instance node in node grap
"""
label = "Open Gizmo in Node Graph"
label = "Open Group"
icon = "wrench"
on = "failed"
@ -48,11 +49,23 @@ class ValidateGizmo(pyblish.api.InstancePlugin):
with grpn:
connections_out = nuke.allNodes('Output')
msg_multiple_outputs = "Only one outcoming connection from "
"\"{}\" is allowed".format(instance.data["name"])
assert len(connections_out) <= 1, msg_multiple_outputs
msg_multiple_outputs = (
"Only one outcoming connection from "
"\"{}\" is allowed").format(instance.data["name"])
if len(connections_out) > 1:
raise PublishXmlValidationError(
self, msg_multiple_outputs, "multiple_outputs",
{"node_name": grpn["name"].value()}
)
connections_in = nuke.allNodes('Input')
msg_missing_inputs = "At least one Input node has to be used in: "
"\"{}\"".format(instance.data["name"])
assert len(connections_in) >= 1, msg_missing_inputs
msg_missing_inputs = (
"At least one Input node has to be inside Group: "
"\"{}\"").format(instance.data["name"])
if len(connections_in) == 0:
raise PublishXmlValidationError(
self, msg_missing_inputs, "no_inputs",
{"node_name": grpn["name"].value()}
)

View file

@ -1,7 +1,11 @@
import nuke
import six
import pyblish.api
import openpype.api
from openpype.pipeline.publish import (
RepairContextAction,
PublishXmlValidationError,
)
class ValidateKnobs(pyblish.api.ContextPlugin):
@ -23,15 +27,25 @@ class ValidateKnobs(pyblish.api.ContextPlugin):
order = pyblish.api.ValidatorOrder
label = "Validate Knobs"
hosts = ["nuke"]
actions = [openpype.api.RepairContextAction]
actions = [RepairContextAction]
optional = True
def process(self, context):
invalid = self.get_invalid(context, compute=True)
if invalid:
raise RuntimeError(
"Found knobs with invalid values:\n{}".format(invalid)
invalid_items = [
(
"Node __{node_name}__ with knob _{label}_ "
"expecting _{expected}_, "
"but is set to _{current}_"
).format(**i)
for i in invalid
]
raise PublishXmlValidationError(
self,
"Found knobs with invalid values:\n{}".format(invalid),
formatting_data={
"invalid_items": "\n".join(invalid_items)}
)
@classmethod
@ -54,15 +68,24 @@ class ValidateKnobs(pyblish.api.ContextPlugin):
# Filter families.
families = [instance.data["family"]]
families += instance.data.get("families", [])
families = list(set(families) & set(cls.knobs.keys()))
if not families:
continue
# Get all knobs to validate.
knobs = {}
for family in families:
# check if dot in family
if "." in family:
family = family.split(".")[0]
# avoid families not in settings
if family not in cls.knobs:
continue
# get presets of knobs
for preset in cls.knobs[family]:
knobs.update({preset: cls.knobs[family][preset]})
knobs[preset] = cls.knobs[family][preset]
# Get invalid knobs.
nodes = []
@ -71,8 +94,7 @@ class ValidateKnobs(pyblish.api.ContextPlugin):
nodes.append(node)
if node.Class() == "Group":
node.begin()
for i in nuke.allNodes():
nodes.append(i)
nodes.extend(iter(nuke.allNodes()))
node.end()
for node in nodes:
@ -84,6 +106,7 @@ class ValidateKnobs(pyblish.api.ContextPlugin):
if node[knob].value() != expected:
invalid_knobs.append(
{
"node_name": node.name(),
"knob": node[knob],
"name": node[knob].name(),
"label": node[knob].label(),
@ -99,7 +122,9 @@ class ValidateKnobs(pyblish.api.ContextPlugin):
def repair(cls, instance):
invalid = cls.get_invalid(instance)
for data in invalid:
if isinstance(data["expected"], unicode):
# TODO: will need to improve type definitions
# with the new settings for knob types
if isinstance(data["expected"], six.text_type):
data["knob"].setValue(str(data["expected"]))
continue

View file

@ -1,43 +1,9 @@
import nuke
import pyblish.api
class RepairWriteResolutionDifference(pyblish.api.Action):
label = "Repair"
icon = "wrench"
on = "failed"
def process(self, context, plugin):
# Get the errored instances
failed = []
for result in context.data["results"]:
if (result["error"] is not None and result["instance"] is not None
and result["instance"] not in failed):
failed.append(result["instance"])
# Apply pyblish.logic to get the instances for the plug-in
instances = pyblish.api.instances_by_plugin(failed, plugin)
for instance in instances:
reformat = instance[0].dependencies()[0]
if reformat.Class() != "Reformat":
reformat = nuke.nodes.Reformat(inputs=[instance[0].input(0)])
xpos = instance[0].xpos()
ypos = instance[0].ypos() - 26
dependent_ypos = instance[0].dependencies()[0].ypos()
if (instance[0].ypos() - dependent_ypos) <= 51:
xpos += 110
reformat.setXYpos(xpos, ypos)
instance[0].setInput(0, reformat)
reformat["resize"].setValue("none")
from openpype.hosts.nuke.api import maintained_selection
from openpype.pipeline import PublishXmlValidationError
from openpype.pipeline.publish import RepairAction
import nuke
class ValidateOutputResolution(pyblish.api.InstancePlugin):
@ -52,27 +18,75 @@ class ValidateOutputResolution(pyblish.api.InstancePlugin):
families = ["render", "render.local", "render.farm"]
label = "Write Resolution"
hosts = ["nuke"]
actions = [RepairWriteResolutionDifference]
actions = [RepairAction]
missing_msg = "Missing Reformat node in render group node"
resolution_msg = "Reformat is set to wrong format"
def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:
raise PublishXmlValidationError(self, invalid)
# Skip bounding box check if a reformat node exists.
if instance[0].dependencies()[0].Class() == "Reformat":
return
@classmethod
def get_reformat(cls, instance):
reformat = None
for inode in instance:
if inode.Class() != "Reformat":
continue
reformat = inode
msg = "Bounding box is outside the format."
assert self.check_resolution(instance), msg
return reformat
def check_resolution(self, instance):
node = instance[0]
@classmethod
def get_invalid(cls, instance):
def _check_resolution(instance, reformat):
root_width = instance.data["resolutionWidth"]
root_height = instance.data["resolutionHeight"]
root_width = instance.data["resolutionWidth"]
root_height = instance.data["resolutionHeight"]
write_width = reformat.format().width()
write_height = reformat.format().height()
write_width = node.format().width()
write_height = node.format().height()
if (root_width != write_width) or (root_height != write_height):
return None
else:
return True
if (root_width != write_width) or (root_height != write_height):
return None
else:
return True
# check if reformat is in render node
reformat = cls.get_reformat(instance)
if not reformat:
return cls.missing_msg
# check if reformat is set to correct root format
correct_format = _check_resolution(instance, reformat)
if not correct_format:
return cls.resolution_msg
@classmethod
def repair(cls, instance):
invalid = cls.get_invalid(instance)
grp_node = instance[0]
if cls.missing_msg == invalid:
# make sure we are inside of the group node
with grp_node:
# find input node and select it
_input = None
for inode in instance:
if inode.Class() != "Input":
continue
_input = inode
# add reformat node under it
with maintained_selection():
_input['selected'].setValue(True)
_rfn = nuke.createNode("Reformat", "name Reformat01")
_rfn["resize"].setValue(0)
_rfn["black_outside"].setValue(1)
cls.log.info("I am adding reformat node")
if cls.resolution_msg == invalid:
reformat = cls.get_reformat(instance)
reformat["format"].setValue(nuke.root()["format"].value())
cls.log.info("I am fixing reformat to root.format")

View file

@ -1,5 +1,6 @@
import pyblish
import nuke
from openpype.pipeline import PublishXmlValidationError
class FixProxyMode(pyblish.api.Action):
@ -7,7 +8,7 @@ class FixProxyMode(pyblish.api.Action):
Togger off proxy switch OFF
"""
label = "Proxy toggle to OFF"
label = "Repair"
icon = "wrench"
on = "failed"
@ -30,4 +31,7 @@ class ValidateProxyMode(pyblish.api.ContextPlugin):
rootNode = nuke.root()
isProxy = rootNode["proxy"].value()
assert not isProxy, "Proxy mode should be toggled OFF"
if isProxy:
raise PublishXmlValidationError(
self, "Proxy mode should be toggled OFF"
)

View file

@ -1,7 +1,7 @@
import os
import pyblish.api
from openpype.api import ValidationException
import clique
from openpype.pipeline import PublishXmlValidationError
@pyblish.api.log
@ -36,7 +36,7 @@ class RepairActionBase(pyblish.api.Action):
class RepairCollectionActionToLocal(RepairActionBase):
label = "Repair > rerender with `Local` machine"
label = "Repair - rerender with \"Local\""
def process(self, context, plugin):
instances = self.get_instance(context, plugin)
@ -44,7 +44,7 @@ class RepairCollectionActionToLocal(RepairActionBase):
class RepairCollectionActionToFarm(RepairActionBase):
label = "Repair > rerender `On farm` with remote machines"
label = "Repair - rerender with \"On farm\""
def process(self, context, plugin):
instances = self.get_instance(context, plugin)
@ -63,6 +63,10 @@ class ValidateRenderedFrames(pyblish.api.InstancePlugin):
def process(self, instance):
f_data = {
"node_name": instance[0]["name"].value()
}
for repre in instance.data["representations"]:
if not repre.get("files"):
@ -71,7 +75,8 @@ class ValidateRenderedFrames(pyblish.api.InstancePlugin):
"Check properties of write node (group) and"
"select 'Local' option in 'Publish' dropdown.")
self.log.error(msg)
raise ValidationException(msg)
raise PublishXmlValidationError(
self, msg, formatting_data=f_data)
if isinstance(repre["files"], str):
return
@ -82,21 +87,23 @@ class ValidateRenderedFrames(pyblish.api.InstancePlugin):
collection = collections[0]
fstartH = instance.data["frameStartHandle"]
fendH = instance.data["frameEndHandle"]
f_start_h = instance.data["frameStartHandle"]
f_end_h = instance.data["frameEndHandle"]
frame_length = int(fendH - fstartH + 1)
frame_length = int(f_end_h - f_start_h + 1)
if frame_length != 1:
if len(collections) != 1:
msg = "There are multiple collections in the folder"
self.log.error(msg)
raise ValidationException(msg)
raise PublishXmlValidationError(
self, msg, formatting_data=f_data)
if not collection.is_contiguous():
msg = "Some frames appear to be missing"
self.log.error(msg)
raise ValidationException(msg)
raise PublishXmlValidationError(
self, msg, formatting_data=f_data)
collected_frames_len = len(collection.indexes)
coll_start = min(collection.indexes)
@ -105,7 +112,8 @@ class ValidateRenderedFrames(pyblish.api.InstancePlugin):
self.log.info("frame_length: {}".format(frame_length))
self.log.info("collected_frames_len: {}".format(
collected_frames_len))
self.log.info("fstartH-fendH: {}-{}".format(fstartH, fendH))
self.log.info("f_start_h-f_end_h: {}-{}".format(
f_start_h, f_end_h))
self.log.info(
"coll_start-coll_end: {}-{}".format(coll_start, coll_end))
@ -116,13 +124,19 @@ class ValidateRenderedFrames(pyblish.api.InstancePlugin):
if ("slate" in instance.data["families"]) \
and (frame_length != collected_frames_len):
collected_frames_len -= 1
fstartH += 1
f_start_h += 1
assert ((collected_frames_len >= frame_length)
and (coll_start <= fstartH)
and (coll_end >= fendH)), (
"{} missing frames. Use repair to render all frames"
).format(__name__)
if (
collected_frames_len != frame_length
and coll_start <= f_start_h
and coll_end >= f_end_h
):
raise PublishXmlValidationError(
self, (
"{} missing frames. Use repair to "
"render all frames"
).format(__name__), formatting_data=f_data
)
instance.data["collection"] = collection

View file

@ -1,156 +0,0 @@
import pyblish.api
from openpype.client import get_project, get_asset_by_id, get_asset_by_name
from openpype.pipeline import legacy_io
@pyblish.api.log
class ValidateScript(pyblish.api.InstancePlugin):
""" Validates file output. """
order = pyblish.api.ValidatorOrder + 0.1
families = ["workfile"]
label = "Check script settings"
hosts = ["nuke"]
optional = True
def process(self, instance):
ctx_data = instance.context.data
project_name = legacy_io.active_project()
asset_name = ctx_data["asset"]
# TODO repace query with using 'instance.data["assetEntity"]'
asset = get_asset_by_name(project_name, asset_name)
asset_data = asset["data"]
# These attributes will be checked
attributes = [
"fps",
"frameStart",
"frameEnd",
"resolutionWidth",
"resolutionHeight",
"handleStart",
"handleEnd"
]
# Value of these attributes can be found on parents
hierarchical_attributes = [
"fps",
"resolutionWidth",
"resolutionHeight",
"pixelAspect",
"handleStart",
"handleEnd"
]
missing_attributes = []
asset_attributes = {}
for attr in attributes:
if attr in asset_data:
asset_attributes[attr] = asset_data[attr]
elif attr in hierarchical_attributes:
# TODO this should be probably removed
# Hierarchical attributes is not a thing since Pype 2?
# Try to find attribute on parent
parent_id = asset['parent']
parent_type = "project"
if asset_data['visualParent'] is not None:
parent_type = "asset"
parent_id = asset_data['visualParent']
value = self.check_parent_hierarchical(
project_name, parent_type, parent_id, attr
)
if value is None:
missing_attributes.append(attr)
else:
asset_attributes[attr] = value
else:
missing_attributes.append(attr)
# Raise error if attributes weren't found on asset in database
if len(missing_attributes) > 0:
atr = ", ".join(missing_attributes)
msg = 'Missing attributes "{}" in asset "{}"'
message = msg.format(atr, asset_name)
raise ValueError(message)
# Get handles from database, Default is 0 (if not found)
handle_start = 0
handle_end = 0
if "handleStart" in asset_attributes:
handle_start = asset_attributes["handleStart"]
if "handleEnd" in asset_attributes:
handle_end = asset_attributes["handleEnd"]
asset_attributes["fps"] = float("{0:.4f}".format(
asset_attributes["fps"]))
# Get values from nukescript
script_attributes = {
"handleStart": ctx_data["handleStart"],
"handleEnd": ctx_data["handleEnd"],
"fps": float("{0:.4f}".format(ctx_data["fps"])),
"frameStart": ctx_data["frameStart"],
"frameEnd": ctx_data["frameEnd"],
"resolutionWidth": ctx_data["resolutionWidth"],
"resolutionHeight": ctx_data["resolutionHeight"],
"pixelAspect": ctx_data["pixelAspect"]
}
# Compare asset's values Nukescript X Database
not_matching = []
for attr in attributes:
self.log.debug("asset vs script attribute \"{}\": {}, {}".format(
attr, asset_attributes[attr], script_attributes[attr])
)
if asset_attributes[attr] != script_attributes[attr]:
not_matching.append(attr)
# Raise error if not matching
if len(not_matching) > 0:
msg = "Attributes '{}' are not set correctly"
# Alert user that handles are set if Frame start/end not match
if (
(("frameStart" in not_matching) or ("frameEnd" in not_matching)) and
((handle_start > 0) or (handle_end > 0))
):
msg += " (`handle_start` are set to {})".format(handle_start)
msg += " (`handle_end` are set to {})".format(handle_end)
message = msg.format(", ".join(not_matching))
raise ValueError(message)
def check_parent_hierarchical(
self, project_name, parent_type, parent_id, attr
):
if parent_id is None:
return None
doc = None
if parent_type == "project":
doc = get_project(project_name)
elif parent_type == "asset":
doc = get_asset_by_id(project_name, parent_id)
if not doc:
return None
doc_data = doc["data"]
if attr in doc_data:
self.log.info(attr)
return doc_data[attr]
if parent_type == "project":
return None
parent_id = doc_data.get("visualParent")
new_parent_type = "asset"
if parent_id is None:
parent_id = doc["parent"]
new_parent_type = "project"
return self.check_parent_hierarchical(
project_name, new_parent_type, parent_id, attr
)

View file

@ -0,0 +1,127 @@
from pprint import pformat
import pyblish.api
from openpype.pipeline import PublishXmlValidationError
from openpype.pipeline.publish import RepairAction
from openpype.hosts.nuke.api.lib import (
get_avalon_knob_data,
WorkfileSettings
)
import nuke
@pyblish.api.log
class ValidateScriptAttributes(pyblish.api.InstancePlugin):
""" Validates file output. """
order = pyblish.api.ValidatorOrder + 0.1
families = ["workfile"]
label = "Validatte script attributes"
hosts = ["nuke"]
optional = True
actions = [RepairAction]
def process(self, instance):
root = nuke.root()
knob_data = get_avalon_knob_data(root)
asset = instance.data["assetEntity"]
# get asset data frame values
frame_start = asset["data"]["frameStart"]
frame_end = asset["data"]["frameEnd"]
handle_start = asset["data"]["handleStart"]
handle_end = asset["data"]["handleEnd"]
# These attributes will be checked
attributes = [
"fps",
"frameStart",
"frameEnd",
"resolutionWidth",
"resolutionHeight",
"handleStart",
"handleEnd"
]
# get only defined attributes from asset data
asset_attributes = {
attr: asset["data"][attr]
for attr in attributes
if attr in asset["data"]
}
# fix float to max 4 digints (only for evaluating)
fps_data = float("{0:.4f}".format(
asset_attributes["fps"]))
# fix frame values to include handles
asset_attributes.update({
"frameStart": frame_start - handle_start,
"frameEnd": frame_end + handle_end,
"fps": fps_data
})
self.log.debug(pformat(
asset_attributes
))
# Get format
_format = root["format"].value()
# Get values from nukescript
script_attributes = {
"handleStart": int(knob_data["handleStart"]),
"handleEnd": int(knob_data["handleEnd"]),
"fps": float("{0:.4f}".format(root['fps'].value())),
"frameStart": int(root["first_frame"].getValue()),
"frameEnd": int(root["last_frame"].getValue()),
"resolutionWidth": _format.width(),
"resolutionHeight": _format.height(),
"pixelAspect": _format.pixelAspect()
}
self.log.debug(pformat(
script_attributes
))
# Compare asset's values Nukescript X Database
not_matching = []
for attr in attributes:
self.log.debug(
"Asset vs Script attribute \"{}\": {}, {}".format(
attr,
asset_attributes[attr],
script_attributes[attr]
)
)
if asset_attributes[attr] != script_attributes[attr]:
not_matching.append({
"name": attr,
"expected": asset_attributes[attr],
"actual": script_attributes[attr]
})
# Raise error if not matching
if not_matching:
msg = "Following attributes are not set correctly: \n{}"
attrs_wrong_str = "\n".join([
(
"`{0}` is set to `{1}`, "
"but should be set to `{2}`"
).format(at["name"], at["actual"], at["expected"])
for at in not_matching
])
attrs_wrong_html = "<br/>".join([
(
"-- __{0}__ is set to __{1}__, "
"but should be set to __{2}__"
).format(at["name"], at["actual"], at["expected"])
for at in not_matching
])
raise PublishXmlValidationError(
self, msg.format(attrs_wrong_str),
formatting_data={
"failed_attributes": attrs_wrong_html
}
)
@classmethod
def repair(cls, instance):
cls.log.debug("__ repairing instance: {}".format(instance))
WorkfileSettings().set_context_settings()

View file

@ -3,8 +3,9 @@ import toml
import nuke
import pyblish.api
import openpype.api
from openpype.pipeline import discover_creator_plugins
from openpype.pipeline.publish import RepairAction
from openpype.hosts.nuke.api.lib import get_avalon_knob_data
@ -16,7 +17,7 @@ class ValidateWriteLegacy(pyblish.api.InstancePlugin):
families = ["write"]
label = "Validate Write Legacy"
hosts = ["nuke"]
actions = [openpype.api.RepairAction]
actions = [RepairAction]
def process(self, instance):
node = instance[0]

View file

@ -1,10 +1,11 @@
import os
import pyblish.api
import openpype.utils
from openpype.pipeline.publish import get_errored_instances_from_context
from openpype.hosts.nuke.api.lib import (
get_write_node_template_attr,
get_node_path
set_node_knobs_from_settings,
color_gui_to_int
)
from openpype.pipeline import PublishXmlValidationError
@pyblish.api.log
@ -14,18 +15,29 @@ class RepairNukeWriteNodeAction(pyblish.api.Action):
icon = "wrench"
def process(self, context, plugin):
instances = openpype.utils.filter_instances(context, plugin)
instances = get_errored_instances_from_context(context)
for instance in instances:
node = instance[1]
correct_data = get_write_node_template_attr(node)
for k, v in correct_data.items():
node[k].setValue(v)
write_group_node = instance[0]
# get write node from inside of group
write_node = None
for x in instance:
if x.Class() == "Write":
write_node = x
correct_data = get_write_node_template_attr(write_group_node)
set_node_knobs_from_settings(write_node, correct_data["knobs"])
self.log.info("Node attributes were fixed")
class ValidateNukeWriteNode(pyblish.api.InstancePlugin):
""" Validates file output. """
""" Validate Write node's knobs.
Compare knobs on write node inside the render group
with settings. At the moment supporting only `file` knob.
"""
order = pyblish.api.ValidatorOrder
optional = True
@ -35,38 +47,72 @@ class ValidateNukeWriteNode(pyblish.api.InstancePlugin):
hosts = ["nuke"]
def process(self, instance):
write_group_node = instance[0]
node = instance[1]
correct_data = get_write_node_template_attr(node)
# get write node from inside of group
write_node = None
for x in instance:
if x.Class() == "Write":
write_node = x
if write_node is None:
return
correct_data = get_write_node_template_attr(write_group_node)
if correct_data:
check_knobs = correct_data["knobs"]
else:
return
check = []
for k, v in correct_data.items():
if k is 'file':
padding = len(v.split('#'))
ref_path = get_node_path(v, padding)
n_path = get_node_path(node[k].value(), padding)
isnt = False
for i, p in enumerate(ref_path):
if str(n_path[i]) not in str(p):
if not isnt:
isnt = True
else:
continue
if isnt:
check.append([k, v, node[k].value()])
self.log.debug("__ write_node: {}".format(
write_node
))
for knob_data in check_knobs:
key = knob_data["name"]
value = knob_data["value"]
node_value = write_node[key].value()
# fix type differences
if type(node_value) in (int, float):
if isinstance(value, list):
value = color_gui_to_int(value)
else:
value = float(value)
node_value = float(node_value)
else:
if str(node[k].value()) not in str(v):
check.append([k, v, node[k].value()])
value = str(value)
node_value = str(node_value)
self.log.debug("__ key: {} | value: {}".format(
key, value
))
if (
node_value != value
and key != "file"
and key != "tile_color"
):
check.append([key, value, write_node[key].value()])
self.log.info(check)
msg = "Node's attribute `{0}` is not correct!\n" \
"\nCorrect: `{1}` \n\nWrong: `{2}` \n\n"
if check:
print_msg = ""
for item in check:
print_msg += msg.format(item[0], item[1], item[2])
print_msg += "`RMB` click to the validator and `A` to fix!"
self._make_error(check)
assert not check, print_msg
def _make_error(self, check):
# sourcery skip: merge-assign-and-aug-assign, move-assign-in-block
dbg_msg = "Write node's knobs values are not correct!\n"
msg_add = "Knob '{0}' > Correct: `{1}` > Wrong: `{2}`"
details = [
msg_add.format(item[0], item[1], item[2])
for item in check
]
xml_msg = "<br/>".join(details)
dbg_msg += "\n\t".join(details)
raise PublishXmlValidationError(
self, dbg_msg, formatting_data={"xml_msg": xml_msg}
)