diff --git a/client/ayon_nuke/api/lib.py b/client/ayon_nuke/api/lib.py index 58f00b2753..beae32b6c6 100644 --- a/client/ayon_nuke/api/lib.py +++ b/client/ayon_nuke/api/lib.py @@ -7,7 +7,7 @@ import platform import tempfile import contextlib -from collections import OrderedDict +from collections import defaultdict, OrderedDict import nuke from qtpy import QtCore, QtWidgets @@ -824,6 +824,17 @@ def get_view_process_node(): return duplicate_node(ipn_node) +def on_create_root_node(): + if script_name(): + log.info("Not a new Script. Skipping setting context settings.") + return + + log.info("New Script. Setting context settings ...") + + # Set context settings. + WorkfileSettings().set_context_settings() + + def on_script_load(): """Callback for ffmpeg support""" if nuke.env["LINUX"]: @@ -1453,6 +1464,60 @@ def create_backdrop(label="", color=None, layer=0, bdn["note_font_size"].setValue(20) return bdn +class ConfirmSetContextSettingMessageBox(QtWidgets.QDialog): + def __init__(self, change_description): + super().__init__() + layout = QtWidgets.QVBoxLayout() + self.setLayout(layout) + layout.addWidget(QtWidgets.QLabel( + "Would you like this script to have the following change:\n\n" + f"{change_description}\n\n" + "This is to match the correct settings for this context." + )) + if not ASSIST: + self._always_check_check_box = QtWidgets.QCheckBox( + "Always check this Script's context settings on load\n" + "(if switched off, can be switched back on in Project Settings User tab)" + ) + self._always_check_check_box.setChecked(True) + layout.addWidget( + self._always_check_check_box + ) + self._always_check_check_box.toggled.connect(self._always_check_check_box_toggled) + button_box = QtWidgets.QDialogButtonBox( + QtWidgets.QDialogButtonBox.StandardButtons.Yes| + QtWidgets.QDialogButtonBox.StandardButtons.No, + ) + layout.addWidget(button_box) + button_box.accepted.connect(self.accept) + button_box.rejected.connect(self.reject) + + def _always_check_check_box_toggled(self, is_checked): + root_node = nuke.root() + if "check_context_settings_on_load" not in root_node.knobs(): + root_node.addKnob(nuke.Boolean_Knob( + "check_context_settings_on_load", + "Check Context Settings on Load", + True, + )) + root_node["check_context_settings_on_load"].setValue(is_checked) + + @classmethod + def ask_for_confirmation(cls, message): + project_settings = get_current_project_settings() + if not project_settings["nuke"]["general"]["check_context_settings_on_script_open"]: + return False + + root_node = nuke.root() + # a specific script can be set to never check + if ( + "check_context_settings_on_load" in root_node.knobs() + and not root_node["check_context_settings_on_load"].value() + ): + return False + + return cls(message).exec() == QtWidgets.QDialog.Accepted + class WorkfileSettings(object): """ @@ -2013,7 +2078,7 @@ def set_colorspace(self): if read_clrs_inputs: self.set_reads_colorspace(read_clrs_inputs) - def reset_frame_range_handles(self): + def reset_frame_range_handles(self, requires_confirmation=False): """Set frame range to current folder.""" if "attrib" not in self._task_entity: @@ -2052,22 +2117,64 @@ def reset_frame_range_handles(self): frame_start_handle = frame_start - handle_start frame_end_handle = frame_end + handle_end - self._root_node["lock_range"].setValue(False) - self._root_node["fps"].setValue(fps) - self._root_node["first_frame"].setValue(frame_start_handle) - self._root_node["last_frame"].setValue(frame_end_handle) - self._root_node["lock_range"].setValue(True) - - # update node graph so knobs are updated - update_node_graph() - frame_range = '{0}-{1}'.format(frame_start, frame_end) - for node in nuke.allNodes(filter="Viewer"): - node['frame_range'].setValue(frame_range) - node['frame_range_lock'].setValue(True) - node['frame_range'].setValue(frame_range) - node['frame_range_lock'].setValue(True) + knob_name_current_new_value_triplets_by_node = defaultdict(list) + for knob_name_new_value_pairs, nodes in ( + ( + ( + ("lock_range", False), + ("fps", fps), + ("first_frame", frame_start_handle), + ("last_frame", frame_end_handle), + ("lock_range", True), + ), + (self._root_node,), + ), + ( + ( + ("frame_range", frame_range), + ("frame_range_lock", True), + ), + nuke.allNodes(filter="Viewer"), + ) + ): + for node in nodes: + for knob_name, new_value in knob_name_new_value_pairs: + current_value = node[knob_name].value() + if current_value != new_value: + knob_name_current_new_value_triplets_by_node[node].append( + (knob_name, current_value, new_value) + ) + + if knob_name_current_new_value_triplets_by_node: + if not requires_confirmation or ConfirmSetContextSettingMessageBox.ask_for_confirmation( + "\n\n".join( + "{}:\n{}".format( + node.name(), + "\n".join( + f"{knob_name}: {current_value} -> {new_value}" for knob_name, current_value, new_value in knob_name_current_new_value_triplets + ) + ) for node, knob_name_current_new_value_triplets in knob_name_current_new_value_triplets_by_node.items() + ) + ): + was_node_graph_updated = False + for node, knob_name_current_new_value_triplets in knob_name_current_new_value_triplets_by_node.items(): + # Root node will be first. + # Based on how the code was when I found it, + # `update_node_graph` needs calling between + # root node and viewers - splidje + if not was_node_graph_updated and node != self._root_node: + # update node graph so knobs are updated + update_node_graph() + was_node_graph_updated = True + for knob_name, _, new_value in knob_name_current_new_value_triplets: + node[knob_name].setValue(new_value) + + # in case no Viewers needed knob changes + if not was_node_graph_updated: + # update node graph so knobs are updated + update_node_graph() if not ASSIST: set_node_data( @@ -2084,7 +2191,7 @@ def reset_frame_range_handles(self): "updating custom knobs..." ) - def reset_resolution(self): + def reset_resolution(self, requires_confirmation=False): """Set resolution to project resolution.""" log.info("Resetting resolution") project_name = get_current_project_name() @@ -2106,6 +2213,20 @@ def reset_resolution(self): log.error(msg) nuke.message(msg) + current_format = nuke.root()["format"].value() + if requires_confirmation and any(( + current_format.width() != format_data["width"], + current_format.height() != format_data["height"], + current_format.pixelAspect() != format_data["pixel_aspect"], + )): + if not ConfirmSetContextSettingMessageBox.ask_for_confirmation( + "Format:\n" + f"{current_format.width()}x{current_format.height()} {current_format.pixelAspect()}" + " -> " + f'{format_data["width"]}x{format_data["height"]} {format_data["pixel_aspect"]}' + ): + return + existing_format = None for format in nuke.formats(): if format_data["name"] == format.name(): @@ -2148,10 +2269,10 @@ def make_format_string(self, **kwargs): "{name}".format(**kwargs) ) - def set_context_settings(self): - self.reset_resolution() - self.reset_frame_range_handles() - # add colorspace menu item + def set_context_settings(self, requires_confirmation=False): + self.reset_resolution(requires_confirmation) + self.reset_frame_range_handles(requires_confirmation) + # todo: No need to check confirmation for color? self.set_colorspace() def set_favorites(self): diff --git a/client/ayon_nuke/api/pipeline.py b/client/ayon_nuke/api/pipeline.py index ce815f1bdf..1f2670e921 100644 --- a/client/ayon_nuke/api/pipeline.py +++ b/client/ayon_nuke/api/pipeline.py @@ -56,6 +56,7 @@ check_inventory_versions, set_avalon_knob_data, read_avalon_data, + on_create_root_node, on_script_load, dirmap_file_name_filter, add_scripts_menu, @@ -169,14 +170,14 @@ def add_nuke_callbacks(project_settings: dict = None): project_settings = get_current_project_settings() nuke_settings = project_settings["nuke"] - workfile_settings = WorkfileSettings() - # Set context settings. - nuke.addOnCreate( - workfile_settings.set_context_settings, nodeClass="Root") + nuke.addOnCreate(on_create_root_node, nodeClass="Root") # adding favorites to file browser - nuke.addOnCreate(workfile_settings.set_favorites, nodeClass="Root") + nuke.addOnCreate( + lambda: WorkfileSettings().set_favorites(), + nodeClass="Root", + ) # template builder callbacks nuke.addOnCreate(start_workfile_template_builder, nodeClass="Root") @@ -188,8 +189,10 @@ def add_nuke_callbacks(project_settings: dict = None): nuke.addOnScriptLoad(check_inventory_versions) nuke.addOnScriptSave(check_inventory_versions) - # set apply all workfile settings on script load and save - nuke.addOnScriptLoad(WorkfileSettings().set_context_settings) + # set apply all workfile settings on script load + nuke.addOnScriptLoad( + lambda: WorkfileSettings().set_context_settings(requires_confirmation=True) + ) if nuke_settings["dirmap"]["enabled"]: log.info("Added Nuke's dir-mapping callback ...") diff --git a/client/ayon_nuke/api/workio.py b/client/ayon_nuke/api/workio.py index 0a46b8043b..a985d38dc2 100644 --- a/client/ayon_nuke/api/workio.py +++ b/client/ayon_nuke/api/workio.py @@ -23,6 +23,7 @@ def save_file(filepath): def open_file(filepath): + from .lib import WorkfileSettings def read_script(nuke_script): if not ASSIST: @@ -31,6 +32,7 @@ def read_script(nuke_script): nuke.Root()["name"].setValue(nuke_script) nuke.Root()["project_directory"].setValue(os.path.dirname(nuke_script)) nuke.Root().setModified(False) + WorkfileSettings().set_context_settings(requires_confirmation=True) else: nuke.scriptOpen(nuke_script) diff --git a/client/ayon_nuke/plugins/create/convert_legacy.py b/client/ayon_nuke/plugins/create/convert_legacy.py index 65e719d15b..de743eb223 100644 --- a/client/ayon_nuke/plugins/create/convert_legacy.py +++ b/client/ayon_nuke/plugins/create/convert_legacy.py @@ -6,7 +6,7 @@ get_avalon_knob_data, NODE_TAB_NAME, ) -from ayon_nuke.api.plugin import convert_to_valid_instaces +from ayon_nuke.api.plugin import convert_to_valid_instances import nuke @@ -50,6 +50,6 @@ def find_instances(self): def convert(self): # loop all instances and convert them - convert_to_valid_instaces() + convert_to_valid_instances() # remove legacy item if all is fine self.remove_convertor_item() diff --git a/server/settings/general.py b/server/settings/general.py index d6eeb0013c..a82346e8dd 100644 --- a/server/settings/general.py +++ b/server/settings/general.py @@ -27,6 +27,8 @@ class MenuShortcut(BaseSettingsModel): class GeneralSettings(BaseSettingsModel): """Nuke general project settings.""" + check_context_settings_on_script_open: bool = SettingsField(True, title="Check Context Settings on Script Open") + menu: MenuShortcut = SettingsField( default_factory=MenuShortcut, title="Menu Shortcuts", @@ -34,6 +36,7 @@ class GeneralSettings(BaseSettingsModel): DEFAULT_GENERAL_SETTINGS = { + "check_context_settings_on_script_open": True, "menu": { "create": "ctrl+alt+c", "publish": "ctrl+alt+p",