# Cattery Pattern and Gizmo Design ## Cattery Directory Structure Place plugins in `~/.nuke/Cattery/NodeName/`: ``` NodeName/ ├── NodeName.gizmo # node UI definition ├── NodeName.cat # compiled TorchScript model ├── cat.json # Cattery metadata ├── NodeName.png # icon (26x26) └── NodeName@2x.png # retina icon (52x52) ``` ## cat.json ```json { "minimum_nuke_version_required": 14.0, "version": 1, "category": "Depth Estimation", "description": "NodeName - https://github.com/Biohazard-VFX/...", "cats": [ { "filepath": "NodeName.gizmo", "name": "NodeName", "icon": "NodeName.png" } ] } ``` ## Registration init.py (all modes including command-line): ```python import nuke nuke.pluginAddPath('./Cattery/NodeName') ``` menu.py (interactive only): ```python import nuke toolbar = nuke.menu("Nodes") toolbar.addCommand( 'Cattery/Category/NodeName', 'nuke.createNode("NodeName")', icon="NodeName.png" ) ``` ## Gizmo Structure A gizmo is a Group node containing an internal processing pipeline. Key sections: knob definitions, internal nodes, Python callbacks. ### Knob Types for ML Nodes ```tcl # GPU settings addUserKnob {6 useGPU l "Use GPU" +STARTLINE} useGPU true addUserKnob {26 gpuName l "" +STARTLINE} # Model selection (Enumeration) addUserKnob {4 model_variant l "Model" M {"Small" "Base" "Large" "Metric"}} # Resolution control addUserKnob {4 downrez l "Downrez" M {"1/1 (Full)" "1/2" "1/4"}} downrez "1/2" # Processing resolution (Enumeration with numeric comparison) addUserKnob {4 process_res l "Process Res" M {"Auto" "336" "504" "672" "1008"}} # Output mode addUserKnob {4 view l "Output" M {"Depth (grayscale)" "False Color" "Confidence" "Metric Depth"}} # Depth controls addUserKnob {7 near l "Near" R -20 20} near 5 addUserKnob {7 far l "Far" R -20 20} far 0 # Toggle addUserKnob {6 bypass_srgb l "Bypass sRGB" +STARTLINE} addUserKnob {6 invert_map l "Invert Depth" +STARTLINE} # Info tab addUserKnob {20 info l "Info"} addUserKnob {26 version l "Version" T "1.0.0"} addUserKnob {26 author l "Author" T "Biohazard VFX"} ``` ### Internal Node Graph Pipeline Typical ML inference gizmo pipeline: ``` Input1 | AssertNukeVersion (check >= 14.0) | Shuffle (ensure RGBA channels exist) | Colorspace (linear -> sRGB, unless bypass_srgb) | NOTE: use Colorspace node, NOT ColorMatrix | sRGB EOTF is nonlinear, ColorMatrix can't do it | Reformat (downrez based on knob) | scale expression: pow(2, parent.downrez * -1) | Inference (load .cat model) | modelFile: [lsearch -inline [plugins -all NodeName.cat] *.cat] | useGPU: {parent.useGPU} | Reformat (uprez back to original) | reference Input1 dimensions: Input1.width, Input1.height | ChannelCopy (depth.alpha -> depth.Z) | Grade (near/far adjustment) | white: {parent.near - parent.far} | black: {parent.far} | [Invert] (optional, controlled by invert_map knob) | Switch (output mode selection) ├─ input 0: Depth grayscale ├─ input 1: False Color (Ramp gradient + STMap lookup) ├─ input 2: Confidence map └─ input 3: Metric depth | Output1 ``` ### False Color Visualization Build a Turbo/viridis colormap gradient texture using stacked Ramp nodes, then use the depth map as UV coordinates via STMap: ``` Constant (512x64) | Ramp1 (blue->cyan) | Ramp2 (cyan->green) | Ramp3 (green->yellow) | Ramp4 (yellow->red) | STMap (use depth as U coordinate) | uv: depth channel ``` ### Expression Gotchas - Enum knobs: compare by NUMERIC INDEX, not string - correct: `parent.process_res == 0` (for "Auto") - wrong: `parent.process_res == "Auto"` - TCL model path: `[lsearch -inline [plugins -all Name.cat] *.cat]` - Reference parent input: `Input1.width` not `width` - Downrez scale: `pow(2, parent.downrez * -1.0)` ## .cat File Format .cat files are NOT just renamed .pt files. They have a FlatBuffers header (~136 bytes) prepended before the TorchScript zip archive. This header encodes channel mappings, scale factors, model ID, and zip offsets. The ONLY way to create a valid .cat file is through Nuke's CatFileCreator node. Manual zip repacking will produce "Unrecognized filetype" errors. ## .cat File Creation Requires NukeX or Nuke Studio (CatFileCreator node not in Nuke NC): 1. torch.jit.script your model -> save as .pt 2. In NukeX, create CatFileCreator node 3. Set torchScriptFile to your .pt file 4. Set catFile to desired output path 5. Set channelsIn (e.g., "rgba.red rgba.green rgba.blue") 6. Set channelsOut (e.g., "rgba.alpha") 7. Set modelId 8. Click "Create .cat file and Inference" Script via nuke -t: ```python import nuke cat = nuke.createNode('CatFileCreator', inpanel=False) cat['torchScriptFile'].setValue('/path/to/model.pt') cat['catFile'].setValue('/path/to/output.cat') cat['modelId'].setValue('NodeName') cat['channelsIn'].setValue('rgba.red rgba.green rgba.blue') cat['channelsOut'].setValue('rgba.alpha') cat.knob('createCatFile').execute() ``` ## Distribution End users just need: 1. Copy `NodeName/` folder to `~/.nuke/Cattery/` 2. Restart Nuke (or Cattery > Update) 3. Node appears in Cattery menu For studio deployment: - Set NUKE_PATH to shared network location - Place Cattery folder in shared path - All artists get the node automatically