ComfyUI / redux_experiment_final /scripts /build_workflows.py
aleph65's picture
Archive flux-redux experiment: final writeup, workflows, inputs, scripts, research, v4 outputs, comparison sheets, Claude memories
478cb8f verified
Raw
History Blame Contribute Delete
25.1 kB
#!/usr/bin/env python3
"""Generate the flux-redux workflow JSON drafts (ComfyUI graph format 0.4)."""
import json, os
OUT = "/workspace/workflows/wip"
os.makedirs(OUT, exist_ok=True)
CORE = {"cnr_id": "comfy-core", "ver": "0.27.0"}
AUX = {"cnr_id": "comfyui_controlnet_aux"}
REFLUX = {"aux_id": "kaibioinfo/ComfyUI_AdvancedRefluxControl"}
# node type -> (link_inputs [(name,TYPE)...], outputs [(name,TYPE)...], pack_props, default_size)
DEFS = {
"MarkdownNote": ([], [], CORE, [520, 460]),
"UNETLoader": ([], [("MODEL", "MODEL")], CORE, [340, 82]),
"DualCLIPLoader": ([], [("CLIP", "CLIP")], CORE, [340, 130]),
"VAELoader": ([], [("VAE", "VAE")], CORE, [340, 58]),
"CLIPVisionLoader": ([], [("CLIP_VISION", "CLIP_VISION")], CORE, [340, 58]),
"LoadImage": ([], [("IMAGE", "IMAGE"), ("MASK", "MASK")], CORE, [274, 314]),
"CLIPVisionEncode": ([("clip_vision", "CLIP_VISION"), ("image", "IMAGE")],
[("CLIP_VISION_OUTPUT", "CLIP_VISION_OUTPUT")], CORE, [290, 78]),
"StyleModelLoader": ([], [("STYLE_MODEL", "STYLE_MODEL")], CORE, [340, 58]),
"StyleModelApply": ([("conditioning", "CONDITIONING"), ("style_model", "STYLE_MODEL"),
("clip_vision_output", "CLIP_VISION_OUTPUT")],
[("CONDITIONING", "CONDITIONING")], CORE, [320, 102]),
"ReduxAdvanced": ([("conditioning", "CONDITIONING"), ("style_model", "STYLE_MODEL"),
("clip_vision", "CLIP_VISION"), ("image", "IMAGE"), ("mask", "MASK")],
[("CONDITIONING", "CONDITIONING"), ("IMAGE", "IMAGE"), ("MASK", "MASK")],
REFLUX, [320, 190]),
"CLIPTextEncode": ([("clip", "CLIP")], [("CONDITIONING", "CONDITIONING")], CORE, [400, 160]),
"FluxGuidance": ([("conditioning", "CONDITIONING")], [("CONDITIONING", "CONDITIONING")], CORE, [290, 58]),
"ConditioningZeroOut": ([("conditioning", "CONDITIONING")], [("CONDITIONING", "CONDITIONING")], CORE, [290, 48]),
"ControlNetLoader": ([], [("CONTROL_NET", "CONTROL_NET")], CORE, [380, 58]),
"ControlNetApplySD3": ([("positive", "CONDITIONING"), ("negative", "CONDITIONING"),
("control_net", "CONTROL_NET"), ("vae", "VAE"), ("image", "IMAGE")],
[("positive", "CONDITIONING"), ("negative", "CONDITIONING")], CORE, [320, 166]),
"Canny": ([("image", "IMAGE")], [("IMAGE", "IMAGE")], CORE, [290, 82]),
"DepthAnythingV2Preprocessor": ([("image", "IMAGE")], [("IMAGE", "IMAGE")], AUX, [330, 82]),
"EmptySD3LatentImage": ([], [("LATENT", "LATENT")], CORE, [290, 106]),
"ModelSamplingFlux": ([("model", "MODEL")], [("MODEL", "MODEL")], CORE, [300, 130]),
"LoraLoaderModelOnly": ([("model", "MODEL")], [("MODEL", "MODEL")], CORE, [340, 82]),
"KSampler": ([("model", "MODEL"), ("positive", "CONDITIONING"), ("negative", "CONDITIONING"),
("latent_image", "LATENT")], [("LATENT", "LATENT")], CORE, [300, 262]),
"RandomNoise": ([], [("NOISE", "NOISE")], CORE, [290, 82]),
"KSamplerSelect": ([], [("SAMPLER", "SAMPLER")], CORE, [290, 58]),
"BasicScheduler": ([("model", "MODEL")], [("SIGMAS", "SIGMAS")], CORE, [290, 106]),
"BasicGuider": ([("model", "MODEL"), ("conditioning", "CONDITIONING")],
[("GUIDER", "GUIDER")], CORE, [240, 66]),
"SamplerCustomAdvanced": ([("noise", "NOISE"), ("guider", "GUIDER"), ("sampler", "SAMPLER"),
("sigmas", "SIGMAS"), ("latent_image", "LATENT")],
[("output", "LATENT"), ("denoised_output", "LATENT")], CORE, [270, 126]),
"InstructPixToPixConditioning": ([("positive", "CONDITIONING"), ("negative", "CONDITIONING"),
("vae", "VAE"), ("pixels", "IMAGE")],
[("positive", "CONDITIONING"), ("negative", "CONDITIONING"),
("latent", "LATENT")], CORE, [300, 106]),
"VAEDecode": ([("samples", "LATENT"), ("vae", "VAE")], [("IMAGE", "IMAGE")], CORE, [210, 66]),
"SaveImage": ([("images", "IMAGE")], [], CORE, [400, 400]),
"PreviewImage": ([("images", "IMAGE")], [], CORE, [300, 300]),
}
BYPASS = 4
class G:
def __init__(self):
self.nodes, self.links, self.groups = [], [], []
self.nid, self.lid = 0, 0
def add(self, type_, widgets=None, inputs=None, pos=(0, 0), size=None, mode=0, title=None):
"""inputs: {slot_name: (src_node_dict, src_slot_index)}"""
link_ins, outs, pack, dsize = DEFS[type_]
self.nid += 1
node = {
"id": self.nid, "type": type_, "pos": list(pos),
"size": list(size or dsize), "flags": {}, "order": len(self.nodes),
"mode": mode,
"inputs": [], "outputs": [],
"properties": {"Node name for S&R": type_, **pack},
}
if title:
node["title"] = title
if widgets is not None:
node["widgets_values"] = list(widgets)
for name, typ in link_ins:
entry = {"name": name, "type": typ, "link": None}
src = (inputs or {}).get(name)
if src is not None:
src_node, src_slot = src
self.lid += 1
entry["link"] = self.lid
self.links.append([self.lid, src_node["id"], src_slot, self.nid,
len(node["inputs"]), typ])
src_node["outputs"][src_slot].setdefault("links", []).append(self.lid)
node["inputs"].append(entry)
for i, (name, typ) in enumerate(outs):
node["outputs"].append({"name": name, "type": typ, "links": [], "slot_index": i})
self.nodes.append(node)
return node
def group(self, title, x, y, w, h, color="#3f789e"):
self.groups.append({"id": len(self.groups) + 1, "title": title,
"bounding": [x, y, w, h], "color": color,
"font_size": 24, "flags": {}})
def dump(self, path):
doc = {"id": "00000000-0000-0000-0000-000000000000", "revision": 0,
"last_node_id": self.nid, "last_link_id": self.lid,
"nodes": self.nodes, "links": self.links, "groups": self.groups,
"config": {}, "extra": {}, "version": 0.4}
with open(path, "w") as f:
json.dump(doc, f, indent=2)
print("wrote", path, f"({len(self.nodes)} nodes, {len(self.links)} links)")
def flux_loaders(g, unet="flux1-dev.safetensors", x=-720):
unet_n = g.add("UNETLoader", [unet, "default"], pos=(x, -80))
clip = g.add("DualCLIPLoader", ["t5xxl_fp16.safetensors", "clip_l.safetensors", "flux", "default"], pos=(x, 60))
vae = g.add("VAELoader", ["ae.safetensors"], pos=(x, 250))
return unet_n, clip, vae
def redux_branch(g, x, y, img_name="style_ref.png"):
img = g.add("LoadImage", [img_name, "image"], pos=(x, y), title="Style reference (Image A)")
cv_loader = g.add("CLIPVisionLoader", ["sigclip_vision_patch14_384.safetensors"], pos=(x, y + 360))
enc = g.add("CLIPVisionEncode", ["center"], inputs={"clip_vision": (cv_loader, 0), "image": (img, 0)},
pos=(x + 380, y + 60))
sm = g.add("StyleModelLoader", ["flux1-redux-dev.safetensors"], pos=(x, y + 450))
return img, cv_loader, enc, sm
# ---------------------------------------------------------------- #1 priority
def build_style_composition():
g = G()
g.add("MarkdownNote", ["""## FLUX Redux + ControlNet β€” style + composition (v1 draft)
**Image A (style)** supplies aesthetic/palette/lighting via **FLUX.1 Redux**; **Image B
(composition)** supplies layout via **Depth** (Shakker-Labs ControlNet **Union-Pro-2.0** β€” mode
inferred from the control image, do NOT add SetUnionControlNetType). Optional text prompt.
**Models** (HF `aleph65/ComfyUI`):
- `diffusion_models/flux1-dev.safetensors` (bf16) Β· `text_encoders/t5xxl_fp16.safetensors` +
`clip_l.safetensors` Β· `vae/ae.safetensors`
- `style_models/flux1-redux-dev.safetensors` Β· `clip_vision/sigclip_vision_patch14_384.safetensors`
- `controlnet/FLUX.1-dev-ControlNet-Union-Pro-2.0.safetensors`
- bypassed extras: `loras/flux1-turbo-alpha.safetensors`
- `depth_anything_v2_vitl.pth` auto-downloads into `custom_nodes/comfyui_controlnet_aux/ckpts/`
**Knobs**
- *Apply Style Model* `strength` = Redux influence, `attn_bias` mode: 0.3–0.7 (start 0.5).
Raise if style too weak; lower (or use the ReduxAdvanced group) if Image A's layout leaks.
- *Depth ControlNet* strength 0.6–0.8, end_percent 0.8 (official 2.0 rec: 0.8/0.8).
- **Canny group (bypassed)**: enable to stack hard contour lock at LOW strength 0.25–0.4, or
use instead of depth (rewire). Union-Pro-2.0 handles both from the same loader.
- **ReduxAdvanced group (bypassed)**: alternative style isolation via token downsampling
(factor 3). To use: connect its CONDITIONING output to the depth Apply ControlNet `positive`
(replacing Apply Style Model) and enable the node.
- **Turbo group (bypassed)**: enable turbo-alpha LoRA + set steps to 8 for fast previews.
- Sampler: euler / simple / 28 steps / CFG 1.0 / FluxGuidance 3.5 / denoise 1.0.
Prompt may be empty; a short scene description usually helps. Match latent aspect to Image B.
"""], pos=(-1340, -120), size=[580, 700])
unet, clip, vae = flux_loaders(g)
# style branch
s_img, cv_loader, cv_enc, sm_loader = redux_branch(g, -720, 420)
# text branch
txt = g.add("CLIPTextEncode", [""], inputs={"clip": (clip, 0)}, pos=(-300, 60),
title="Prompt (optional β€” may stay empty)")
guid = g.add("FluxGuidance", [3.5], inputs={"conditioning": (txt, 0)}, pos=(140, 60))
neg = g.add("ConditioningZeroOut", inputs={"conditioning": (guid, 0)}, pos=(140, 170))
apply_style = g.add("StyleModelApply", [0.5, "attn_bias"],
inputs={"conditioning": (guid, 0), "style_model": (sm_loader, 0),
"clip_vision_output": (cv_enc, 0)}, pos=(140, 300))
# ALT: ReduxAdvanced (bypassed, output unconnected)
radv = g.add("ReduxAdvanced", [3, "area", "center crop (square)", 1.0, 0.1],
inputs={"conditioning": (guid, 0), "style_model": (sm_loader, 0),
"clip_vision": (cv_loader, 0), "image": (s_img, 0)},
pos=(140, 520), mode=BYPASS, title="ALT: ReduxAdvanced (rewire to use)")
# composition branch
c_img = g.add("LoadImage", ["composition_ref.png", "image"], pos=(-720, 1000),
title="Composition reference (Image B)")
depth = g.add("DepthAnythingV2Preprocessor", ["depth_anything_v2_vitl.pth", 1024],
inputs={"image": (c_img, 0)}, pos=(-300, 1030))
depth_prev = g.add("PreviewImage", inputs={"images": (depth, 0)}, pos=(-300, 1180),
title="Depth map preview")
canny = g.add("Canny", [0.2, 0.5], inputs={"image": (c_img, 0)}, pos=(-300, 1540),
mode=BYPASS, title="OPTIONAL: canny edges")
cn_loader = g.add("ControlNetLoader", ["FLUX.1-dev-ControlNet-Union-Pro-2.0.safetensors"],
pos=(140, 950))
cn_depth = g.add("ControlNetApplySD3", [0.7, 0.0, 0.8],
inputs={"positive": (apply_style, 0), "negative": (neg, 0),
"control_net": (cn_loader, 0), "vae": (vae, 0), "image": (depth, 0)},
pos=(560, 300), title="Apply ControlNet β€” DEPTH")
cn_canny = g.add("ControlNetApplySD3", [0.35, 0.0, 0.6],
inputs={"positive": (cn_depth, 0), "negative": (cn_depth, 1),
"control_net": (cn_loader, 0), "vae": (vae, 0), "image": (canny, 0)},
pos=(560, 560), mode=BYPASS, title="OPTIONAL: Apply ControlNet β€” CANNY (stack)")
# model chain + sampling
turbo = g.add("LoraLoaderModelOnly", ["flux1-turbo-alpha.safetensors", 1.0],
inputs={"model": (unet, 0)}, pos=(-300, -160), mode=BYPASS,
title="FAST PREVIEW: turbo LoRA (set steps 8)")
msf = g.add("ModelSamplingFlux", [1.15, 0.5, 1024, 1024], inputs={"model": (turbo, 0)},
pos=(140, -160))
latent = g.add("EmptySD3LatentImage", [1024, 1024, 1], pos=(560, 950))
ks = g.add("KSampler", [0, "randomize", 28, 1.0, "euler", "simple", 1.0],
inputs={"model": (msf, 0), "positive": (cn_canny, 0), "negative": (cn_canny, 1),
"latent_image": (latent, 0)}, pos=(980, 300))
dec = g.add("VAEDecode", inputs={"samples": (ks, 0), "vae": (vae, 0)}, pos=(1320, 300))
g.add("SaveImage", ["flux-redux/style-comp"], inputs={"images": (dec, 0)}, pos=(1320, 420))
g.group("STYLE REFERENCE β€” Redux", -740, 340, 1220, 700, "#3f789e")
g.group("COMPOSITION REFERENCE β€” Union-Pro-2.0", -740, 900, 1220, 800, "#8f5b34")
g.group("SAMPLING", 940, 200, 800, 700, "#444")
g.dump(f"{OUT}/flux-redux-style-composition.json")
# ---------------------------------------------------------------- #2 fal replica
def build_fal_dev():
g = G()
g.add("MarkdownNote", ["""## FLUX Redux β€” fal `flux/dev/redux` replica (v1 draft)
Official ComfyUI Redux example graph with fal's exact defaults. One image in β†’ variations out.
No safety checker (unlike fal). Doubles as the plain single-image Redux workflow.
**Models** (HF `aleph65/ComfyUI`): `diffusion_models/flux1-dev.safetensors` (bf16, dtype
`default`) Β· `text_encoders/t5xxl_fp16.safetensors` + `clip_l.safetensors` Β· `vae/ae.safetensors`
Β· `style_models/flux1-redux-dev.safetensors` Β· `clip_vision/sigclip_vision_patch14_384.safetensors`
**fal parity settings** β€” prompt empty (fal dev endpoint passes none), FluxGuidance 3.5
(= guidance_scale), euler / simple / 28 steps / denoise 1.0, 768x1024 (= portrait_4_3),
Apply Style Model strength 1.0 / multiply. Seeds do NOT transfer from fal.
**Extras fal doesn't expose**: type a prompt; lower strength (or switch to `attn_bias`) to let
the prompt steer; chain a second Apply Style Model to blend two references.
"""], pos=(-1300, -80), size=[540, 460])
unet, clip, vae = flux_loaders(g)
s_img, cv_loader, cv_enc, sm_loader = redux_branch(g, -720, 420)
txt = g.add("CLIPTextEncode", [""], inputs={"clip": (clip, 0)}, pos=(-300, 60),
title="Prompt (empty = fal parity)")
guid = g.add("FluxGuidance", [3.5], inputs={"conditioning": (txt, 0)}, pos=(140, 60))
apply_style = g.add("StyleModelApply", [1.0, "multiply"],
inputs={"conditioning": (guid, 0), "style_model": (sm_loader, 0),
"clip_vision_output": (cv_enc, 0)}, pos=(140, 200))
msf = g.add("ModelSamplingFlux", [1.15, 0.5, 768, 1024], inputs={"model": (unet, 0)},
pos=(140, -160))
noise = g.add("RandomNoise", [0, "randomize"], pos=(560, -160))
guider = g.add("BasicGuider", inputs={"model": (msf, 0), "conditioning": (apply_style, 0)},
pos=(560, 20))
sampler = g.add("KSamplerSelect", ["euler"], pos=(560, 140))
sched = g.add("BasicScheduler", ["simple", 28, 1.0], inputs={"model": (msf, 0)}, pos=(560, 250))
latent = g.add("EmptySD3LatentImage", [768, 1024, 1], pos=(560, 420))
samp = g.add("SamplerCustomAdvanced",
inputs={"noise": (noise, 0), "guider": (guider, 0), "sampler": (sampler, 0),
"sigmas": (sched, 0), "latent_image": (latent, 0)}, pos=(980, 60))
dec = g.add("VAEDecode", inputs={"samples": (samp, 0), "vae": (vae, 0)}, pos=(1240, 60))
g.add("SaveImage", ["flux-redux/fal-dev"], inputs={"images": (dec, 0)}, pos=(1240, 180))
g.group("STYLE REFERENCE β€” Redux", -740, 340, 940, 700, "#3f789e")
g.group("SAMPLING (fal defaults)", 540, -220, 1100, 760, "#444")
g.dump(f"{OUT}/flux-redux-fal-dev.json")
# ---------------------------------------------------------------- #3 image + prompt
def build_prompt():
g = G()
g.add("MarkdownNote", ["""## FLUX Redux + text prompt (v1 draft)
One style image + a text prompt that keeps authority. Same graph as the fal replica, but Redux
is restrained with **attn_bias 0.5** so the prompt actually steers content β€” at 1.0/multiply
Redux drowns the prompt (that's the known Redux failure mode).
**Models**: same as `flux-redux-fal-dev.json`. Optional fast preview: enable the bypassed
turbo LoRA group (`loras/flux1-turbo-alpha.safetensors`) and set steps 28 β†’ 8.
**Knobs**: strength 0.3–0.7 attn_bias (higher = more style, more subject leakage from the
reference); FluxGuidance 3.5; euler / simple / 28 steps; 1024x1024 (any flux-legal size).
If the reference's composition/subjects still leak: lower strength, or install
`ComfyUI_AdvancedRefluxControl` and swap in ReduxAdvanced (downsampling 3) β€” see the
style-composition workflow for the wiring.
"""], pos=(-1300, -80), size=[540, 420])
unet, clip, vae = flux_loaders(g)
s_img, cv_loader, cv_enc, sm_loader = redux_branch(g, -720, 420)
txt = g.add("CLIPTextEncode", ["describe the scene you want, in the reference's style"],
inputs={"clip": (clip, 0)}, pos=(-300, 60), title="Prompt")
guid = g.add("FluxGuidance", [3.5], inputs={"conditioning": (txt, 0)}, pos=(140, 60))
apply_style = g.add("StyleModelApply", [0.5, "attn_bias"],
inputs={"conditioning": (guid, 0), "style_model": (sm_loader, 0),
"clip_vision_output": (cv_enc, 0)}, pos=(140, 200))
turbo = g.add("LoraLoaderModelOnly", ["flux1-turbo-alpha.safetensors", 1.0],
inputs={"model": (unet, 0)}, pos=(-300, -160), mode=BYPASS,
title="FAST PREVIEW: turbo LoRA (set steps 8)")
msf = g.add("ModelSamplingFlux", [1.15, 0.5, 1024, 1024], inputs={"model": (turbo, 0)},
pos=(140, -160))
noise = g.add("RandomNoise", [0, "randomize"], pos=(560, -160))
guider = g.add("BasicGuider", inputs={"model": (msf, 0), "conditioning": (apply_style, 0)},
pos=(560, 20))
sampler = g.add("KSamplerSelect", ["euler"], pos=(560, 140))
sched = g.add("BasicScheduler", ["simple", 28, 1.0], inputs={"model": (msf, 0)}, pos=(560, 250))
latent = g.add("EmptySD3LatentImage", [1024, 1024, 1], pos=(560, 420))
samp = g.add("SamplerCustomAdvanced",
inputs={"noise": (noise, 0), "guider": (guider, 0), "sampler": (sampler, 0),
"sigmas": (sched, 0), "latent_image": (latent, 0)}, pos=(980, 60))
dec = g.add("VAEDecode", inputs={"samples": (samp, 0), "vae": (vae, 0)}, pos=(1240, 60))
g.add("SaveImage", ["flux-redux/prompt"], inputs={"images": (dec, 0)}, pos=(1240, 180))
g.group("STYLE REFERENCE β€” Redux", -740, 340, 940, 700, "#3f789e")
g.group("SAMPLING", 540, -220, 1100, 760, "#444")
g.dump(f"{OUT}/flux-redux-prompt.json")
# ---------------------------------------------------------------- #4 replicate schnell
def build_schnell():
g = G()
g.add("MarkdownNote", ["""## FLUX Redux schnell β€” Replicate `flux-redux-schnell` replica (v1 draft)
Parity with Replicate's `black-forest-labs/flux-redux-schnell` endpoint: image variations in
**4 steps**, no prompt (their `redux_image` replaces it), no guidance (schnell ignores it β€” the
endpoint schema has none, hence no FluxGuidance node), no safety checker locally.
**Models** (HF `aleph65/ComfyUI`): `diffusion_models/flux1-schnell.safetensors` (bf16,
Apache-2.0, HF repo not gated) Β· `text_encoders/t5xxl_fp16.safetensors` + `clip_l.safetensors`
Β· `vae/ae.safetensors` Β· `style_models/flux1-redux-dev.safetensors` Β·
`clip_vision/sigclip_vision_patch14_384.safetensors`
**Endpoint mapping** β€” aspect_ratio @ ~1MP β†’ EmptySD3LatentImage size (default 1:1 =
1024x1024); num_outputs 1–4 β†’ batch_size; num_inference_steps (max 4) β†’ steps; seed β†’ noise
seed (values do NOT reproduce Replicate's outputs). euler / simple / denoise 1.0 /
Apply Style Model 1.0 multiply. Lower steps = faster + worse, exactly like the endpoint.
"""], pos=(-1300, -80), size=[540, 440])
unet, clip, vae = flux_loaders(g, unet="flux1-schnell.safetensors")
s_img, cv_loader, cv_enc, sm_loader = redux_branch(g, -720, 420)
txt = g.add("CLIPTextEncode", [""], inputs={"clip": (clip, 0)}, pos=(-300, 60),
title="Prompt (endpoint has none β€” leave empty)")
apply_style = g.add("StyleModelApply", [1.0, "multiply"],
inputs={"conditioning": (txt, 0), "style_model": (sm_loader, 0),
"clip_vision_output": (cv_enc, 0)}, pos=(140, 100))
noise = g.add("RandomNoise", [0, "randomize"], pos=(560, -160))
guider = g.add("BasicGuider", inputs={"model": (unet, 0), "conditioning": (apply_style, 0)},
pos=(560, 20))
sampler = g.add("KSamplerSelect", ["euler"], pos=(560, 140))
sched = g.add("BasicScheduler", ["simple", 4, 1.0], inputs={"model": (unet, 0)}, pos=(560, 250))
latent = g.add("EmptySD3LatentImage", [1024, 1024, 1], pos=(560, 420),
title="size = aspect_ratio @ 1MP Β· batch = num_outputs")
samp = g.add("SamplerCustomAdvanced",
inputs={"noise": (noise, 0), "guider": (guider, 0), "sampler": (sampler, 0),
"sigmas": (sched, 0), "latent_image": (latent, 0)}, pos=(980, 60))
dec = g.add("VAEDecode", inputs={"samples": (samp, 0), "vae": (vae, 0)}, pos=(1240, 60))
g.add("SaveImage", ["flux-redux/schnell"], inputs={"images": (dec, 0)}, pos=(1240, 180))
g.group("STYLE REFERENCE β€” Redux", -740, 340, 940, 700, "#3f789e")
g.group("SAMPLING (Replicate defaults, 4 steps)", 540, -220, 1100, 760, "#444")
g.dump(f"{OUT}/flux-redux-schnell.json")
# ---------------------------------------------------------------- #5 BFL depth lora A/B
def build_bfl_lora():
g = G()
g.add("MarkdownNote", ["""## FLUX Redux + official BFL Depth LoRA (v1 draft β€” A/B variant)
Alternative to the Union-Pro-2.0 workflow for maximum structural adherence: BFL's
`flux1-depth-dev-lora` on stock flux1-dev, control image fed via latent-concat
(**InstructPixToPixConditioning**), Redux on the conditioning as usual.
**Models**: dev base + Redux files as in the other workflows, plus
`loras/flux1-depth-dev-lora.safetensors` (HF `black-forest-labs/FLUX.1-Depth-dev-lora`, gated).
**Differences vs the ControlNet version** β€” structure control has NO strength/start/end;
weaken it via the LoRA strength (1.0 = full adherence, ~0.7 looser). No canny stacking.
**FluxGuidance 10** (BFL's depth rec β€” NOT 3.5). Redux attn_bias 0.5 as usual.
Latent size comes from InstructPixToPixConditioning (= control image size) β€” feed a
flux-legal resolution composition image (~1MP).
"""], pos=(-1300, -80), size=[540, 420])
unet, clip, vae = flux_loaders(g)
s_img, cv_loader, cv_enc, sm_loader = redux_branch(g, -720, 420)
txt = g.add("CLIPTextEncode", [""], inputs={"clip": (clip, 0)}, pos=(-300, 60),
title="Prompt (optional)")
guid = g.add("FluxGuidance", [10.0], inputs={"conditioning": (txt, 0)}, pos=(140, 60))
neg = g.add("ConditioningZeroOut", inputs={"conditioning": (guid, 0)}, pos=(140, 170))
apply_style = g.add("StyleModelApply", [0.5, "attn_bias"],
inputs={"conditioning": (guid, 0), "style_model": (sm_loader, 0),
"clip_vision_output": (cv_enc, 0)}, pos=(140, 300))
c_img = g.add("LoadImage", ["composition_ref.png", "image"], pos=(-720, 1000),
title="Composition reference (Image B)")
depth = g.add("DepthAnythingV2Preprocessor", ["depth_anything_v2_vitl.pth", 1024],
inputs={"image": (c_img, 0)}, pos=(-300, 1030))
g.add("PreviewImage", inputs={"images": (depth, 0)}, pos=(-300, 1180), title="Depth map preview")
ip2p = g.add("InstructPixToPixConditioning",
inputs={"positive": (apply_style, 0), "negative": (neg, 0), "vae": (vae, 0),
"pixels": (depth, 0)}, pos=(560, 300))
dlora = g.add("LoraLoaderModelOnly", ["flux1-depth-dev-lora.safetensors", 1.0],
inputs={"model": (unet, 0)}, pos=(-300, -160), title="BFL Depth LoRA")
msf = g.add("ModelSamplingFlux", [1.15, 0.5, 1024, 1024], inputs={"model": (dlora, 0)},
pos=(140, -160))
ks = g.add("KSampler", [0, "randomize", 28, 1.0, "euler", "simple", 1.0],
inputs={"model": (msf, 0), "positive": (ip2p, 0), "negative": (ip2p, 1),
"latent_image": (ip2p, 2)}, pos=(980, 300))
dec = g.add("VAEDecode", inputs={"samples": (ks, 0), "vae": (vae, 0)}, pos=(1320, 300))
g.add("SaveImage", ["flux-redux/style-comp-bfl"], inputs={"images": (dec, 0)}, pos=(1320, 420))
g.group("STYLE REFERENCE β€” Redux", -740, 340, 940, 700, "#3f789e")
g.group("COMPOSITION β€” BFL Depth LoRA", -740, 900, 1220, 500, "#8f5b34")
g.dump(f"{OUT}/flux-redux-style-composition-bfl-lora.json")
build_style_composition()
build_fal_dev()
build_prompt()
build_schnell()
build_bfl_lora()