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 91acf4eabcb..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,6 +114,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/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 0539c1188f7..f490b580d6f 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py +++ b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py @@ -10,7 +10,7 @@ from openpype.pipeline.publish import ( PublishXmlValidationError, - OptionalPyblishPluginMixin, + OptionalPyblishPluginMixin ) diff --git a/server_addon/nuke/server/settings/create_plugins.py b/server_addon/nuke/server/settings/create_plugins.py index 378d449b0b0..6bdc5ee5ad8 100644 --- a/server_addon/nuke/server/settings/create_plugins.py +++ b/server_addon/nuke/server/settings/create_plugins.py @@ -55,7 +55,10 @@ class CreateWriteRenderModel(BaseSettingsModel): enum_resolver=instance_attributes_enum, title="Instance attributes" ) - + exposed_knobs: list[str] = SettingsField( + title="Write Node Exposed Knobs", + default_factory=list + ) prenodes: list[PrenodeModel] = SettingsField( default_factory=list, title="Preceding nodes", @@ -81,7 +84,10 @@ class CreateWritePrerenderModel(BaseSettingsModel): enum_resolver=instance_attributes_enum, title="Instance attributes" ) - + exposed_knobs: list[str] = SettingsField( + title="Write Node Exposed Knobs", + default_factory=list + ) prenodes: list[PrenodeModel] = SettingsField( default_factory=list, title="Preceding nodes", @@ -107,7 +113,10 @@ class CreateWriteImageModel(BaseSettingsModel): enum_resolver=instance_attributes_enum, title="Instance attributes" ) - + exposed_knobs: list[str] = SettingsField( + title="Write Node Exposed Knobs", + default_factory=list + ) prenodes: list[PrenodeModel] = SettingsField( default_factory=list, title="Preceding nodes", @@ -146,6 +155,7 @@ class CreatorPluginsSettings(BaseSettingsModel): "reviewable", "farm_rendering" ], + "exposed_knobs": [], "prenodes": [ { "name": "Reformat01", @@ -179,6 +189,7 @@ class CreatorPluginsSettings(BaseSettingsModel): "farm_rendering", "use_range_limit" ], + "exposed_knobs": [], "prenodes": [] }, "CreateWriteImage": { @@ -191,6 +202,7 @@ class CreatorPluginsSettings(BaseSettingsModel): "instance_attributes": [ "use_range_limit" ], + "exposed_knobs": [], "prenodes": [ { "name": "FrameHold01", diff --git a/server_addon/nuke/server/version.py b/server_addon/nuke/server/version.py index 9cb17e79762..c11f861afbe 100644 --- a/server_addon/nuke/server/version.py +++ b/server_addon/nuke/server/version.py @@ -1 +1 @@ -__version__ = "0.1.8" +__version__ = "0.1.9"