# 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
