diff --git a/openpype/hosts/nuke/api/__init__.py b/openpype/hosts/nuke/api/__init__.py
index b126de4ceb..962f31c177 100644
--- a/openpype/hosts/nuke/api/__init__.py
+++ b/openpype/hosts/nuke/api/__init__.py
@@ -26,8 +26,8 @@ from .lib import (
maintained_selection,
reset_selection,
get_view_process_node,
- duplicate_node
-
+ duplicate_node,
+ convert_knob_value_to_correct_type
)
from .utils import (
@@ -59,6 +59,7 @@ __all__ = (
"reset_selection",
"get_view_process_node",
"duplicate_node",
+ "convert_knob_value_to_correct_type",
"colorspace_exists_on_node",
"get_colorspace_list"
diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py
index a53d932db1..6bcc95fcfc 100644
--- a/openpype/hosts/nuke/api/lib.py
+++ b/openpype/hosts/nuke/api/lib.py
@@ -1593,28 +1593,35 @@ def set_node_knobs_from_settings(node, knob_settings, **kwargs):
if not knob_value:
continue
- # first convert string types to string
- # just to ditch unicode
- if isinstance(knob_value, six.text_type):
- knob_value = str(knob_value)
-
- # set correctly knob types
- if knob_type == "bool":
- knob_value = bool(knob_value)
- elif knob_type == "decimal_number":
- knob_value = float(knob_value)
- elif knob_type == "number":
- knob_value = int(knob_value)
- elif knob_type == "text":
- knob_value = knob_value
- elif knob_type == "color_gui":
- knob_value = color_gui_to_int(knob_value)
- elif knob_type in ["2d_vector", "3d_vector", "color"]:
- knob_value = [float(v) for v in knob_value]
+ knob_value = convert_knob_value_to_correct_type(
+ knob_type, knob_value)
node[knob_name].setValue(knob_value)
+def convert_knob_value_to_correct_type(knob_type, knob_value):
+ # first convert string types to string
+ # just to ditch unicode
+ if isinstance(knob_value, six.text_type):
+ knob_value = str(knob_value)
+
+ # set correctly knob types
+ if knob_type == "bool":
+ knob_value = bool(knob_value)
+ elif knob_type == "decimal_number":
+ knob_value = float(knob_value)
+ elif knob_type == "number":
+ knob_value = int(knob_value)
+ elif knob_type == "text":
+ knob_value = knob_value
+ elif knob_type == "color_gui":
+ knob_value = color_gui_to_int(knob_value)
+ elif knob_type in ["2d_vector", "3d_vector", "color"]:
+ knob_value = [float(v) for v in knob_value]
+
+ return knob_value
+
+
def color_gui_to_int(color_gui):
hex_value = (
"0x{0:0>2x}{1:0>2x}{2:0>2x}{3:0>2x}").format(*color_gui)
@@ -2227,10 +2234,9 @@ def get_write_node_template_attr(node):
subset=avalon_knob_data["subset"]
)
+
# collecting correct data
- correct_data = OrderedDict({
- "file": get_render_path(node)
- })
+ correct_data = OrderedDict()
# adding imageio knob presets
for k, v in nuke_imageio_writes.items():
diff --git a/openpype/hosts/nuke/plugins/publish/help/validate_asset_name.xml b/openpype/hosts/nuke/plugins/publish/help/validate_asset_name.xml
new file mode 100644
index 0000000000..1097909a5f
--- /dev/null
+++ b/openpype/hosts/nuke/plugins/publish/help/validate_asset_name.xml
@@ -0,0 +1,18 @@
+
+
+
+ Shot/Asset mame
+
+## 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.
+
+
+
\ No newline at end of file
diff --git a/openpype/hosts/nuke/plugins/publish/help/validate_backdrop.xml b/openpype/hosts/nuke/plugins/publish/help/validate_backdrop.xml
new file mode 100644
index 0000000000..ab1b650773
--- /dev/null
+++ b/openpype/hosts/nuke/plugins/publish/help/validate_backdrop.xml
@@ -0,0 +1,36 @@
+
+
+
+ Found multiple outputs
+
+## 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.
+
+
+### 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.
+
+
+
+ Empty backdrop
+
+## 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.
+
+
+
\ No newline at end of file
diff --git a/openpype/hosts/nuke/plugins/publish/help/validate_gizmo.xml b/openpype/hosts/nuke/plugins/publish/help/validate_gizmo.xml
new file mode 100644
index 0000000000..f39a41a4f9
--- /dev/null
+++ b/openpype/hosts/nuke/plugins/publish/help/validate_gizmo.xml
@@ -0,0 +1,36 @@
+
+
+
+ Found multiple outputs
+
+## 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.
+
+
+### How could this happen?
+
+Perhaps you had created exciently more than one Output node.
+
+
+
+ Missing Input nodes
+
+## 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.
+
+
+
\ No newline at end of file
diff --git a/openpype/hosts/nuke/plugins/publish/help/validate_knobs.xml b/openpype/hosts/nuke/plugins/publish/help/validate_knobs.xml
new file mode 100644
index 0000000000..76c184f653
--- /dev/null
+++ b/openpype/hosts/nuke/plugins/publish/help/validate_knobs.xml
@@ -0,0 +1,18 @@
+
+
+
+ Knobs value
+
+## 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.
+
+
+
\ No newline at end of file
diff --git a/openpype/hosts/nuke/plugins/publish/help/validate_output_resolution.xml b/openpype/hosts/nuke/plugins/publish/help/validate_output_resolution.xml
new file mode 100644
index 0000000000..08a88a993e
--- /dev/null
+++ b/openpype/hosts/nuke/plugins/publish/help/validate_output_resolution.xml
@@ -0,0 +1,16 @@
+
+
+
+ Output format
+
+## 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.
+
+
+
\ No newline at end of file
diff --git a/openpype/hosts/nuke/plugins/publish/help/validate_proxy_mode.xml b/openpype/hosts/nuke/plugins/publish/help/validate_proxy_mode.xml
new file mode 100644
index 0000000000..6fe5d5d43e
--- /dev/null
+++ b/openpype/hosts/nuke/plugins/publish/help/validate_proxy_mode.xml
@@ -0,0 +1,16 @@
+
+
+
+ Proxy mode
+
+## 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.
+
+
+
\ No newline at end of file
diff --git a/openpype/hosts/nuke/plugins/publish/help/validate_rendered_frames.xml b/openpype/hosts/nuke/plugins/publish/help/validate_rendered_frames.xml
new file mode 100644
index 0000000000..434081c269
--- /dev/null
+++ b/openpype/hosts/nuke/plugins/publish/help/validate_rendered_frames.xml
@@ -0,0 +1,17 @@
+
+
+
+ Rendered Frames
+
+## 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.
+
+
+
\ No newline at end of file
diff --git a/openpype/hosts/nuke/plugins/publish/help/validate_script_attributes.xml b/openpype/hosts/nuke/plugins/publish/help/validate_script_attributes.xml
new file mode 100644
index 0000000000..871fc629ce
--- /dev/null
+++ b/openpype/hosts/nuke/plugins/publish/help/validate_script_attributes.xml
@@ -0,0 +1,18 @@
+
+
+
+ Script attributes
+
+## 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.
+
+
+
\ No newline at end of file
diff --git a/openpype/hosts/nuke/plugins/publish/help/validate_write_nodes.xml b/openpype/hosts/nuke/plugins/publish/help/validate_write_nodes.xml
new file mode 100644
index 0000000000..cdf85102bc
--- /dev/null
+++ b/openpype/hosts/nuke/plugins/publish/help/validate_write_nodes.xml
@@ -0,0 +1,18 @@
+
+
+
+ Knobs values
+
+## 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.
+
+
+
\ No newline at end of file
diff --git a/openpype/hosts/nuke/plugins/publish/precollect_workfile.py b/openpype/hosts/nuke/plugins/publish/precollect_workfile.py
index 7349a8f424..822f405a6f 100644
--- a/openpype/hosts/nuke/plugins/publish/precollect_workfile.py
+++ b/openpype/hosts/nuke/plugins/publish/precollect_workfile.py
@@ -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)
diff --git a/openpype/hosts/nuke/plugins/publish/validate_instance_in_context.py b/openpype/hosts/nuke/plugins/publish/validate_asset_name.py
similarity index 74%
rename from openpype/hosts/nuke/plugins/publish/validate_instance_in_context.py
rename to openpype/hosts/nuke/plugins/publish/validate_asset_name.py
index 842f74b6f6..7647471f8a 100644
--- a/openpype/hosts/nuke/plugins/publish/validate_instance_in_context.py
+++ b/openpype/hosts/nuke/plugins/publish/validate_asset_name.py
@@ -3,20 +3,17 @@
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 import PublishXmlValidationError
class SelectInvalidInstances(pyblish.api.Action):
"""Select invalid instances in Outliner."""
- label = "Select Instances"
+ label = "Select"
icon = "briefcase"
on = "failed"
@@ -39,6 +36,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 +48,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 +83,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 +97,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"
+ 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
+ }
+ )
diff --git a/openpype/hosts/nuke/plugins/publish/validate_backdrop.py b/openpype/hosts/nuke/plugins/publish/validate_backdrop.py
index e2843d146e..17dc79dc56 100644
--- a/openpype/hosts/nuke/plugins/publish/validate_backdrop.py
+++ b/openpype/hosts/nuke/plugins/publish/validate_backdrop.py
@@ -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"
+ )
diff --git a/openpype/hosts/nuke/plugins/publish/validate_gizmo.py b/openpype/hosts/nuke/plugins/publish/validate_gizmo.py
index d0d930f50c..2321bd1fd4 100644
--- a/openpype/hosts/nuke/plugins/publish/validate_gizmo.py
+++ b/openpype/hosts/nuke/plugins/publish/validate_gizmo.py
@@ -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()}
+ )
diff --git a/openpype/hosts/nuke/plugins/publish/validate_knobs.py b/openpype/hosts/nuke/plugins/publish/validate_knobs.py
index d290ff4541..e2b11892e5 100644
--- a/openpype/hosts/nuke/plugins/publish/validate_knobs.py
+++ b/openpype/hosts/nuke/plugins/publish/validate_knobs.py
@@ -1,7 +1,8 @@
import nuke
-
+import six
import pyblish.api
import openpype.api
+from openpype.pipeline import PublishXmlValidationError
class ValidateKnobs(pyblish.api.ContextPlugin):
@@ -27,11 +28,21 @@ class ValidateKnobs(pyblish.api.ContextPlugin):
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 +65,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 +91,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 +103,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 +119,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
diff --git a/openpype/hosts/nuke/plugins/publish/validate_output_resolution.py b/openpype/hosts/nuke/plugins/publish/validate_output_resolution.py
index 27094b8d74..fc07e9b83b 100644
--- a/openpype/hosts/nuke/plugins/publish/validate_output_resolution.py
+++ b/openpype/hosts/nuke/plugins/publish/validate_output_resolution.py
@@ -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")
+import openpype.api
+from openpype.hosts.nuke.api import maintained_selection
+from openpype.pipeline import PublishXmlValidationError
+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 = [openpype.api.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")
diff --git a/openpype/hosts/nuke/plugins/publish/validate_proxy_mode.py b/openpype/hosts/nuke/plugins/publish/validate_proxy_mode.py
index 9c6ca03ffd..dac240ad19 100644
--- a/openpype/hosts/nuke/plugins/publish/validate_proxy_mode.py
+++ b/openpype/hosts/nuke/plugins/publish/validate_proxy_mode.py
@@ -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"
+ )
diff --git a/openpype/hosts/nuke/plugins/publish/validate_rendered_frames.py b/openpype/hosts/nuke/plugins/publish/validate_rendered_frames.py
index 5f7b1f3806..237ff423e5 100644
--- a/openpype/hosts/nuke/plugins/publish/validate_rendered_frames.py
+++ b/openpype/hosts/nuke/plugins/publish/validate_rendered_frames.py
@@ -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
diff --git a/openpype/hosts/nuke/plugins/publish/validate_script.py b/openpype/hosts/nuke/plugins/publish/validate_script.py
deleted file mode 100644
index b8d7494b9d..0000000000
--- a/openpype/hosts/nuke/plugins/publish/validate_script.py
+++ /dev/null
@@ -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
- )
diff --git a/openpype/hosts/nuke/plugins/publish/validate_script_attributes.py b/openpype/hosts/nuke/plugins/publish/validate_script_attributes.py
new file mode 100644
index 0000000000..106d7a2524
--- /dev/null
+++ b/openpype/hosts/nuke/plugins/publish/validate_script_attributes.py
@@ -0,0 +1,127 @@
+from pprint import pformat
+import pyblish.api
+
+import openpype.api
+from openpype.pipeline import PublishXmlValidationError
+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 = [openpype.api.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 = "
".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()
diff --git a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py
index c0d5c8f402..362ff31174 100644
--- a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py
+++ b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py
@@ -1,10 +1,10 @@
-import os
import pyblish.api
-import openpype.utils
+from openpype.api 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
)
+from openpype.pipeline import PublishXmlValidationError
@pyblish.api.log
@@ -14,18 +14,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 +46,69 @@ 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):
+ 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 = "
".join(details)
+ dbg_msg += "\n\t".join(details)
+
+ raise PublishXmlValidationError(
+ self, dbg_msg, formatting_data={"xml_msg": xml_msg}
+ )
diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json
index 3e29122074..f40ec1fe9e 100644
--- a/openpype/settings/defaults/project_settings/nuke.json
+++ b/openpype/settings/defaults/project_settings/nuke.json
@@ -131,7 +131,7 @@
"write"
]
},
- "ValidateInstanceInContext": {
+ "ValidateCorrectAssetName": {
"enabled": true,
"optional": true,
"active": true
diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json
index 575bfe79e7..e5827a92c4 100644
--- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json
+++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json
@@ -61,8 +61,8 @@
"name": "template_publish_plugin",
"template_data": [
{
- "key": "ValidateInstanceInContext",
- "label": "Validate Instance In Context"
+ "key": "ValidateCorrectAssetName",
+ "label": "Validate Correct Asset name"
}
]
},