SAX Structure Lock (SDXL)
Stop SDXL From Melting Your Torso on the Second Pass
- pipe
- PIPE
The failure this fixes
You generate a decent SDXL image. Then you run it through a detailer or push it up a scale factor, and the second pass quietly re-rolls the parts that were already right: the torso anatomy drifts, an arm fuses into a sleeve, the composition slides. A full-image img2img pass at partial denoise has nothing anchoring the layout, so "add detail" turns into "generate a similar image."
SAX Structure Lock (SDXL) is a one-input node that drops a ControlNet structure constraint onto the pipe, so the pack's Detailer and Upscaler passes get a scaffold to hold on to. It's the plumbing answer to a classic fix - ControlNet on your redraw pass - without you building the preprocessor branch by hand.
It's a carrier, not a loader
This is the part worth understanding, because it's why the node looks empty. Structure Lock does not load a ControlNet. It writes a small settings block into PIPE: which checkpoint, which mode, how strong, and over what slice of the sampling steps. The consumers - SAX Detailer, SAX Enhanced Detailer, SAX Upscaler - read that block and, per pass, generate the structure hint internally from the image they're already working on. The detailer hints from its cropped region; the upscaler hints from the upscaled full frame. The ControlNet is applied locally to conditioning inside each consuming node and is never written back to the pipe, so it can't get applied twice.
Two consequences fall straight out of that design:
- Placing the node enables the constraint. Removing it disables it completely. There's no
enabledtoggle and nooffmode - the pack's author is explicit that he refuses to ship "visible but inert" UI. If you want it off, delete the node. - It only affects those img2img passes - nothing happens to your base generation. If anatomy breaks on the first pass, this isn't the tool; fix it at native resolution, because upscaling bakes defects in.
The inputs that matter
controlnet_name- a ControlNet frommodels/controlnet. Use a Union model; xinsir's Union (the "promax" one) is the SDXL standard and covers tile, depth, pose and lineart in a single file. This is also why tile mode works at all: SDXL never got a standalone Tile ControlNet, so Union's tile condition is the route in.mode-tile(default) is a blurred-structure lock and needs zero extra dependencies: the hint is built by downscaling to half, restoring the size and applying a light gaussian blur.depth,openposeandlineartuse real detectors and needcontrolnet-aux.strength- 0.6 is the balanced default. Higher locks structure harder; at the top of the range you've told the redraw to change almost nothing, which looks exactly like "the detailer stopped working."start_percent/end_percent- optional, 0.0 to 1.0, for limiting the constraint to part of the sampling run. They're validated so that an empty range (start ≥ end) is rejected instead of silently doing nothing.
Output is a single PIPE. Slot it between the Loader and the Detailer/Upscaler in the chain.
Note the fail-fast behaviour: running with controlnet_name = None is an error, not a no-op. A ControlNet that can't be loaded is an error too. The author's stated logic is that a placed node is an explicit intent, so it should shout rather than pretend.
Installing it
cd ComfyUI/custom_nodes
git clone https://github.com/so16tm/SAX_Bridge
# restart ComfyUI
ComfyUI Manager → install via Git URL with the same address. Then put a Union ControlNet in ComfyUI/models/controlnet/. If you want depth, openpose or lineart instead of the dependency-free tile:
pip install controlnet-aux
That one is worth flagging: the pack README's dependency table doesn't mention controlnet-aux, but the source imports it for every detector mode, and the first run downloads annotator weights (MiDaS for depth, DWPose for pose, LineartAnime for lines). Detectors are cached per process, and a failed one isn't retried.
Where people get burned
controlnet_name is 'None'/ControlNet 'x' was not found in the controlnet folder. Either you placed the node without picking a model, or the file isn't where ComfyUI expects. Both are placement-time errors, so you find out immediately.openpose(ordepth, orlineart) behaving liketile. That's the fallback chain doing its job: a missingcontrolnet-aux, an unavailable detector, or a detector that found nothing (a blank pose map is treated as a failure) degrades down -openpose→depth→tile,lineart→tile. Check the console for the warning; the image won't tell you.- Cranking strength to 1.2+ and calling it dead. Strong structure constraint plus partial denoise means the pass has almost no room to improve anything. 0.6 first, and tune from there.
- Expecting it on a modern DiT base. The pack is upfront that this is the SDXL path - the constraint isn't wired up for its Krea 2 / DiT loaders, and 2026-era bases get a narrower ControlNet menu anyway, so don't assume the trick ports.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| pipe | PIPE_LINE | — | |
| controlnet_name | COMBO | None | ControlNet model for the structure constraint. Union ControlNet (SDXL) is recommended. |
| mode | COMBO | tile | Structure hint type. tile=blurred-structure lock (no extra deps), depth=depth lock, openpose=pose skeleton lock, lineart=line art lock. depth/openpose/lineart need controlnet_aux and fall back to tile when unavailable. |
| strength | FLOAT | 0.600–1.5 | Structure constraint strength. 0.6=balanced. Higher values lock structure more strongly. |
| start_percentopt | FLOAT | 0.000–1 | Sampling step range start for the ControlNet (0.0=first step). |
| end_percentopt | FLOAT | 1.000–1 | Sampling step range end for the ControlNet (1.0=last step). |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| PIPE | PIPE_LINE | — |