diff --git a/openpype/hosts/nuke/api/__init__.py b/openpype/hosts/nuke/api/__init__.py index c6ccd0baf15..2536230637b 100644 --- a/openpype/hosts/nuke/api/__init__.py +++ b/openpype/hosts/nuke/api/__init__.py @@ -43,7 +43,8 @@ get_node_data, set_node_data, update_node_data, - create_write_node + create_write_node, + link_knobs ) from .utils import ( colorspace_exists_on_node, @@ -95,6 +96,7 @@ "set_node_data", "update_node_data", "create_write_node", + "link_knobs", "colorspace_exists_on_node", "get_colorspace_list", diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 7ba53caead9..cdefd05c114 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3499,3 +3499,27 @@ def create_camera_node_by_version(): return nuke.createNode("Camera4") else: return nuke.createNode("Camera2") + + +def link_knobs(knobs, node, group_node): + """Link knobs from inside `group_node`""" + + missing_knobs = [] + for knob in knobs: + if knob in group_node.knobs(): + continue + + if knob not in node.knobs().keys(): + missing_knobs.append(knob) + + link = nuke.Link_Knob("") + link.makeLink(node.name(), knob) + link.setName(knob) + link.setFlag(0x1000) + group_node.addKnob(link) + + if missing_knobs: + raise ValueError( + "Write node exposed knobs missing:\n\n{}\n\nPlease review" + " project settings.".format("\n".join(missing_knobs)) + ) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 15d7bfc4b96..c8301b81fda 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -44,7 +44,8 @@ get_view_process_node, get_viewer_config_from_string, deprecated, - get_filenames_without_hash + get_filenames_without_hash, + link_knobs ) from .pipeline import ( list_instances, @@ -1344,3 +1345,11 @@ def _remove_old_knobs(node): node.removeKnob(knob) except ValueError: pass + + +def exposed_write_knobs(settings, plugin_name, instance_node): + exposed_knobs = settings["nuke"]["create"][plugin_name]["exposed_knobs"] + if exposed_knobs: + instance_node.addKnob(nuke.Text_Knob('', 'Write Knobs')) + write_node = nuke.allNodes(group=instance_node, filter="Write")[0] + link_knobs(exposed_knobs, write_node, instance_node) diff --git a/openpype/hosts/nuke/plugins/create/create_write_image.py b/openpype/hosts/nuke/plugins/create/create_write_image.py index 8c18739587e..f21d871c9f4 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_image.py +++ b/openpype/hosts/nuke/plugins/create/create_write_image.py @@ -12,6 +12,7 @@ EnumDef ) from openpype.hosts.nuke import api as napi +from openpype.hosts.nuke.api.plugin import exposed_write_knobs class CreateWriteImage(napi.NukeWriteCreator): @@ -132,6 +133,10 @@ def create(self, subset_name, instance_data, pre_create_data): instance.data_to_store() ) + exposed_write_knobs( + self.project_settings, self.__class__.__name__, instance_node + ) + return instance except Exception as er: diff --git a/openpype/hosts/nuke/plugins/create/create_write_prerender.py b/openpype/hosts/nuke/plugins/create/create_write_prerender.py index 395c3b002fd..742bfb20adf 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_prerender.py +++ b/openpype/hosts/nuke/plugins/create/create_write_prerender.py @@ -9,6 +9,7 @@ BoolDef ) from openpype.hosts.nuke import api as napi +from openpype.hosts.nuke.api.plugin import exposed_write_knobs class CreateWritePrerender(napi.NukeWriteCreator): @@ -119,6 +120,10 @@ def create(self, subset_name, instance_data, pre_create_data): instance.data_to_store() ) + exposed_write_knobs( + self.project_settings, self.__class__.__name__, instance_node + ) + return instance except Exception as er: diff --git a/openpype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py index e622376917d..fc16876f757 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -9,6 +9,7 @@ BoolDef ) from openpype.hosts.nuke import api as napi +from openpype.hosts.nuke.api.plugin import exposed_write_knobs class CreateWriteRender(napi.NukeWriteCreator): @@ -113,15 +114,9 @@ def create(self, subset_name, instance_data, pre_create_data): instance.data_to_store() ) - settings = self.project_settings["nuke"]["create"] - exposed_knobs = settings["CreateWriteRender"]["exposed_knobs"] - write_node = nuke.allNodes(group=instance_node, filter="Write")[0] - for knob in exposed_knobs: - link = nuke.Link_Knob("") - link.makeLink(write_node.name(), knob) - link.setName(knob) - link.setFlag(0x1000) - instance_node.addKnob(link) + exposed_write_knobs( + self.project_settings, self.__class__.__name__, instance_node + ) return instance diff --git a/openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py b/openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py new file mode 100644 index 00000000000..fe5644f0c91 --- /dev/null +++ b/openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py @@ -0,0 +1,77 @@ +import pyblish.api + +from openpype.pipeline.publish import get_errored_instances_from_context +from openpype.hosts.nuke.api.lib import link_knobs +from openpype.pipeline.publish import ( + OptionalPyblishPluginMixin, + PublishValidationError +) + + +class RepairExposedKnobs(pyblish.api.Action): + label = "Repair" + on = "failed" + icon = "wrench" + + def process(self, context, plugin): + instances = get_errored_instances_from_context(context) + + for instance in instances: + child_nodes = ( + instance.data.get("transientData", {}).get("childNodes") + or instance + ) + + write_group_node = instance.data["transientData"]["node"] + # get write node from inside of group + write_node = None + for x in child_nodes: + if x.Class() == "Write": + write_node = x + + plugin_name = plugin.families_mapping[instance.data["family"]] + nuke_settings = instance.context.data["project_settings"]["nuke"] + create_settings = nuke_settings["create"][plugin_name] + exposed_knobs = create_settings["exposed_knobs"] + link_knobs(exposed_knobs, write_node, write_group_node) + + +class ValidateExposedKnobs( + OptionalPyblishPluginMixin, + pyblish.api.InstancePlugin +): + """ Validate write node exposed knobs. + + Compare exposed linked knobs to settings. + """ + + order = pyblish.api.ValidatorOrder + optional = True + families = ["render", "prerender", "image"] + label = "Validate Exposed Knobs" + actions = [RepairExposedKnobs] + hosts = ["nuke"] + families_mapping = { + "render": "CreateWriteRender", + "prerender": "CreateWritePrerender", + "image": "CreateWriteImage" + } + + def process(self, instance): + if not self.is_active(instance.data): + return + + plugin = self.families_mapping[instance.data["family"]] + group_node = instance.data["transientData"]["node"] + nuke_settings = instance.context.data["project_settings"]["nuke"] + create_settings = nuke_settings["create"][plugin] + exposed_knobs = create_settings["exposed_knobs"] + unexposed_knobs = [] + for knob in exposed_knobs: + if knob not in group_node.knobs(): + unexposed_knobs.append(knob) + + if unexposed_knobs: + raise PublishValidationError( + "Missing exposed knobs: {}".format(unexposed_knobs) + ) diff --git a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py index 434537022dd..f490b580d6f 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py +++ b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py @@ -1,7 +1,5 @@ from collections import defaultdict -import nuke - import pyblish.api from openpype.pipeline.publish import get_errored_instances_from_context from openpype.hosts.nuke.api.lib import ( @@ -12,8 +10,7 @@ from openpype.pipeline.publish import ( PublishXmlValidationError, - OptionalPyblishPluginMixin, - PublishValidationError + OptionalPyblishPluginMixin ) @@ -42,19 +39,6 @@ def process(self, context, plugin): set_node_knobs_from_settings(write_node, correct_data["knobs"]) - nuke_settings = instance.context.data["project_settings"]["nuke"] - create_settings = nuke_settings["create"]["CreateWriteRender"] - exposed_knobs = create_settings["exposed_knobs"] - for knob in exposed_knobs: - if knob in write_group_node.knobs(): - continue - - link = nuke.Link_Knob("") - link.makeLink(write_node.name(), knob) - link.setName(knob) - link.setFlag(0x1000) - write_group_node.addKnob(link) - self.log.debug("Node attributes were fixed") @@ -150,27 +134,6 @@ def process(self, instance): if check: self._make_error(check) - nuke_settings = instance.context.data["project_settings"]["nuke"] - create_settings = nuke_settings["create"]["CreateWriteRender"] - exposed_knobs = create_settings["exposed_knobs"] - unexposed_knobs = [] - for knob in exposed_knobs: - if knob not in write_group_node.knobs(): - unexposed_knobs.append(knob) - - """ - link = nuke.Link_Knob("") - link.makeLink(write_node.name(), knob) - link.setName(knob) - link.setFlag(0x1000) - write_group_node.addKnob(link) - """ - - if unexposed_knobs: - raise PublishValidationError( - "Missing exposed knobs: {}".format(unexposed_knobs) - ) - 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" diff --git a/server_addon/nuke/server/settings/create_plugins.py b/server_addon/nuke/server/settings/create_plugins.py index 30719c06f62..6bdc5ee5ad8 100644 --- a/server_addon/nuke/server/settings/create_plugins.py +++ b/server_addon/nuke/server/settings/create_plugins.py @@ -55,8 +55,8 @@ class CreateWriteRenderModel(BaseSettingsModel): enum_resolver=instance_attributes_enum, title="Instance attributes" ) - exposed_knobs: list[str] = Field( - title="Exposed Knobs", + exposed_knobs: list[str] = SettingsField( + title="Write Node Exposed Knobs", default_factory=list ) prenodes: list[PrenodeModel] = SettingsField( @@ -84,8 +84,8 @@ class CreateWritePrerenderModel(BaseSettingsModel): enum_resolver=instance_attributes_enum, title="Instance attributes" ) - exposed_knobs: list[str] = Field( - title="Exposed Knobs", + exposed_knobs: list[str] = SettingsField( + title="Write Node Exposed Knobs", default_factory=list ) prenodes: list[PrenodeModel] = SettingsField( @@ -113,8 +113,8 @@ class CreateWriteImageModel(BaseSettingsModel): enum_resolver=instance_attributes_enum, title="Instance attributes" ) - exposed_knobs: list[str] = Field( - title="Exposed Knobs", + exposed_knobs: list[str] = SettingsField( + title="Write Node Exposed Knobs", default_factory=list ) prenodes: list[PrenodeModel] = SettingsField(