Skip to content

Instantly share code, notes, and snippets.

@herronelou
Last active July 27, 2025 04:58
Show Gist options
  • Save herronelou/1397f1ca00c1bb2263cb75c8977fac37 to your computer and use it in GitHub Desktop.
Save herronelou/1397f1ca00c1bb2263cb75c8977fac37 to your computer and use it in GitHub Desktop.
Nuke Workspace from File
import nuke
import os
def parse_nuke_layout(filepath):
"""
Parses the XML window layout from the header of a Nuke (.nk) script file.
Args:
filepath (str): The full path to the .nk script file.
Returns:
str or None: The XML layout string if found, otherwise None.
"""
if not os.path.exists(filepath):
nuke.tprint(f"Error: File not found at {filepath}")
return None
try:
with open(filepath, 'r', encoding='utf-8') as f:
xml_lines = []
in_layout_block = False
# Search for the start of the layout block within the first 10 lines
for i, line in enumerate(f):
if i >= 10 and not in_layout_block:
# Abort if the layout hasn't started by line 10
nuke.tprint("No 'define_window_layout_xml' found in the first 10 lines.")
return None
stripped_line = line.strip()
if stripped_line.startswith('define_window_layout_xml'):
in_layout_block = True
# Extract the XML declaration part from the first line
# It starts with '<?xml ...'
xml_start_index = stripped_line.find('<?xml')
if xml_start_index != -1:
xml_lines.append(stripped_line[xml_start_index:])
continue
if in_layout_block:
# The block ends with a single '}' on a line
if stripped_line == '}':
# We have found the end of the block
full_xml = "\n".join(xml_lines)
# The last line in the XML block is '</layout>', so we remove the final '}'
# which is part of the nuke script syntax, not the XML.
return full_xml.strip()
xml_lines.append(line)
# If the loop finishes and we were in a block, the file is likely malformed
if in_layout_block:
nuke.tprint("Warning: Reached end of file but layout block was not properly closed.")
return None
except Exception as e:
nuke.tprint(f"An error occurred while reading the file: {e}")
return None
return None
def apply_and_save_script_layout():
"""
This function is intended to be used as a Nuke onScriptLoad callback.
It parses the layout from the opened script, saves it to a temporary
workspace file, and applies it using a QTimer to avoid instability.
"""
# Check if we are in a GUI session
if not nuke.GUI:
return
# Import UI-specific modules safely
try:
import hiero.ui
from PySide2.QtCore import QTimer
except ImportError:
nuke.tprint("Could not import hiero.ui or PySide2.QtCore. Cannot set workspace.")
return
# Get the path of the currently opened script
script_path = nuke.root().name()
if script_path == 'Root' or not script_path:
# This can happen on script launch before a file is opened.
return
nuke.tprint(f"Checking for layout in: {script_path}")
# Parse the layout from the script
layout_xml = parse_nuke_layout(script_path)
if layout_xml:
def deferred_set_workspace():
"""
This function contains the logic that will be deferred.
It writes the layout to a file and then applies it.
"""
try:
# Define the workspace name and path
workspace_name = "_script_layout"
nuke_dir = os.path.expanduser("~/.nuke")
workspace_dir = os.path.join(nuke_dir, "Workspaces", "Nuke")
# Ensure the target directory exists
if not os.path.exists(workspace_dir):
os.makedirs(workspace_dir)
nuke.tprint(f"Created directory: {workspace_dir}")
workspace_filepath = os.path.join(workspace_dir, f"{workspace_name}.xml")
# Write the XML content to the workspace file
with open(workspace_filepath, 'w', encoding='utf-8') as f:
f.write(layout_xml)
nuke.tprint(f"Saved script layout to: {workspace_filepath}")
# Apply the workspace layout
hiero.ui.setWorkspace(workspace_name)
nuke.tprint(f"Applied workspace: '{workspace_name}'")
except Exception as e:
nuke.tprint(f"Error saving or applying workspace: {e}")
# Use QTimer.singleShot to delay the execution.
# A delay of 0ms pushes the call to the end of the Qt event queue,
# ensuring the Nuke UI is stable before we try to modify it.
QTimer.singleShot(10, deferred_set_workspace)
else:
nuke.tprint("No layout found in script or an error occurred during parsing.")
# --- HOW TO USE ---
#
# 1. Save this script as a Python file (e.g., 'layout_manager.py') in your .nuke folder.
# 2. In your 'menu.py' file, add the following lines:
#
# import layout_manager
# nuke.addOnScriptLoad(layout_manager.apply_and_save_script_layout)
#
# 3. Restart Nuke. Now, whenever you open a .nk script that contains a layout
# definition in its header, that layout will be automatically applied.
# This will only work once it created the first XML and you've restarted nuke, as it seems to not let you load a layout by name if it's not in the menu already.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment