Purpose: a fresh conversation picks this up and continues with zero re-investigation and zero conflict. Read this file top to bottom before touching anything. Update the STATE table as you go — that table is the contract between sessions.
| batch | scope | status | landed |
|---|---|---|---|
| 1a | LoadEXRMEC via OpenImageIO |
DONE | CNP main 881f06b |
| 1b | SaveEXRMEC via OIIO — compression, bit depth, AOV write, metadata |
DONE | CNP e2a6dcf |
| 1c | Channel/AOV shuffle + metadata nodes | not started | — |
| 2 | OCIO colour tier — CameraLogConvert, CameraGamutConvert, ColorMatrix, LUTApply, OCIOCDLTransform, OCIOFileTransform, OCIOLookTransform, OCIOConfigInfo | DONE | NukeMax (this session) |
| 3 | Grade tier — ColorLookup, SoftClip, HueCorrect | DONE | NukeMax (this session) |
| 4 | Transform/Filter tier — MotionBlur, Matrix, FrameHold | DONE | NukeMax (this session) |
| 5 | Merge/Keying tier — Copy, HueKeyer | DONE | NukeMax (this session) |
Batch 2-5 verification: 12/12 functional checks pass (log round-trip,
gamut identity, matrix identity, lookup identity, SoftClip asymptote,
FrameHold freeze, Matrix identity kernel, Copy channel routing, HueKeyer
matte range, HDR/negative preservation). Measured, not read from code.
Shared helpers: _comp_linear.py (linear-float ops), _primaries.py
(derived gamut matrices), _curves.py (camera log curves), _cube.py
(.cube LUT parser). essentials/init.py updated to the linear-float
contract (no colour clamping; mattes still bounded 0..1).
Still open: 1c (Channel/AOV shuffle + metadata nodes) — the AOV plumbing becomes usable in a graph rather than just correct at the file boundary.
Target pack for ALL of it: ComfyUI-NukeMaxNodes.
Exception already shipped: LoadEXRMEC lives in ComfyUI-CustomNodePacks/nodes/exr_io.py
because it already existed there. Do NOT duplicate it into NukeMax — extend in place.
- ComfyUI python:
D:/PROJECT/ComfyUI_windows_portable/comfy_env/python.exeThe plain shell python has no torch. Always use the full path. - Installed and verified this program:
OpenImageIO 3.1.16,PyOpenColorIO 2.5.2. OIIO CLI (oiiotool,maketx,idiff) is incomfy_env\Scripts, not on PATH. - cv2 CANNOT read EXR in this environment. OpenCV's OpenEXR codec is
disabled unless
OPENCV_IO_ENABLE_OPENEXR=1is set before cv2 is imported. Every reference pack reads EXR throughcv.imread, so they fail here. This is the measured justification for rebuilding rather than vendoring. - ComfyUI
IMAGE=torch.float32 [B,H,W,C], values 0..1 nominal but not clamped for linear/HDR work.MASK=[B,H,W].
D:/PROJECT/Custom_Nodes/third_party/
| pack | registered nodes | what it is good for |
|---|---|---|
ComfyUI-OCIO |
12 | thin but correct PyOpenColorIO wrapper |
radiance |
87 | ACES config mgmt, ARRI/DaVinci gamuts, view transforms |
nuke-nodes-comfyui |
32 | the comp operator set |
= 131 nodes. Counted by AST-parsing NODE_CLASS_MAPPINGS. Do NOT count by
grepping "key": — that returns 261/824/234, which is wrong and will mislead
the estimate.
The gap none of them close: named EXR channels / AOVs. All three read
RGB(A) and drop diffuse.R, N.x, depth.Z, cryptomatte. That is the
differentiator for this program.
- Linear float, never clip. Do not clamp to 0..1 anywhere in an IO or colour node. Clamping on load destroys speculars and emissives.
- Model-agnostic means contract-agnostic. Arbitrary resolution, arbitrary channel count, no assumption of 8-bit sRGB 512x512. "Works with every HuggingFace model" is not verifiable and is not the target; this is.
- No silent fallback. Every backend downgrade is logged with the reason.
Total failure raises, naming each attempt and the fix (e.g.
pip install OpenImageIO). Silent substitution is how AOVs go missing until delivery. - Surface what you cannot output. If a node returns RGB but the file had
11 channels, the channel list, compression and metadata go out in an
infooutput. Never drop information without saying so. IS_CHANGEDmust hash real inputs — neverfloat("nan").- Standard ComfyUI conventions:
INPUT_TYPES,RETURN_TYPES,FUNCTION,CATEGORY,NODE_CLASS_MAPPINGS. No new mandatory dependency beyond OIIO and OCIO, which are installed.
Every claim gets a measured number, never a reading of the code.
Round-trip fixture that batch 1 must keep passing — write an 11-channel EXR
(R,G,B,A, diffuse.R/G/B, N.x/y/z, depth.Z) with DWAA compression and a
comment attribute, then assert on read-back:
- all 11 channel names returned
- compression string preserved
- metadata preserved
max > 1.0survives (HDR not clipped)
Baseline already measured for LoadEXRMEC: 11 channels, dwaa,
comment='shot_0010', max=1.007.
- Symbol-exists ≠ file-exists. A previous session shipped
gaze_preflight.pycallingexpected_weight_paths()andsearched_checkpoint_paths()which exist nowhere. Always grep fordef <name>before calling across modules. - Widget order is a contract. ComfyUI matches widgets by POSITION. Deleting
or inserting a widget mid-list shifts every later value in saved workflows.
Hide widgets in JS instead of removing them from
INPUT_TYPES. - Never remove user-facing options without asking. Doing so broke a working setup in this workspace and had to be reverted.
- Do not
\ninside a bash heredoc that writes Python string literals — it becomes a real newline and produces an unterminated string. Build the escape aschr(92)+"n". - Push to
mainvia the worktree pattern with a real import/compile check on the main copy, not the dev copy.
- Wan-Animate gaze control. Architecturally impossible — the face tile is
compressed to ~20 numbers (
motion_dim=20) and the pose conditioning image contains no iris. Do not add gaze features. See thewan-animate-face-ceilingmemory. - PoseAndFaceDetectionV2 widget-count reduction — user has not yet named which gaze/iris outputs they consume, so nothing may be deleted.