File size: 11,104 Bytes
478cb8f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#!/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()