211 lines
5.3 KiB
Markdown
211 lines
5.3 KiB
Markdown
# 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
|