#!/usr/bin/env python3 """Generate flux-v2 workflow JSONs: portrait pre-pass, saved crops, hires group, bfl guidance fix.""" import build_workflows as B from build_workflows import G, DEFS, CORE, BYPASS, flux_loaders, redux_branch ESSENTIALS = {"cnr_id": "comfyui_essentials"} DEFS["ImageResize+"] = ([("image", "IMAGE")], [("IMAGE", "IMAGE"), ("width", "INT"), ("height", "INT")], ESSENTIALS, [310, 170]) DEFS["LatentUpscaleBy"] = ([("samples", "LATENT")], [("LATENT", "LATENT")], CORE, [290, 82]) W, H = 896, 1152 # portrait bucket, both /64 HW, HH = 1344, 1728 # hires 1.5x INPUT_SPEC = """ **Optimal inputs** (both refs): 3:4 portrait, 1152x1536, sRGB 8-bit JPEG q90+/PNG, no watermarks/text/borders, EXIF baked. Style ref: keep the style-defining content in the vertical middle (sigclip center-crops to a square — top/bottom ~12% are discarded). Composition ref: clear fg/bg depth separation. Any other input still works — the pre-pass normalizes to 896x1152 (fill/crop) and the exact crop + depth map are saved next to the outputs for auditing.""" def comp_prepass(g, x, y, save_prefix): """LoadImage -> ImageResize+ 896x1152 fill/crop -> depth; saves crop + depth map.""" ci = g.add("LoadImage", ["composition_ref.png", "image"], pos=(x, y), title="Composition reference (Image B)") rs = g.add("ImageResize+", [W, H, "lanczos", "fill / crop", "always", 0], inputs={"image": (ci, 0)}, pos=(x + 420, y + 30), title="Normalize to 896x1152") g.add("SaveImage", [f"{save_prefix}/inputs/comp-crop"], inputs={"images": (rs, 0)}, pos=(x + 420, y + 240), size=[260, 270], title="Save crop (audit)") depth = g.add("DepthAnythingV2Preprocessor", ["depth_anything_v2_vitl.pth", W], inputs={"image": (rs, 0)}, pos=(x + 840, y + 30)) g.add("SaveImage", [f"{save_prefix}/inputs/depth"], inputs={"images": (depth, 0)}, pos=(x + 840, y + 240), size=[260, 270], title="Save depth map (audit)") return ci, rs, depth def build_v2_style_composition(): g = G() g.add("MarkdownNote", [f"""## FLUX Redux + ControlNet — style + composition (v2) v2 changes vs v1: portrait pre-pass (any input → exact **896x1152** fill/crop, so depth map == latent, zero padding), saved input crops + depth maps for auditing, steps 32, and a bypassed **HIRES** group (1.5x latent upscale → second pass at denoise 0.30 with Redux conditioning carried, ControlNet released — enable for ~1344x1728 finals; keeps aesthetic, adds detail). Models & knobs: unchanged from v1 (see `flux-redux-style-composition.json` notes). Redux **multiply 0.35** — NEVER attn_bias with a ControlNet: its attention mask never reaches the CN branch (code-verified), so base and CN fight and bodies deform. Union-Pro-2.0 depth 0.7 / end 0.8 (drop end to 0.6 for more freedom), FluxGuidance **3.0** (sweep-validated; 2.5 softer/filmic, 3.5 punchier), euler/simple/32/CFG 1. Bypassed groups: CANNY stack (0.35), ReduxAdvanced alt, turbo fast-preview, HIRES. {INPUT_SPEC}"""], pos=(-1340, -180), size=[580, 620]) unet, clip, vae = flux_loaders(g) s_img, cv_loader, cv_enc, sm_loader = redux_branch(g, -720, 420) txt = g.add("CLIPTextEncode", ['A photograph of a beautiful woman posing naturally. Her body is anatomically correct, with well-proportioned limbs, natural relaxed hands, and a balanced, graceful posture. The image is sharp and coherent, with realistic skin texture and lighting that matches the scene.'], inputs={"clip": (clip, 0)}, pos=(-300, 60), title="Prompt (generic anatomy default — edit or clear)") guid = g.add("FluxGuidance", [3.0], inputs={"conditioning": (txt, 0)}, pos=(140, 60)) neg = g.add("CLIPTextEncode", ['deformed anatomy, extra limbs, missing limbs, fused or extra fingers, malformed hands, twisted joints, distorted face, mutated body, disfigured, blurry, lowres, jpeg artifacts, watermark, text, logo, oversaturated, plastic skin'], inputs={"clip": (clip, 0)}, pos=(-300, 250), title="Negative (INERT at CFG 1.0 — activates only if CFG > 1)") apply_style = g.add("StyleModelApply", [0.35, "multiply"], inputs={"conditioning": (guid, 0), "style_model": (sm_loader, 0), "clip_vision_output": (cv_enc, 0)}, pos=(140, 300)) 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)") ci, rs, depth = comp_prepass(g, -720, 1000, "flux-v2/style-comp") canny = g.add("Canny", [0.2, 0.5], inputs={"image": (rs, 0)}, pos=(-300, 1560), mode=BYPASS, title="OPTIONAL: canny edges") cn_loader = g.add("ControlNetLoader", ["FLUX.1-dev-ControlNet-Union-Pro-2.0.safetensors"], pos=(300, 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=(700, 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=(700, 560), mode=BYPASS, title="OPTIONAL: Apply ControlNet — CANNY (stack)") 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, W, H], inputs={"model": (turbo, 0)}, pos=(140, -160)) latent = g.add("EmptySD3LatentImage", [W, H, 1], pos=(700, 950)) ks = g.add("KSampler", [0, "randomize", 32, 1.0, "euler", "simple", 1.0], inputs={"model": (msf, 0), "positive": (cn_canny, 0), "negative": (cn_canny, 1), "latent_image": (latent, 0)}, pos=(1120, 300)) dec = g.add("VAEDecode", inputs={"samples": (ks, 0), "vae": (vae, 0)}, pos=(1460, 300)) g.add("SaveImage", ["flux-v2/style-comp"], inputs={"images": (dec, 0)}, pos=(1460, 420)) # HIRES group (all bypassed): 1.5x latent -> refine at denoise 0.30, Redux kept, CN released up = g.add("LatentUpscaleBy", ["bislerp", 1.5], inputs={"samples": (ks, 0)}, pos=(1120, 660), mode=BYPASS, title="HIRES: 1.5x latent") msf2 = g.add("ModelSamplingFlux", [1.15, 0.5, HW, HH], inputs={"model": (turbo, 0)}, pos=(1120, 800), mode=BYPASS, title="HIRES: shift @1344x1728") ks2 = g.add("KSampler", [0, "randomize", 32, 1.0, "euler", "simple", 0.30], inputs={"model": (msf2, 0), "positive": (apply_style, 0), "negative": (neg, 0), "latent_image": (up, 0)}, pos=(1460, 660), mode=BYPASS, title="HIRES: refine pass (denoise 0.30)") dec2 = g.add("VAEDecode", inputs={"samples": (ks2, 0), "vae": (vae, 0)}, pos=(1800, 660), mode=BYPASS) g.add("SaveImage", ["flux-v2/style-comp-hires"], inputs={"images": (dec2, 0)}, pos=(1800, 780), mode=BYPASS, title="HIRES: save") g.group("STYLE REFERENCE — Redux", -740, 340, 1220, 700, "#3f789e") g.group("COMPOSITION REFERENCE — 896x1152 pre-pass", -740, 900, 1500, 840, "#8f5b34") g.group("SAMPLING", 1080, 200, 800, 380, "#444") g.group("HIRES (bypassed — enable all 5)", 1080, 600, 1100, 520, "#4a6b4a") g.dump(f"{B.OUT}/flux-v2-redux-style-composition.json") def build_v2_bfl_lora(): g = G() g.add("MarkdownNote", [f"""## FLUX Redux + official BFL Depth LoRA (v2 — A/B variant) v2 changes vs v1: portrait pre-pass (896x1152 fill/crop — critical here: with latent-concat conditioning any padding becomes image content), saved crop + depth map, steps 32, and **FluxGuidance 10.0 + LoRA 0.85** (BFL spec — the LoRA is distilled at guidance 10; earlier lower-guidance results were an artifact of the attn_bias bug). Redux multiply 0.35, never attn_bias (see style-composition notes). No canny stacking possible in this method. {INPUT_SPEC}"""], pos=(-1340, -180), size=[580, 560]) unet, clip, vae = flux_loaders(g) s_img, cv_loader, cv_enc, sm_loader = redux_branch(g, -720, 420) txt = g.add("CLIPTextEncode", ['A photograph of a beautiful woman posing naturally. Her body is anatomically correct, with well-proportioned limbs, natural relaxed hands, and a balanced, graceful posture. The image is sharp and coherent, with realistic skin texture and lighting that matches the scene.'], inputs={"clip": (clip, 0)}, pos=(-300, 60), title="Prompt (generic anatomy default — edit or clear)") guid = g.add("FluxGuidance", [10.0], inputs={"conditioning": (txt, 0)}, pos=(140, 60)) neg = g.add("CLIPTextEncode", ['deformed anatomy, extra limbs, missing limbs, fused or extra fingers, malformed hands, twisted joints, distorted face, mutated body, disfigured, blurry, lowres, jpeg artifacts, watermark, text, logo, oversaturated, plastic skin'], inputs={"clip": (clip, 0)}, pos=(-300, 250), title="Negative (INERT at CFG 1.0 — activates only if CFG > 1)") apply_style = g.add("StyleModelApply", [0.35, "multiply"], inputs={"conditioning": (guid, 0), "style_model": (sm_loader, 0), "clip_vision_output": (cv_enc, 0)}, pos=(140, 300)) ci, rs, depth = comp_prepass(g, -720, 1000, "flux-v2/style-comp-bfl") ip2p = g.add("InstructPixToPixConditioning", inputs={"positive": (apply_style, 0), "negative": (neg, 0), "vae": (vae, 0), "pixels": (depth, 0)}, pos=(700, 300)) dlora = g.add("LoraLoaderModelOnly", ["flux1-depth-dev-lora.safetensors", 0.85], inputs={"model": (unet, 0)}, pos=(-300, -160), title="BFL Depth LoRA") msf = g.add("ModelSamplingFlux", [1.15, 0.5, W, H], inputs={"model": (dlora, 0)}, pos=(140, -160)) ks = g.add("KSampler", [0, "randomize", 32, 1.0, "euler", "simple", 1.0], inputs={"model": (msf, 0), "positive": (ip2p, 0), "negative": (ip2p, 1), "latent_image": (ip2p, 2)}, pos=(1120, 300)) dec = g.add("VAEDecode", inputs={"samples": (ks, 0), "vae": (vae, 0)}, pos=(1460, 300)) g.add("SaveImage", ["flux-v2/style-comp-bfl"], inputs={"images": (dec, 0)}, pos=(1460, 420)) g.group("STYLE REFERENCE — Redux", -740, 340, 1220, 700, "#3f789e") g.group("COMPOSITION — 896x1152 pre-pass + BFL Depth LoRA", -740, 900, 1500, 620, "#8f5b34") g.dump(f"{B.OUT}/flux-v2-redux-style-composition-bfl-lora.json") build_v2_style_composition() build_v2_bfl_lora()