File size: 7,442 Bytes
695fbf0
 
dc64aaa
 
 
 
 
 
 
 
 
 
 
 
 
 
695fbf0
 
dc64aaa
695fbf0
 
 
 
 
 
dc64aaa
 
695fbf0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dc64aaa
 
 
 
 
 
 
695fbf0
 
 
 
 
 
 
 
dc64aaa
 
 
 
 
695fbf0
 
dc64aaa
695fbf0
 
dc64aaa
695fbf0
dc64aaa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
155
156
157
158
159
160
161
from __future__ import annotations

"""MG_SuperSimple: orchestrates a 1..4 step pipeline over CF -> CADE pairs.

Step 1: CADE with the "Step 1" preset (denoise is forced to 1.0).
Steps 2..N: ControlFusion (CF) with the "Step N" preset, then CADE with the same "Step N" preset.
When custom=True: visible CADE controls (seed/steps/cfg/denoise/sampler/scheduler/clipseg_text) override the corresponding Step presets across all steps (Step 1 still uses denoise=1.0).
When custom=False: CADE values come from Step presets; node UI values are ignored. CF always uses its Step presets (kept minimal here).
Inputs:

model/vae/latent/positive/negative: standard Comfy connectors
control_net: ControlNet module for CF (required)
reference_image/clip_vision: forwarded into CADE (optional)
Outputs:

(LATENT, IMAGE) from the final executed step
"""


import torch

from .mg_cade25_easy import ComfyAdaptiveDetailEnhancer25 as _CADE
from .mg_controlfusion_easy import MG_ControlFusion as _CF
from .mg_cade25_easy import _sampler_names as _sampler_names
from .mg_cade25_easy import _scheduler_names as _scheduler_names
import comfy.model_management as model_management
from ..hard.mg_upscale_module import clear_gpu_and_ram_cache


class MG_SuperSimple:
    CATEGORY = "MagicNodes/Easy"

    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                # High-level pipeline control
                "step_count": ("INT", {"default": 4, "min": 1, "max": 4, "tooltip": "Number of steps to run (1..4)."}),
                "custom": ("BOOLEAN", {"default": False, "tooltip": "When enabled, CADE UI values below override Step presets across all steps (denoise on Step 1 is still forced to 1.0)."}),

                # Connectors
                "model": ("MODEL", {}),
                "positive": ("CONDITIONING", {}),
                "negative": ("CONDITIONING", {}),
                "vae": ("VAE", {}),
                "latent": ("LATENT", {}),
                "control_net": ("CONTROL_NET", {"tooltip": "ControlNet module used by ControlFusion."}),

                # Shared CADE surface controls
                "seed": ("INT", {"default": 0, "min": 0, "max": 0xFFFFFFFFFFFFFFFF, "control_after_generate": True, "tooltip": "Seed 0 = SmartSeed (Sobol + light probe). Non-zero = fixed seed (deterministic)."}),
                "steps": ("INT", {"default": 25, "min": 1, "max": 10000, "tooltip": "KSampler steps for CADE (applies to all steps)."}),
                "cfg": ("FLOAT", {"default": 4.5, "min": 0.0, "max": 100.0, "step": 0.1}),
                # Denoise is clamped; Step 1 uses 1.0 regardless
                "denoise": ("FLOAT", {"default": 0.65, "min": 0.35, "max": 0.9, "step": 0.0001}),
                "sampler_name": (_sampler_names(), {"default": _sampler_names()[0]}),
                "scheduler": (_scheduler_names(), {"default": "MGHybrid"}),
                "clipseg_text": ("STRING", {"default": "hand, feet, face", "multiline": False, "tooltip": "Focus terms for CLIPSeg (comma-separated)."}),
            },
            "optional": {
                "reference_image": ("IMAGE", {}),
                "clip_vision": ("CLIP_VISION", {}),
            },
        }

    RETURN_TYPES = ("LATENT", "IMAGE")
    RETURN_NAMES = ("LATENT", "IMAGE")
    FUNCTION = "run"

    def _cade(self,
              preset_step: str,
              custom_override: bool,
              model, vae, positive, negative, latent,
              seed: int, steps: int, cfg: float, denoise: float,
              sampler_name: str, scheduler: str,
              clipseg_text: str,
              reference_image=None, clip_vision=None):
        # CADE core call mirrors CADEEasyUI -> apply_cade2
        lat, img, _s, _c, _d, _mask = _CADE().apply_cade2(
            model, vae, positive, negative, latent,
            int(seed), int(steps), float(cfg), float(denoise),
            str(sampler_name), str(scheduler), 0.0,
            preset_step=str(preset_step), custom_override=bool(custom_override),
            clipseg_text=str(clipseg_text),
            reference_image=reference_image, clip_vision=clip_vision,
        )
        return lat, img

    def _cf(self,
            preset_step: str,
            image, positive, negative, control_net, vae):
        # Keep CF strictly on presets for SuperSimple (no extra UI),
        # so pass custom_override=False intentionally.
        pos, neg, _prev = _CF().apply(
            image=image, positive=positive, negative=negative,
            control_net=control_net, vae=vae,
            preset_step=str(preset_step), custom_override=False,
        )
        return pos, neg

    def run(self,
             step_count, custom,
             model, positive, negative, vae, latent, control_net,
             seed, steps, cfg, denoise, sampler_name, scheduler, clipseg_text,
             reference_image=None, clip_vision=None):
        # Cooperative cancel before any heavy work
        model_management.throw_exception_if_processing_interrupted()

        # Clamp step_count to 1..4
        n = int(max(1, min(4, step_count)))

        cur_latent = latent
        cur_image = None
        cur_pos = positive
        cur_neg = negative

        try:
            # Step 1: CADE with Step 1 preset, denoise forced to 1.0
            denoise_step1 = 1.0
            lat1, img1 = self._cade(
                preset_step="Step 1",
                custom_override=bool(custom),
                model=model, vae=vae, positive=cur_pos, negative=cur_neg, latent=cur_latent,
                seed=seed, steps=steps, cfg=cfg, denoise=denoise_step1,
                sampler_name=sampler_name, scheduler=scheduler,
                clipseg_text=clipseg_text,
                reference_image=reference_image, clip_vision=clip_vision,
            )
            cur_latent, cur_image = lat1, img1

            # Steps 2..n: CF -> CADE per step
            for i in range(2, n + 1):
                # allow user cancel between steps
                model_management.throw_exception_if_processing_interrupted()
                # ControlFusion on current image/conds
                cur_pos, cur_neg = self._cf(
                    preset_step=f"Step {i}",
                    image=cur_image, positive=cur_pos, negative=cur_neg,
                    control_net=control_net, vae=vae,
                )
                # CADE with shared controls
                # If no external reference_image is provided, use the previous step image
                # so that reference_clean / CLIP-Vision gating can take effect.
                ref_img = reference_image if (reference_image is not None) else cur_image
                lat_i, img_i = self._cade(
                    preset_step=f"Step {i}",
                    custom_override=bool(custom),
                    model=model, vae=vae, positive=cur_pos, negative=cur_neg, latent=cur_latent,
                    seed=seed, steps=steps, cfg=cfg, denoise=denoise,
                    sampler_name=sampler_name, scheduler=scheduler,
                    clipseg_text=clipseg_text,
                    reference_image=ref_img, clip_vision=clip_vision,
                )
                cur_latent, cur_image = lat_i, img_i
            return (cur_latent, cur_image)
        finally:
            # try to free memory on cancel or normal exit
            try:
                clear_gpu_and_ram_cache()
            except Exception:
                pass