From e55b540899532875eb39ad4a54be9f813fcee689 Mon Sep 17 00:00:00 2001 From: bigcat88 Date: Fri, 25 Jul 2025 20:11:08 +0300 Subject: [PATCH] restore nodes order as it in the V1 version for smaller git diff (2) --- comfy_extras/v3/nodes_custom_sampler.py | 1242 +++++++++++------------ comfy_extras/v3/nodes_flux.py | 43 +- comfy_extras/v3/nodes_freelunch.py | 2 +- comfy_extras/v3/nodes_hidream.py | 56 +- comfy_extras/v3/nodes_hunyuan.py | 90 +- comfy_extras/v3/nodes_hunyuan3d.py | 2 +- comfy_extras/v3/nodes_latent.py | 272 ++--- comfy_extras/v3/nodes_load_3d.py | 2 +- comfy_extras/v3/nodes_lt.py | 2 +- comfy_extras/v3/nodes_optimalsteps.py | 4 +- comfy_extras/v3/nodes_pag.py | 4 +- comfy_extras/v3/nodes_perpneg.py | 4 +- 12 files changed, 864 insertions(+), 859 deletions(-) diff --git a/comfy_extras/v3/nodes_custom_sampler.py b/comfy_extras/v3/nodes_custom_sampler.py index b4e17d922..dca18b6ad 100644 --- a/comfy_extras/v3/nodes_custom_sampler.py +++ b/comfy_extras/v3/nodes_custom_sampler.py @@ -14,130 +14,6 @@ from comfy.k_diffusion import sampling as k_diffusion_sampling from comfy_api.latest import io -class Noise_EmptyNoise: - def __init__(self): - self.seed = 0 - - def generate_noise(self, input_latent): - latent_image = input_latent["samples"] - return torch.zeros(latent_image.shape, dtype=latent_image.dtype, layout=latent_image.layout, device="cpu") - - -class Noise_RandomNoise: - def __init__(self, seed): - self.seed = seed - - def generate_noise(self, input_latent): - latent_image = input_latent["samples"] - batch_inds = input_latent["batch_index"] if "batch_index" in input_latent else None - return comfy.sample.prepare_noise(latent_image, self.seed, batch_inds) - - -class Guider_Basic(comfy.samplers.CFGGuider): - def set_conds(self, positive): - self.inner_set_conds({"positive": positive}) - - -class Guider_DualCFG(comfy.samplers.CFGGuider): - def set_cfg(self, cfg1, cfg2, nested=False): - self.cfg1 = cfg1 - self.cfg2 = cfg2 - self.nested = nested - - def set_conds(self, positive, middle, negative): - middle = node_helpers.conditioning_set_values(middle, {"prompt_type": "negative"}) - self.inner_set_conds({"positive": positive, "middle": middle, "negative": negative}) - - def predict_noise(self, x, timestep, model_options={}, seed=None): - negative_cond = self.conds.get("negative", None) - middle_cond = self.conds.get("middle", None) - positive_cond = self.conds.get("positive", None) - - if self.nested: - out = comfy.samplers.calc_cond_batch(self.inner_model, [negative_cond, middle_cond, positive_cond], x, timestep, model_options) - pred_text = comfy.samplers.cfg_function(self.inner_model, out[2], out[1], self.cfg1, x, timestep, model_options=model_options, cond=positive_cond, uncond=middle_cond) - return out[0] + self.cfg2 * (pred_text - out[0]) - else: - if model_options.get("disable_cfg1_optimization", False) is False: - if math.isclose(self.cfg2, 1.0): - negative_cond = None - if math.isclose(self.cfg1, 1.0): - middle_cond = None - - out = comfy.samplers.calc_cond_batch(self.inner_model, [negative_cond, middle_cond, positive_cond], x, timestep, model_options) - return comfy.samplers.cfg_function(self.inner_model, out[1], out[0], self.cfg2, x, timestep, model_options=model_options, cond=middle_cond, uncond=negative_cond) + (out[2] - out[1]) * self.cfg1 - - -class AddNoise(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="AddNoise_V3", - category="_for_testing/custom_sampling/noise", - is_experimental=True, - inputs=[ - io.Model.Input("model"), - io.Noise.Input("noise"), - io.Sigmas.Input("sigmas"), - io.Latent.Input("latent_image"), - ], - outputs=[ - io.Latent.Output(), - ] - ) - - @classmethod - def execute(cls, model, noise, sigmas, latent_image): - if len(sigmas) == 0: - return io.NodeOutput(latent_image) - - latent = latent_image - latent_image = latent["samples"] - - noisy = noise.generate_noise(latent) - - model_sampling = model.get_model_object("model_sampling") - process_latent_out = model.get_model_object("process_latent_out") - process_latent_in = model.get_model_object("process_latent_in") - - if len(sigmas) > 1: - scale = torch.abs(sigmas[0] - sigmas[-1]) - else: - scale = sigmas[0] - - if torch.count_nonzero(latent_image) > 0: #Don't shift the empty latent image. - latent_image = process_latent_in(latent_image) - noisy = model_sampling.noise_scaling(scale, noisy, latent_image) - noisy = process_latent_out(noisy) - noisy = torch.nan_to_num(noisy, nan=0.0, posinf=0.0, neginf=0.0) - - out = latent.copy() - out["samples"] = noisy - return io.NodeOutput(out) - - -class BasicGuider(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="BasicGuider_V3", - category="sampling/custom_sampling/guiders", - inputs=[ - io.Model.Input("model"), - io.Conditioning.Input("conditioning"), - ], - outputs=[ - io.Guider.Output(), - ] - ) - - @classmethod - def execute(cls, model, conditioning): - guider = Guider_Basic(model) - guider.set_conds(conditioning) - return io.NodeOutput(guider) - - class BasicScheduler(io.ComfyNode): @classmethod def define_schema(cls): @@ -168,6 +44,123 @@ class BasicScheduler(io.ComfyNode): return io.NodeOutput(sigmas) +class KarrasScheduler(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="KarrasScheduler_V3", + category="sampling/custom_sampling/schedulers", + inputs=[ + io.Int.Input("steps", default=20, min=1, max=10000), + io.Float.Input("sigma_max", default=14.614642, min=0.0, max=5000.0, step=0.01, round=False), + io.Float.Input("sigma_min", default=0.0291675, min=0.0, max=5000.0, step=0.01, round=False), + io.Float.Input("rho", default=7.0, min=0.0, max=100.0, step=0.01, round=False), + ], + outputs=[ + io.Sigmas.Output(), + ] + ) + + @classmethod + def execute(cls, steps, sigma_max, sigma_min, rho): + sigmas = k_diffusion_sampling.get_sigmas_karras(n=steps, sigma_min=sigma_min, sigma_max=sigma_max, rho=rho) + return io.NodeOutput(sigmas) + + +class ExponentialScheduler(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="ExponentialScheduler_V3", + category="sampling/custom_sampling/schedulers", + inputs=[ + io.Int.Input("steps", default=20, min=1, max=10000), + io.Float.Input("sigma_max", default=14.614642, min=0.0, max=5000.0, step=0.01, round=False), + io.Float.Input("sigma_min", default=0.0291675, min=0.0, max=5000.0, step=0.01, round=False), + ], + outputs=[ + io.Sigmas.Output(), + ] + ) + + @classmethod + def execute(cls, steps, sigma_max, sigma_min): + sigmas = k_diffusion_sampling.get_sigmas_exponential(n=steps, sigma_min=sigma_min, sigma_max=sigma_max) + return io.NodeOutput(sigmas) + + +class PolyexponentialScheduler(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="PolyexponentialScheduler_V3", + category="sampling/custom_sampling/schedulers", + inputs=[ + io.Int.Input("steps", default=20, min=1, max=10000), + io.Float.Input("sigma_max", default=14.614642, min=0.0, max=5000.0, step=0.01, round=False), + io.Float.Input("sigma_min", default=0.0291675, min=0.0, max=5000.0, step=0.01, round=False), + io.Float.Input("rho", default=1.0, min=0.0, max=100.0, step=0.01, round=False), + ], + outputs=[ + io.Sigmas.Output(), + ] + ) + + @classmethod + def execute(cls, steps, sigma_max, sigma_min, rho): + sigmas = k_diffusion_sampling.get_sigmas_polyexponential(n=steps, sigma_min=sigma_min, sigma_max=sigma_max, rho=rho) + return io.NodeOutput(sigmas) + + +class LaplaceScheduler(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="LaplaceScheduler_V3", + category="sampling/custom_sampling/schedulers", + inputs=[ + io.Int.Input("steps", default=20, min=1, max=10000), + io.Float.Input("sigma_max", default=14.614642, min=0.0, max=5000.0, step=0.01, round=False), + io.Float.Input("sigma_min", default=0.0291675, min=0.0, max=5000.0, step=0.01, round=False), + io.Float.Input("mu", default=0.0, min=-10.0, max=10.0, step=0.1, round=False), + io.Float.Input("beta", default=0.5, min=0.0, max=10.0, step=0.1, round=False), + ], + outputs=[ + io.Sigmas.Output(), + ] + ) + + @classmethod + def execute(cls, steps, sigma_max, sigma_min, mu, beta): + sigmas = k_diffusion_sampling.get_sigmas_laplace(n=steps, sigma_min=sigma_min, sigma_max=sigma_max, mu=mu, beta=beta) + return io.NodeOutput(sigmas) + + +class SDTurboScheduler(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="SDTurboScheduler_V3", + category="sampling/custom_sampling/schedulers", + inputs=[ + io.Model.Input("model"), + io.Int.Input("steps", default=1, min=1, max=10), + io.Float.Input("denoise", default=1.0, min=0, max=1.0, step=0.01), + ], + outputs=[ + io.Sigmas.Output(), + ] + ) + + @classmethod + def execute(cls, model, steps, denoise): + start_step = 10 - int(10 * denoise) + timesteps = torch.flip(torch.arange(1, 11) * 100 - 1, (0,))[start_step:start_step + steps] + sigmas = model.get_model_object("model_sampling").sigma(timesteps) + sigmas = torch.cat([sigmas, sigmas.new_zeros([1])]) + return io.NodeOutput(sigmas) + + class BetaSamplingScheduler(io.ComfyNode): @classmethod def define_schema(cls): @@ -191,86 +184,17 @@ class BetaSamplingScheduler(io.ComfyNode): return io.NodeOutput(sigmas) -class CFGGuider(io.ComfyNode): +class VPScheduler(io.ComfyNode): @classmethod def define_schema(cls): return io.Schema( - node_id="CFGGuider_V3", - category="sampling/custom_sampling/guiders", - inputs=[ - io.Model.Input("model"), - io.Conditioning.Input("positive"), - io.Conditioning.Input("negative"), - io.Float.Input("cfg", default=8.0, min=0.0, max=100.0, step=0.1, round=0.01), - ], - outputs=[ - io.Guider.Output(), - ] - ) - - @classmethod - def execute(cls, model, positive, negative, cfg): - guider = comfy.samplers.CFGGuider(model) - guider.set_conds(positive, negative) - guider.set_cfg(cfg) - return io.NodeOutput(guider) - - -class DisableNoise(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="DisableNoise_V3", - category="sampling/custom_sampling/noise", - inputs=[], - outputs=[ - io.Noise.Output(), - ] - ) - - @classmethod - def execute(cls): - return io.NodeOutput(Noise_EmptyNoise()) - - -class DualCFGGuider(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="DualCFGGuider_V3", - category="sampling/custom_sampling/guiders", - inputs=[ - io.Model.Input("model"), - io.Conditioning.Input("cond1"), - io.Conditioning.Input("cond2"), - io.Conditioning.Input("negative"), - io.Float.Input("cfg_conds", default=8.0, min=0.0, max=100.0, step=0.1, round=0.01), - io.Float.Input("cfg_cond2_negative", default=8.0, min=0.0, max=100.0, step=0.1, round=0.01), - io.Combo.Input("style", options=["regular", "nested"]), - ], - outputs=[ - io.Guider.Output(), - ] - ) - - @classmethod - def execute(cls, model, cond1, cond2, negative, cfg_conds, cfg_cond2_negative, style): - guider = Guider_DualCFG(model) - guider.set_conds(cond1, cond2, negative) - guider.set_cfg(cfg_conds, cfg_cond2_negative, nested=(style == "nested")) - return io.NodeOutput(guider) - - -class ExponentialScheduler(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="ExponentialScheduler_V3", + node_id="VPScheduler_V3", category="sampling/custom_sampling/schedulers", inputs=[ io.Int.Input("steps", default=20, min=1, max=10000), - io.Float.Input("sigma_max", default=14.614642, min=0.0, max=5000.0, step=0.01, round=False), - io.Float.Input("sigma_min", default=0.0291675, min=0.0, max=5000.0, step=0.01, round=False), + io.Float.Input("beta_d", default=19.9, min=0.0, max=5000.0, step=0.01, round=False), + io.Float.Input("beta_min", default=0.1, min=0.0, max=5000.0, step=0.01, round=False), + io.Float.Input("eps_s", default=0.001, min=0.0, max=1.0, step=0.0001, round=False), ], outputs=[ io.Sigmas.Output(), @@ -278,8 +202,103 @@ class ExponentialScheduler(io.ComfyNode): ) @classmethod - def execute(cls, steps, sigma_max, sigma_min): - sigmas = k_diffusion_sampling.get_sigmas_exponential(n=steps, sigma_min=sigma_min, sigma_max=sigma_max) + def execute(cls, steps, beta_d, beta_min, eps_s): + sigmas = k_diffusion_sampling.get_sigmas_vp(n=steps, beta_d=beta_d, beta_min=beta_min, eps_s=eps_s) + return io.NodeOutput(sigmas) + + +class SplitSigmas(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="SplitSigmas_V3", + category="sampling/custom_sampling/sigmas", + inputs=[ + io.Sigmas.Input("sigmas"), + io.Int.Input("step", default=0, min=0, max=10000), + ], + outputs=[ + io.Sigmas.Output(display_name="high_sigmas"), + io.Sigmas.Output(display_name="low_sigmas"), + ] + ) + + @classmethod + def execute(cls, sigmas, step): + sigmas1 = sigmas[:step + 1] + sigmas2 = sigmas[step:] + return io.NodeOutput(sigmas1, sigmas2) + + +class SplitSigmasDenoise(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="SplitSigmasDenoise_V3", + category="sampling/custom_sampling/sigmas", + inputs=[ + io.Sigmas.Input("sigmas"), + io.Float.Input("denoise", default=1.0, min=0.0, max=1.0, step=0.01), + ], + outputs=[ + io.Sigmas.Output(display_name="high_sigmas"), + io.Sigmas.Output(display_name="low_sigmas"), + ] + ) + + @classmethod + def execute(cls, sigmas, denoise): + steps = max(sigmas.shape[-1] - 1, 0) + total_steps = round(steps * denoise) + sigmas1 = sigmas[:-(total_steps)] + sigmas2 = sigmas[-(total_steps + 1):] + return io.NodeOutput(sigmas1, sigmas2) + + +class FlipSigmas(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="FlipSigmas_V3", + category="sampling/custom_sampling/sigmas", + inputs=[ + io.Sigmas.Input("sigmas"), + ], + outputs=[ + io.Sigmas.Output(), + ] + ) + + @classmethod + def execute(cls, sigmas): + if len(sigmas) == 0: + return io.NodeOutput(sigmas) + + sigmas = sigmas.flip(0) + if sigmas[0] == 0: + sigmas[0] = 0.0001 + return io.NodeOutput(sigmas) + + +class SetFirstSigma(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="SetFirstSigma_V3", + category="sampling/custom_sampling/sigmas", + inputs=[ + io.Sigmas.Input("sigmas"), + io.Float.Input("sigma", default=136.0, min=0.0, max=20000.0, step=0.001, round=False), + ], + outputs=[ + io.Sigmas.Output(), + ] + ) + + @classmethod + def execute(cls, sigmas, sigma): + sigmas = sigmas.clone() + sigmas[0] = sigma return io.NodeOutput(sigmas) @@ -336,52 +355,32 @@ class ExtendIntermediateSigmas(io.ComfyNode): return io.NodeOutput(extended_sigmas) -class FlipSigmas(io.ComfyNode): +class SamplingPercentToSigma(io.ComfyNode): @classmethod def define_schema(cls): return io.Schema( - node_id="FlipSigmas_V3", + node_id="SamplingPercentToSigma_V3", category="sampling/custom_sampling/sigmas", inputs=[ - io.Sigmas.Input("sigmas"), + io.Model.Input("model"), + io.Float.Input("sampling_percent", default=0.0, min=0.0, max=1.0, step=0.0001), + io.Boolean.Input("return_actual_sigma", default=False, tooltip="Return the actual sigma value instead of the value used for interval checks.\nThis only affects results at 0.0 and 1.0."), ], outputs=[ - io.Sigmas.Output(), + io.Float.Output(display_name="sigma_value"), ] ) @classmethod - def execute(cls, sigmas): - if len(sigmas) == 0: - return io.NodeOutput(sigmas) - - sigmas = sigmas.flip(0) - if sigmas[0] == 0: - sigmas[0] = 0.0001 - return io.NodeOutput(sigmas) - - -class KarrasScheduler(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="KarrasScheduler_V3", - category="sampling/custom_sampling/schedulers", - inputs=[ - io.Int.Input("steps", default=20, min=1, max=10000), - io.Float.Input("sigma_max", default=14.614642, min=0.0, max=5000.0, step=0.01, round=False), - io.Float.Input("sigma_min", default=0.0291675, min=0.0, max=5000.0, step=0.01, round=False), - io.Float.Input("rho", default=7.0, min=0.0, max=100.0, step=0.01, round=False), - ], - outputs=[ - io.Sigmas.Output(), - ] - ) - - @classmethod - def execute(cls, steps, sigma_max, sigma_min, rho): - sigmas = k_diffusion_sampling.get_sigmas_karras(n=steps, sigma_min=sigma_min, sigma_max=sigma_max, rho=rho) - return io.NodeOutput(sigmas) + def execute(cls, model, sampling_percent, return_actual_sigma): + model_sampling = model.get_model_object("model_sampling") + sigma_val = model_sampling.percent_to_sigma(sampling_percent) + if return_actual_sigma: + if sampling_percent == 0.0: + sigma_val = model_sampling.sigma_max.item() + elif sampling_percent == 1.0: + sigma_val = model_sampling.sigma_min.item() + return io.NodeOutput(sigma_val) class KSamplerSelect(io.ComfyNode): @@ -404,193 +403,16 @@ class KSamplerSelect(io.ComfyNode): return io.NodeOutput(sampler) -class LaplaceScheduler(io.ComfyNode): +class SamplerDPMPP_3M_SDE(io.ComfyNode): @classmethod def define_schema(cls): return io.Schema( - node_id="LaplaceScheduler_V3", - category="sampling/custom_sampling/schedulers", - inputs=[ - io.Int.Input("steps", default=20, min=1, max=10000), - io.Float.Input("sigma_max", default=14.614642, min=0.0, max=5000.0, step=0.01, round=False), - io.Float.Input("sigma_min", default=0.0291675, min=0.0, max=5000.0, step=0.01, round=False), - io.Float.Input("mu", default=0.0, min=-10.0, max=10.0, step=0.1, round=False), - io.Float.Input("beta", default=0.5, min=0.0, max=10.0, step=0.1, round=False), - ], - outputs=[ - io.Sigmas.Output(), - ] - ) - - @classmethod - def execute(cls, steps, sigma_max, sigma_min, mu, beta): - sigmas = k_diffusion_sampling.get_sigmas_laplace(n=steps, sigma_min=sigma_min, sigma_max=sigma_max, mu=mu, beta=beta) - return io.NodeOutput(sigmas) - - -class PolyexponentialScheduler(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="PolyexponentialScheduler_V3", - category="sampling/custom_sampling/schedulers", - inputs=[ - io.Int.Input("steps", default=20, min=1, max=10000), - io.Float.Input("sigma_max", default=14.614642, min=0.0, max=5000.0, step=0.01, round=False), - io.Float.Input("sigma_min", default=0.0291675, min=0.0, max=5000.0, step=0.01, round=False), - io.Float.Input("rho", default=1.0, min=0.0, max=100.0, step=0.01, round=False), - ], - outputs=[ - io.Sigmas.Output(), - ] - ) - - @classmethod - def execute(cls, steps, sigma_max, sigma_min, rho): - sigmas = k_diffusion_sampling.get_sigmas_polyexponential(n=steps, sigma_min=sigma_min, sigma_max=sigma_max, rho=rho) - return io.NodeOutput(sigmas) - - -class RandomNoise(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="RandomNoise_V3", - category="sampling/custom_sampling/noise", - inputs=[ - io.Int.Input("noise_seed", default=0, min=0, max=0xffffffffffffffff, control_after_generate=True), - ], - outputs=[ - io.Noise.Output(), - ] - ) - - @classmethod - def execute(cls, noise_seed): - return io.NodeOutput(Noise_RandomNoise(noise_seed)) - - -class SamplerCustom(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="SamplerCustom_V3", - category="sampling/custom_sampling", - inputs=[ - io.Model.Input("model"), - io.Boolean.Input("add_noise", default=True), - io.Int.Input("noise_seed", default=0, min=0, max=0xffffffffffffffff, control_after_generate=True), - io.Float.Input("cfg", default=8.0, min=0.0, max=100.0, step=0.1, round=0.01), - io.Conditioning.Input("positive"), - io.Conditioning.Input("negative"), - io.Sampler.Input("sampler"), - io.Sigmas.Input("sigmas"), - io.Latent.Input("latent_image"), - ], - outputs=[ - io.Latent.Output(display_name="output"), - io.Latent.Output(display_name="denoised_output"), - ] - ) - - @classmethod - def execute(cls, model, add_noise, noise_seed, cfg, positive, negative, sampler, sigmas, latent_image): - latent = latent_image - latent_image = latent["samples"] - latent = latent.copy() - latent_image = comfy.sample.fix_empty_latent_channels(model, latent_image) - latent["samples"] = latent_image - - if not add_noise: - noise = Noise_EmptyNoise().generate_noise(latent) - else: - noise = Noise_RandomNoise(noise_seed).generate_noise(latent) - - noise_mask = None - if "noise_mask" in latent: - noise_mask = latent["noise_mask"] - - x0_output = {} - callback = latent_preview.prepare_callback(model, sigmas.shape[-1] - 1, x0_output) - - disable_pbar = not comfy.utils.PROGRESS_BAR_ENABLED - samples = comfy.sample.sample_custom(model, noise, cfg, sampler, sigmas, positive, negative, latent_image, noise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar, seed=noise_seed) - - out = latent.copy() - out["samples"] = samples - if "x0" in x0_output: - out_denoised = latent.copy() - out_denoised["samples"] = model.model.process_latent_out(x0_output["x0"].cpu()) - else: - out_denoised = out - return io.NodeOutput(out, out_denoised) - - -class SamplerCustomAdvanced(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="SamplerCustomAdvanced_V3", - category="sampling/custom_sampling", - inputs=[ - io.Noise.Input("noise"), - io.Guider.Input("guider"), - io.Sampler.Input("sampler"), - io.Sigmas.Input("sigmas"), - io.Latent.Input("latent_image"), - ], - outputs=[ - io.Latent.Output(display_name="output"), - io.Latent.Output(display_name="denoised_output"), - ] - ) - - @classmethod - def execute(cls, noise, guider, sampler, sigmas, latent_image): - latent = latent_image - latent_image = latent["samples"] - latent = latent.copy() - latent_image = comfy.sample.fix_empty_latent_channels(guider.model_patcher, latent_image) - latent["samples"] = latent_image - - noise_mask = None - if "noise_mask" in latent: - noise_mask = latent["noise_mask"] - - x0_output = {} - callback = latent_preview.prepare_callback(guider.model_patcher, sigmas.shape[-1] - 1, x0_output) - - disable_pbar = not comfy.utils.PROGRESS_BAR_ENABLED - samples = guider.sample(noise.generate_noise(latent), latent_image, sampler, sigmas, denoise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar, seed=noise.seed) - samples = samples.to(comfy.model_management.intermediate_device()) - - out = latent.copy() - out["samples"] = samples - if "x0" in x0_output: - out_denoised = latent.copy() - out_denoised["samples"] = guider.model_patcher.model.process_latent_out(x0_output["x0"].cpu()) - else: - out_denoised = out - return io.NodeOutput(out, out_denoised) - - -class SamplerDPMAdaptative(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="SamplerDPMAdaptative_V3", + node_id="SamplerDPMPP_3M_SDE_V3", category="sampling/custom_sampling/samplers", inputs=[ - io.Int.Input("order", default=3, min=2, max=3), - io.Float.Input("rtol", default=0.05, min=0.0, max=100.0, step=0.01, round=False), - io.Float.Input("atol", default=0.0078, min=0.0, max=100.0, step=0.01, round=False), - io.Float.Input("h_init", default=0.05, min=0.0, max=100.0, step=0.01, round=False), - io.Float.Input("pcoeff", default=0.0, min=0.0, max=100.0, step=0.01, round=False), - io.Float.Input("icoeff", default=1.0, min=0.0, max=100.0, step=0.01, round=False), - io.Float.Input("dcoeff", default=0.0, min=0.0, max=100.0, step=0.01, round=False), - io.Float.Input("accept_safety", default=0.81, min=0.0, max=100.0, step=0.01, round=False), - io.Float.Input("eta", default=0.0, min=0.0, max=100.0, step=0.01, round=False), + io.Float.Input("eta", default=1.0, min=0.0, max=100.0, step=0.01, round=False), io.Float.Input("s_noise", default=1.0, min=0.0, max=100.0, step=0.01, round=False), + io.Combo.Input("noise_device", options=['gpu', 'cpu']), ], outputs=[ io.Sampler.Output(), @@ -598,10 +420,12 @@ class SamplerDPMAdaptative(io.ComfyNode): ) @classmethod - def execute(cls, order, rtol, atol, h_init, pcoeff, icoeff, dcoeff, accept_safety, eta, s_noise): - sampler = comfy.samplers.ksampler("dpm_adaptive", {"order": order, "rtol": rtol, "atol": atol, "h_init": h_init, "pcoeff": pcoeff, - "icoeff": icoeff, "dcoeff": dcoeff, "accept_safety": accept_safety, "eta": eta, - "s_noise":s_noise }) + def execute(cls, eta, s_noise, noise_device): + if noise_device == 'cpu': + sampler_name = "dpmpp_3m_sde" + else: + sampler_name = "dpmpp_3m_sde_gpu" + sampler = comfy.samplers.ksampler(sampler_name, {"eta": eta, "s_noise": s_noise}) return io.NodeOutput(sampler) @@ -632,53 +456,6 @@ class SamplerDPMPP_2M_SDE(io.ComfyNode): return io.NodeOutput(sampler) -class SamplerDPMPP_2S_Ancestral(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="SamplerDPMPP_2S_Ancestral_V3", - category="sampling/custom_sampling/samplers", - inputs=[ - io.Float.Input("eta", default=1.0, min=0.0, max=100.0, step=0.01, round=False), - io.Float.Input("s_noise", default=1.0, min=0.0, max=100.0, step=0.01, round=False), - ], - outputs=[ - io.Sampler.Output(), - ] - ) - - @classmethod - def execute(cls, eta, s_noise): - sampler = comfy.samplers.ksampler("dpmpp_2s_ancestral", {"eta": eta, "s_noise": s_noise}) - return io.NodeOutput(sampler) - - -class SamplerDPMPP_3M_SDE(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="SamplerDPMPP_3M_SDE_V3", - category="sampling/custom_sampling/samplers", - inputs=[ - io.Float.Input("eta", default=1.0, min=0.0, max=100.0, step=0.01, round=False), - io.Float.Input("s_noise", default=1.0, min=0.0, max=100.0, step=0.01, round=False), - io.Combo.Input("noise_device", options=['gpu', 'cpu']), - ], - outputs=[ - io.Sampler.Output(), - ] - ) - - @classmethod - def execute(cls, eta, s_noise, noise_device): - if noise_device == 'cpu': - sampler_name = "dpmpp_3m_sde" - else: - sampler_name = "dpmpp_3m_sde_gpu" - sampler = comfy.samplers.ksampler(sampler_name, {"eta": eta, "s_noise": s_noise}) - return io.NodeOutput(sampler) - - class SamplerDPMPP_SDE(io.ComfyNode): @classmethod def define_schema(cls): @@ -706,16 +483,14 @@ class SamplerDPMPP_SDE(io.ComfyNode): return io.NodeOutput(sampler) -class SamplerER_SDE(io.ComfyNode): +class SamplerDPMPP_2S_Ancestral(io.ComfyNode): @classmethod def define_schema(cls): return io.Schema( - node_id="SamplerER_SDE_V3", + node_id="SamplerDPMPP_2S_Ancestral_V3", category="sampling/custom_sampling/samplers", inputs=[ - io.Combo.Input("solver_type", options=["ER-SDE", "Reverse-time SDE", "ODE"]), - io.Int.Input("max_stage", default=3, min=1, max=3), - io.Float.Input("eta", default=1.0, min=0.0, max=100.0, step=0.01, round=False, tooltip="Stochastic strength of reverse-time SDE.\nWhen eta=0, it reduces to deterministic ODE. This setting doesn't apply to ER-SDE solver type."), + io.Float.Input("eta", default=1.0, min=0.0, max=100.0, step=0.01, round=False), io.Float.Input("s_noise", default=1.0, min=0.0, max=100.0, step=0.01, round=False), ], outputs=[ @@ -724,22 +499,8 @@ class SamplerER_SDE(io.ComfyNode): ) @classmethod - def execute(cls, solver_type, max_stage, eta, s_noise): - if solver_type == "ODE" or (solver_type == "Reverse-time SDE" and eta == 0): - eta = 0 - s_noise = 0 - - def reverse_time_sde_noise_scaler(x): - return x ** (eta + 1) - - if solver_type == "ER-SDE": - # Use the default one in sample_er_sde() - noise_scaler = None - else: - noise_scaler = reverse_time_sde_noise_scaler - - sampler_name = "er_sde" - sampler = comfy.samplers.ksampler(sampler_name, {"s_noise": s_noise, "noise_scaler": noise_scaler, "max_stage": max_stage}) + def execute(cls, eta, s_noise): + sampler = comfy.samplers.ksampler("dpmpp_2s_ancestral", {"eta": eta, "s_noise": s_noise}) return io.NodeOutput(sampler) @@ -808,6 +569,74 @@ class SamplerLMS(io.ComfyNode): return io.NodeOutput(sampler) +class SamplerDPMAdaptative(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="SamplerDPMAdaptative_V3", + category="sampling/custom_sampling/samplers", + inputs=[ + io.Int.Input("order", default=3, min=2, max=3), + io.Float.Input("rtol", default=0.05, min=0.0, max=100.0, step=0.01, round=False), + io.Float.Input("atol", default=0.0078, min=0.0, max=100.0, step=0.01, round=False), + io.Float.Input("h_init", default=0.05, min=0.0, max=100.0, step=0.01, round=False), + io.Float.Input("pcoeff", default=0.0, min=0.0, max=100.0, step=0.01, round=False), + io.Float.Input("icoeff", default=1.0, min=0.0, max=100.0, step=0.01, round=False), + io.Float.Input("dcoeff", default=0.0, min=0.0, max=100.0, step=0.01, round=False), + io.Float.Input("accept_safety", default=0.81, min=0.0, max=100.0, step=0.01, round=False), + io.Float.Input("eta", default=0.0, min=0.0, max=100.0, step=0.01, round=False), + io.Float.Input("s_noise", default=1.0, min=0.0, max=100.0, step=0.01, round=False), + ], + outputs=[ + io.Sampler.Output(), + ] + ) + + @classmethod + def execute(cls, order, rtol, atol, h_init, pcoeff, icoeff, dcoeff, accept_safety, eta, s_noise): + sampler = comfy.samplers.ksampler("dpm_adaptive", {"order": order, "rtol": rtol, "atol": atol, "h_init": h_init, "pcoeff": pcoeff, + "icoeff": icoeff, "dcoeff": dcoeff, "accept_safety": accept_safety, "eta": eta, + "s_noise":s_noise }) + return io.NodeOutput(sampler) + + +class SamplerER_SDE(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="SamplerER_SDE_V3", + category="sampling/custom_sampling/samplers", + inputs=[ + io.Combo.Input("solver_type", options=["ER-SDE", "Reverse-time SDE", "ODE"]), + io.Int.Input("max_stage", default=3, min=1, max=3), + io.Float.Input("eta", default=1.0, min=0.0, max=100.0, step=0.01, round=False, tooltip="Stochastic strength of reverse-time SDE.\nWhen eta=0, it reduces to deterministic ODE. This setting doesn't apply to ER-SDE solver type."), + io.Float.Input("s_noise", default=1.0, min=0.0, max=100.0, step=0.01, round=False), + ], + outputs=[ + io.Sampler.Output(), + ] + ) + + @classmethod + def execute(cls, solver_type, max_stage, eta, s_noise): + if solver_type == "ODE" or (solver_type == "Reverse-time SDE" and eta == 0): + eta = 0 + s_noise = 0 + + def reverse_time_sde_noise_scaler(x): + return x ** (eta + 1) + + if solver_type == "ER-SDE": + # Use the default one in sample_er_sde() + noise_scaler = None + else: + noise_scaler = reverse_time_sde_noise_scaler + + sampler_name = "er_sde" + sampler = comfy.samplers.ksampler(sampler_name, {"s_noise": s_noise, "noise_scaler": noise_scaler, "max_stage": max_stage}) + return io.NodeOutput(sampler) + + class SamplerSASolver(io.ComfyNode): @classmethod def define_schema(cls): @@ -852,153 +681,324 @@ class SamplerSASolver(io.ComfyNode): return io.NodeOutput(sampler) -class SamplingPercentToSigma(io.ComfyNode): +class Noise_EmptyNoise: + def __init__(self): + self.seed = 0 + + def generate_noise(self, input_latent): + latent_image = input_latent["samples"] + return torch.zeros(latent_image.shape, dtype=latent_image.dtype, layout=latent_image.layout, device="cpu") + + +class Noise_RandomNoise: + def __init__(self, seed): + self.seed = seed + + def generate_noise(self, input_latent): + latent_image = input_latent["samples"] + batch_inds = input_latent["batch_index"] if "batch_index" in input_latent else None + return comfy.sample.prepare_noise(latent_image, self.seed, batch_inds) + + +class SamplerCustom(io.ComfyNode): @classmethod def define_schema(cls): return io.Schema( - node_id="SamplingPercentToSigma_V3", - category="sampling/custom_sampling/sigmas", + node_id="SamplerCustom_V3", + category="sampling/custom_sampling", inputs=[ io.Model.Input("model"), - io.Float.Input("sampling_percent", default=0.0, min=0.0, max=1.0, step=0.0001), - io.Boolean.Input("return_actual_sigma", default=False, tooltip="Return the actual sigma value instead of the value used for interval checks.\nThis only affects results at 0.0 and 1.0."), + io.Boolean.Input("add_noise", default=True), + io.Int.Input("noise_seed", default=0, min=0, max=0xffffffffffffffff, control_after_generate=True), + io.Float.Input("cfg", default=8.0, min=0.0, max=100.0, step=0.1, round=0.01), + io.Conditioning.Input("positive"), + io.Conditioning.Input("negative"), + io.Sampler.Input("sampler"), + io.Sigmas.Input("sigmas"), + io.Latent.Input("latent_image"), ], outputs=[ - io.Float.Output(display_name="sigma_value"), + io.Latent.Output(display_name="output"), + io.Latent.Output(display_name="denoised_output"), ] ) @classmethod - def execute(cls, model, sampling_percent, return_actual_sigma): + def execute(cls, model, add_noise, noise_seed, cfg, positive, negative, sampler, sigmas, latent_image): + latent = latent_image + latent_image = latent["samples"] + latent = latent.copy() + latent_image = comfy.sample.fix_empty_latent_channels(model, latent_image) + latent["samples"] = latent_image + + if not add_noise: + noise = Noise_EmptyNoise().generate_noise(latent) + else: + noise = Noise_RandomNoise(noise_seed).generate_noise(latent) + + noise_mask = None + if "noise_mask" in latent: + noise_mask = latent["noise_mask"] + + x0_output = {} + callback = latent_preview.prepare_callback(model, sigmas.shape[-1] - 1, x0_output) + + disable_pbar = not comfy.utils.PROGRESS_BAR_ENABLED + samples = comfy.sample.sample_custom(model, noise, cfg, sampler, sigmas, positive, negative, latent_image, noise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar, seed=noise_seed) + + out = latent.copy() + out["samples"] = samples + if "x0" in x0_output: + out_denoised = latent.copy() + out_denoised["samples"] = model.model.process_latent_out(x0_output["x0"].cpu()) + else: + out_denoised = out + return io.NodeOutput(out, out_denoised) + + +class Guider_Basic(comfy.samplers.CFGGuider): + def set_conds(self, positive): + self.inner_set_conds({"positive": positive}) + + +class BasicGuider(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="BasicGuider_V3", + category="sampling/custom_sampling/guiders", + inputs=[ + io.Model.Input("model"), + io.Conditioning.Input("conditioning"), + ], + outputs=[ + io.Guider.Output(), + ] + ) + + @classmethod + def execute(cls, model, conditioning): + guider = Guider_Basic(model) + guider.set_conds(conditioning) + return io.NodeOutput(guider) + + +class CFGGuider(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="CFGGuider_V3", + category="sampling/custom_sampling/guiders", + inputs=[ + io.Model.Input("model"), + io.Conditioning.Input("positive"), + io.Conditioning.Input("negative"), + io.Float.Input("cfg", default=8.0, min=0.0, max=100.0, step=0.1, round=0.01), + ], + outputs=[ + io.Guider.Output(), + ] + ) + + @classmethod + def execute(cls, model, positive, negative, cfg): + guider = comfy.samplers.CFGGuider(model) + guider.set_conds(positive, negative) + guider.set_cfg(cfg) + return io.NodeOutput(guider) + + +class Guider_DualCFG(comfy.samplers.CFGGuider): + def set_cfg(self, cfg1, cfg2, nested=False): + self.cfg1 = cfg1 + self.cfg2 = cfg2 + self.nested = nested + + def set_conds(self, positive, middle, negative): + middle = node_helpers.conditioning_set_values(middle, {"prompt_type": "negative"}) + self.inner_set_conds({"positive": positive, "middle": middle, "negative": negative}) + + def predict_noise(self, x, timestep, model_options={}, seed=None): + negative_cond = self.conds.get("negative", None) + middle_cond = self.conds.get("middle", None) + positive_cond = self.conds.get("positive", None) + + if self.nested: + out = comfy.samplers.calc_cond_batch(self.inner_model, [negative_cond, middle_cond, positive_cond], x, timestep, model_options) + pred_text = comfy.samplers.cfg_function(self.inner_model, out[2], out[1], self.cfg1, x, timestep, model_options=model_options, cond=positive_cond, uncond=middle_cond) + return out[0] + self.cfg2 * (pred_text - out[0]) + else: + if model_options.get("disable_cfg1_optimization", False) is False: + if math.isclose(self.cfg2, 1.0): + negative_cond = None + if math.isclose(self.cfg1, 1.0): + middle_cond = None + + out = comfy.samplers.calc_cond_batch(self.inner_model, [negative_cond, middle_cond, positive_cond], x, timestep, model_options) + return comfy.samplers.cfg_function(self.inner_model, out[1], out[0], self.cfg2, x, timestep, model_options=model_options, cond=middle_cond, uncond=negative_cond) + (out[2] - out[1]) * self.cfg1 + + +class DualCFGGuider(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="DualCFGGuider_V3", + category="sampling/custom_sampling/guiders", + inputs=[ + io.Model.Input("model"), + io.Conditioning.Input("cond1"), + io.Conditioning.Input("cond2"), + io.Conditioning.Input("negative"), + io.Float.Input("cfg_conds", default=8.0, min=0.0, max=100.0, step=0.1, round=0.01), + io.Float.Input("cfg_cond2_negative", default=8.0, min=0.0, max=100.0, step=0.1, round=0.01), + io.Combo.Input("style", options=["regular", "nested"]), + ], + outputs=[ + io.Guider.Output(), + ] + ) + + @classmethod + def execute(cls, model, cond1, cond2, negative, cfg_conds, cfg_cond2_negative, style): + guider = Guider_DualCFG(model) + guider.set_conds(cond1, cond2, negative) + guider.set_cfg(cfg_conds, cfg_cond2_negative, nested=(style == "nested")) + return io.NodeOutput(guider) + + +class DisableNoise(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="DisableNoise_V3", + category="sampling/custom_sampling/noise", + inputs=[], + outputs=[ + io.Noise.Output(), + ] + ) + + @classmethod + def execute(cls): + return io.NodeOutput(Noise_EmptyNoise()) + + +class RandomNoise(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="RandomNoise_V3", + category="sampling/custom_sampling/noise", + inputs=[ + io.Int.Input("noise_seed", default=0, min=0, max=0xffffffffffffffff, control_after_generate=True), + ], + outputs=[ + io.Noise.Output(), + ] + ) + + @classmethod + def execute(cls, noise_seed): + return io.NodeOutput(Noise_RandomNoise(noise_seed)) + + +class SamplerCustomAdvanced(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="SamplerCustomAdvanced_V3", + category="sampling/custom_sampling", + inputs=[ + io.Noise.Input("noise"), + io.Guider.Input("guider"), + io.Sampler.Input("sampler"), + io.Sigmas.Input("sigmas"), + io.Latent.Input("latent_image"), + ], + outputs=[ + io.Latent.Output(display_name="output"), + io.Latent.Output(display_name="denoised_output"), + ] + ) + + @classmethod + def execute(cls, noise, guider, sampler, sigmas, latent_image): + latent = latent_image + latent_image = latent["samples"] + latent = latent.copy() + latent_image = comfy.sample.fix_empty_latent_channels(guider.model_patcher, latent_image) + latent["samples"] = latent_image + + noise_mask = None + if "noise_mask" in latent: + noise_mask = latent["noise_mask"] + + x0_output = {} + callback = latent_preview.prepare_callback(guider.model_patcher, sigmas.shape[-1] - 1, x0_output) + + disable_pbar = not comfy.utils.PROGRESS_BAR_ENABLED + samples = guider.sample(noise.generate_noise(latent), latent_image, sampler, sigmas, denoise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar, seed=noise.seed) + samples = samples.to(comfy.model_management.intermediate_device()) + + out = latent.copy() + out["samples"] = samples + if "x0" in x0_output: + out_denoised = latent.copy() + out_denoised["samples"] = guider.model_patcher.model.process_latent_out(x0_output["x0"].cpu()) + else: + out_denoised = out + return io.NodeOutput(out, out_denoised) + + +class AddNoise(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="AddNoise_V3", + category="_for_testing/custom_sampling/noise", + is_experimental=True, + inputs=[ + io.Model.Input("model"), + io.Noise.Input("noise"), + io.Sigmas.Input("sigmas"), + io.Latent.Input("latent_image"), + ], + outputs=[ + io.Latent.Output(), + ] + ) + + @classmethod + def execute(cls, model, noise, sigmas, latent_image): + if len(sigmas) == 0: + return io.NodeOutput(latent_image) + + latent = latent_image + latent_image = latent["samples"] + + noisy = noise.generate_noise(latent) + model_sampling = model.get_model_object("model_sampling") - sigma_val = model_sampling.percent_to_sigma(sampling_percent) - if return_actual_sigma: - if sampling_percent == 0.0: - sigma_val = model_sampling.sigma_max.item() - elif sampling_percent == 1.0: - sigma_val = model_sampling.sigma_min.item() - return io.NodeOutput(sigma_val) + process_latent_out = model.get_model_object("process_latent_out") + process_latent_in = model.get_model_object("process_latent_in") + + if len(sigmas) > 1: + scale = torch.abs(sigmas[0] - sigmas[-1]) + else: + scale = sigmas[0] + + if torch.count_nonzero(latent_image) > 0: #Don't shift the empty latent image. + latent_image = process_latent_in(latent_image) + noisy = model_sampling.noise_scaling(scale, noisy, latent_image) + noisy = process_latent_out(noisy) + noisy = torch.nan_to_num(noisy, nan=0.0, posinf=0.0, neginf=0.0) + + out = latent.copy() + out["samples"] = noisy + return io.NodeOutput(out) -class SDTurboScheduler(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="SDTurboScheduler_V3", - category="sampling/custom_sampling/schedulers", - inputs=[ - io.Model.Input("model"), - io.Int.Input("steps", default=1, min=1, max=10), - io.Float.Input("denoise", default=1.0, min=0, max=1.0, step=0.01), - ], - outputs=[ - io.Sigmas.Output(), - ] - ) - - @classmethod - def execute(cls, model, steps, denoise): - start_step = 10 - int(10 * denoise) - timesteps = torch.flip(torch.arange(1, 11) * 100 - 1, (0,))[start_step:start_step + steps] - sigmas = model.get_model_object("model_sampling").sigma(timesteps) - sigmas = torch.cat([sigmas, sigmas.new_zeros([1])]) - return io.NodeOutput(sigmas) - - -class SetFirstSigma(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="SetFirstSigma_V3", - category="sampling/custom_sampling/sigmas", - inputs=[ - io.Sigmas.Input("sigmas"), - io.Float.Input("sigma", default=136.0, min=0.0, max=20000.0, step=0.001, round=False), - ], - outputs=[ - io.Sigmas.Output(), - ] - ) - - @classmethod - def execute(cls, sigmas, sigma): - sigmas = sigmas.clone() - sigmas[0] = sigma - return io.NodeOutput(sigmas) - - -class SplitSigmas(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="SplitSigmas_V3", - category="sampling/custom_sampling/sigmas", - inputs=[ - io.Sigmas.Input("sigmas"), - io.Int.Input("step", default=0, min=0, max=10000), - ], - outputs=[ - io.Sigmas.Output(display_name="high_sigmas"), - io.Sigmas.Output(display_name="low_sigmas"), - ] - ) - - @classmethod - def execute(cls, sigmas, step): - sigmas1 = sigmas[:step + 1] - sigmas2 = sigmas[step:] - return io.NodeOutput(sigmas1, sigmas2) - - -class SplitSigmasDenoise(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="SplitSigmasDenoise_V3", - category="sampling/custom_sampling/sigmas", - inputs=[ - io.Sigmas.Input("sigmas"), - io.Float.Input("denoise", default=1.0, min=0.0, max=1.0, step=0.01), - ], - outputs=[ - io.Sigmas.Output(display_name="high_sigmas"), - io.Sigmas.Output(display_name="low_sigmas"), - ] - ) - - @classmethod - def execute(cls, sigmas, denoise): - steps = max(sigmas.shape[-1] - 1, 0) - total_steps = round(steps * denoise) - sigmas1 = sigmas[:-(total_steps)] - sigmas2 = sigmas[-(total_steps + 1):] - return io.NodeOutput(sigmas1, sigmas2) - - -class VPScheduler(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="VPScheduler_V3", - category="sampling/custom_sampling/schedulers", - inputs=[ - io.Int.Input("steps", default=20, min=1, max=10000), - io.Float.Input("beta_d", default=19.9, min=0.0, max=5000.0, step=0.01, round=False), - io.Float.Input("beta_min", default=0.1, min=0.0, max=5000.0, step=0.01, round=False), - io.Float.Input("eps_s", default=0.001, min=0.0, max=1.0, step=0.0001, round=False), - ], - outputs=[ - io.Sigmas.Output(), - ] - ) - - @classmethod - def execute(cls, steps, beta_d, beta_min, eps_s): - sigmas = k_diffusion_sampling.get_sigmas_vp(n=steps, beta_d=beta_d, beta_min=beta_min, eps_s=eps_s) - return io.NodeOutput(sigmas) - - -NODES_LIST = [ +NODES_LIST: list[type[io.ComfyNode]] = [ AddNoise, BasicGuider, BasicScheduler, diff --git a/comfy_extras/v3/nodes_flux.py b/comfy_extras/v3/nodes_flux.py index 3967fc4ad..f068f7b98 100644 --- a/comfy_extras/v3/nodes_flux.py +++ b/comfy_extras/v3/nodes_flux.py @@ -49,28 +49,6 @@ class CLIPTextEncodeFlux(io.ComfyNode): return io.NodeOutput(clip.encode_from_tokens_scheduled(tokens, add_dict={"guidance": guidance})) - -class FluxDisableGuidance(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="FluxDisableGuidance_V3", - category="advanced/conditioning/flux", - description="This node completely disables the guidance embed on Flux and Flux like models", - inputs=[ - io.Conditioning.Input("conditioning"), - ], - outputs=[ - io.Conditioning.Output(), - ], - ) - - @classmethod - def execute(cls, conditioning): - c = node_helpers.conditioning_set_values(conditioning, {"guidance": None}) - return io.NodeOutput(c) - - class FluxGuidance(io.ComfyNode): @classmethod def define_schema(cls): @@ -91,6 +69,25 @@ class FluxGuidance(io.ComfyNode): c = node_helpers.conditioning_set_values(conditioning, {"guidance": guidance}) return io.NodeOutput(c) +class FluxDisableGuidance(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="FluxDisableGuidance_V3", + category="advanced/conditioning/flux", + description="This node completely disables the guidance embed on Flux and Flux like models", + inputs=[ + io.Conditioning.Input("conditioning"), + ], + outputs=[ + io.Conditioning.Output(), + ], + ) + + @classmethod + def execute(cls, conditioning): + c = node_helpers.conditioning_set_values(conditioning, {"guidance": None}) + return io.NodeOutput(c) class FluxKontextImageScale(io.ComfyNode): @classmethod @@ -117,7 +114,7 @@ class FluxKontextImageScale(io.ComfyNode): return io.NodeOutput(image) -NODES_LIST = [ +NODES_LIST: list[type[io.ComfyNode]] = [ CLIPTextEncodeFlux, FluxDisableGuidance, FluxGuidance, diff --git a/comfy_extras/v3/nodes_freelunch.py b/comfy_extras/v3/nodes_freelunch.py index fe3e2c9dd..7467a1f88 100644 --- a/comfy_extras/v3/nodes_freelunch.py +++ b/comfy_extras/v3/nodes_freelunch.py @@ -125,7 +125,7 @@ class FreeU_V2(io.ComfyNode): return io.NodeOutput(m) -NODES_LIST = [ +NODES_LIST: list[type[io.ComfyNode]] = [ FreeU, FreeU_V2, ] diff --git a/comfy_extras/v3/nodes_hidream.py b/comfy_extras/v3/nodes_hidream.py index 8afd3bb13..a7c733774 100644 --- a/comfy_extras/v3/nodes_hidream.py +++ b/comfy_extras/v3/nodes_hidream.py @@ -6,33 +6,6 @@ import folder_paths from comfy_api.latest import io -class CLIPTextEncodeHiDream(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="CLIPTextEncodeHiDream_V3", - category="advanced/conditioning", - inputs=[ - io.Clip.Input("clip"), - io.String.Input("clip_l", multiline=True, dynamic_prompts=True), - io.String.Input("clip_g", multiline=True, dynamic_prompts=True), - io.String.Input("t5xxl", multiline=True, dynamic_prompts=True), - io.String.Input("llama", multiline=True, dynamic_prompts=True), - ], - outputs=[ - io.Conditioning.Output(), - ] - ) - - @classmethod - def execute(cls, clip, clip_l, clip_g, t5xxl, llama): - tokens = clip.tokenize(clip_g) - tokens["l"] = clip.tokenize(clip_l)["l"] - tokens["t5xxl"] = clip.tokenize(t5xxl)["t5xxl"] - tokens["llama"] = clip.tokenize(llama)["llama"] - return io.NodeOutput(clip.encode_from_tokens_scheduled(tokens)) - - class QuadrupleCLIPLoader(io.ComfyNode): @classmethod def define_schema(cls): @@ -65,7 +38,34 @@ class QuadrupleCLIPLoader(io.ComfyNode): ) -NODES_LIST = [ +class CLIPTextEncodeHiDream(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="CLIPTextEncodeHiDream_V3", + category="advanced/conditioning", + inputs=[ + io.Clip.Input("clip"), + io.String.Input("clip_l", multiline=True, dynamic_prompts=True), + io.String.Input("clip_g", multiline=True, dynamic_prompts=True), + io.String.Input("t5xxl", multiline=True, dynamic_prompts=True), + io.String.Input("llama", multiline=True, dynamic_prompts=True), + ], + outputs=[ + io.Conditioning.Output(), + ] + ) + + @classmethod + def execute(cls, clip, clip_l, clip_g, t5xxl, llama): + tokens = clip.tokenize(clip_g) + tokens["l"] = clip.tokenize(clip_l)["l"] + tokens["t5xxl"] = clip.tokenize(t5xxl)["t5xxl"] + tokens["llama"] = clip.tokenize(llama)["llama"] + return io.NodeOutput(clip.encode_from_tokens_scheduled(tokens)) + + +NODES_LIST: list[type[io.ComfyNode]] = [ CLIPTextEncodeHiDream, QuadrupleCLIPLoader, ] diff --git a/comfy_extras/v3/nodes_hunyuan.py b/comfy_extras/v3/nodes_hunyuan.py index 1c2262a0e..4ad737d7b 100644 --- a/comfy_extras/v3/nodes_hunyuan.py +++ b/comfy_extras/v3/nodes_hunyuan.py @@ -7,16 +7,6 @@ import node_helpers import nodes from comfy_api.latest import io -PROMPT_TEMPLATE_ENCODE_VIDEO_I2V = ( - "<|start_header_id|>system<|end_header_id|>\n\n\nDescribe the video by detailing the following aspects according to the reference image: " - "1. The main content and theme of the video." - "2. The color, shape, size, texture, quantity, text, and spatial relationships of the objects." - "3. Actions, events, behaviors temporal relationships, physical movement changes of the objects." - "4. background environment, light, style and atmosphere." - "5. camera angles, movements, and transitions used in the video:<|eot_id|>\n\n" - "<|start_header_id|>user<|end_header_id|>\n\n{}<|eot_id|>" - "<|start_header_id|>assistant<|end_header_id|>\n\n" -) class CLIPTextEncodeHunyuanDiT(io.ComfyNode): @classmethod @@ -68,6 +58,51 @@ class EmptyHunyuanLatentVideo(io.ComfyNode): return io.NodeOutput({"samples":latent}) +PROMPT_TEMPLATE_ENCODE_VIDEO_I2V = ( + "<|start_header_id|>system<|end_header_id|>\n\n\nDescribe the video by detailing the following aspects according to the reference image: " + "1. The main content and theme of the video." + "2. The color, shape, size, texture, quantity, text, and spatial relationships of the objects." + "3. Actions, events, behaviors temporal relationships, physical movement changes of the objects." + "4. background environment, light, style and atmosphere." + "5. camera angles, movements, and transitions used in the video:<|eot_id|>\n\n" + "<|start_header_id|>user<|end_header_id|>\n\n{}<|eot_id|>" + "<|start_header_id|>assistant<|end_header_id|>\n\n" +) + + +class TextEncodeHunyuanVideo_ImageToVideo(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="TextEncodeHunyuanVideo_ImageToVideo_V3", + category="advanced/conditioning", + inputs=[ + io.Clip.Input("clip"), + io.ClipVisionOutput.Input("clip_vision_output"), + io.String.Input("prompt", multiline=True, dynamic_prompts=True), + io.Int.Input( + "image_interleave", + default=2, + min=1, + max=512, + tooltip="How much the image influences things vs the text prompt. Higher number means more influence from the text prompt.", + ), + ], + outputs=[ + io.Conditioning.Output(), + ], + ) + + @classmethod + def execute(cls, clip, clip_vision_output, prompt, image_interleave): + tokens = clip.tokenize( + prompt, llama_template=PROMPT_TEMPLATE_ENCODE_VIDEO_I2V, + image_embeds=clip_vision_output.mm_projected, + image_interleave=image_interleave, + ) + return io.NodeOutput(clip.encode_from_tokens_scheduled(tokens)) + + class HunyuanImageToVideo(io.ComfyNode): @classmethod def define_schema(cls): @@ -126,40 +161,7 @@ class HunyuanImageToVideo(io.ComfyNode): return io.NodeOutput(positive, out_latent) -class TextEncodeHunyuanVideo_ImageToVideo(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="TextEncodeHunyuanVideo_ImageToVideo_V3", - category="advanced/conditioning", - inputs=[ - io.Clip.Input("clip"), - io.ClipVisionOutput.Input("clip_vision_output"), - io.String.Input("prompt", multiline=True, dynamic_prompts=True), - io.Int.Input( - "image_interleave", - default=2, - min=1, - max=512, - tooltip="How much the image influences things vs the text prompt. Higher number means more influence from the text prompt.", - ), - ], - outputs=[ - io.Conditioning.Output(), - ], - ) - - @classmethod - def execute(cls, clip, clip_vision_output, prompt, image_interleave): - tokens = clip.tokenize( - prompt, llama_template=PROMPT_TEMPLATE_ENCODE_VIDEO_I2V, - image_embeds=clip_vision_output.mm_projected, - image_interleave=image_interleave, - ) - return io.NodeOutput(clip.encode_from_tokens_scheduled(tokens)) - - -NODES_LIST = [ +NODES_LIST: list[type[io.ComfyNode]] = [ CLIPTextEncodeHunyuanDiT, EmptyHunyuanLatentVideo, HunyuanImageToVideo, diff --git a/comfy_extras/v3/nodes_hunyuan3d.py b/comfy_extras/v3/nodes_hunyuan3d.py index f307e7973..a4594c4c2 100644 --- a/comfy_extras/v3/nodes_hunyuan3d.py +++ b/comfy_extras/v3/nodes_hunyuan3d.py @@ -661,7 +661,7 @@ class VoxelToMeshBasic(io.ComfyNode): return io.NodeOutput(MESH(torch.stack(vertices), torch.stack(faces))) -NODES_LIST = [ +NODES_LIST: list[type[io.ComfyNode]] = [ EmptyLatentHunyuan3Dv2, Hunyuan3Dv2Conditioning, Hunyuan3Dv2ConditioningMultiView, diff --git a/comfy_extras/v3/nodes_latent.py b/comfy_extras/v3/nodes_latent.py index 47966c518..a80a05cfb 100644 --- a/comfy_extras/v3/nodes_latent.py +++ b/comfy_extras/v3/nodes_latent.py @@ -44,16 +44,15 @@ class LatentAdd(io.ComfyNode): return io.NodeOutput(samples_out) -class LatentApplyOperation(io.ComfyNode): +class LatentSubtract(io.ComfyNode): @classmethod def define_schema(cls): return io.Schema( - node_id="LatentApplyOperation_V3", - category="latent/advanced/operations", - is_experimental=True, + node_id="LatentSubtract_V3", + category="latent/advanced", inputs=[ - io.Latent.Input("samples"), - io.LatentOperation.Input("operation"), + io.Latent.Input("samples1"), + io.Latent.Input("samples2"), ], outputs=[ io.Latent.Output(), @@ -61,44 +60,78 @@ class LatentApplyOperation(io.ComfyNode): ) @classmethod - def execute(cls, samples, operation): - samples_out = samples.copy() + def execute(cls, samples1, samples2): + samples_out = samples1.copy() - s1 = samples["samples"] - samples_out["samples"] = operation(latent=s1) + s1 = samples1["samples"] + s2 = samples2["samples"] + + s2 = reshape_latent_to(s1.shape, s2) + samples_out["samples"] = s1 - s2 return io.NodeOutput(samples_out) -class LatentApplyOperationCFG(io.ComfyNode): +class LatentMultiply(io.ComfyNode): @classmethod def define_schema(cls): return io.Schema( - node_id="LatentApplyOperationCFG_V3", - category="latent/advanced/operations", - is_experimental=True, + node_id="LatentMultiply_V3", + category="latent/advanced", inputs=[ - io.Model.Input("model"), - io.LatentOperation.Input("operation"), + io.Latent.Input("samples"), + io.Float.Input("multiplier", default=1.0, min=-10.0, max=10.0, step=0.01), ], outputs=[ - io.Model.Output(), + io.Latent.Output(), ], ) @classmethod - def execute(cls, model, operation): - m = model.clone() + def execute(cls, samples, multiplier): + samples_out = samples.copy() - def pre_cfg_function(args): - conds_out = args["conds_out"] - if len(conds_out) == 2: - conds_out[0] = operation(latent=(conds_out[0] - conds_out[1])) + conds_out[1] - else: - conds_out[0] = operation(latent=conds_out[0]) - return conds_out + s1 = samples["samples"] + samples_out["samples"] = s1 * multiplier + return io.NodeOutput(samples_out) - m.set_model_sampler_pre_cfg_function(pre_cfg_function) - return io.NodeOutput(m) + +class LatentInterpolate(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="LatentInterpolate_V3", + category="latent/advanced", + inputs=[ + io.Latent.Input("samples1"), + io.Latent.Input("samples2"), + io.Float.Input("ratio", default=1.0, min=0.0, max=1.0, step=0.01), + ], + outputs=[ + io.Latent.Output(), + ], + ) + + @classmethod + def execute(cls, samples1, samples2, ratio): + samples_out = samples1.copy() + + s1 = samples1["samples"] + s2 = samples2["samples"] + + s2 = reshape_latent_to(s1.shape, s2) + + m1 = torch.linalg.vector_norm(s1, dim=(1)) + m2 = torch.linalg.vector_norm(s2, dim=(1)) + + s1 = torch.nan_to_num(s1 / m1) + s2 = torch.nan_to_num(s2 / m2) + + t = (s1 * ratio + s2 * (1.0 - ratio)) + mt = torch.linalg.vector_norm(t, dim=(1)) + st = torch.nan_to_num(t / mt) + + samples_out["samples"] = st * (m1 * ratio + m2 * (1.0 - ratio)) + return io.NodeOutput(samples_out) class LatentBatch(io.ComfyNode): @@ -159,54 +192,16 @@ class LatentBatchSeedBehavior(io.ComfyNode): return io.NodeOutput(samples_out) -class LatentInterpolate(io.ComfyNode): +class LatentApplyOperation(io.ComfyNode): @classmethod def define_schema(cls): return io.Schema( - node_id="LatentInterpolate_V3", - category="latent/advanced", - inputs=[ - io.Latent.Input("samples1"), - io.Latent.Input("samples2"), - io.Float.Input("ratio", default=1.0, min=0.0, max=1.0, step=0.01), - ], - outputs=[ - io.Latent.Output(), - ], - ) - - @classmethod - def execute(cls, samples1, samples2, ratio): - samples_out = samples1.copy() - - s1 = samples1["samples"] - s2 = samples2["samples"] - - s2 = reshape_latent_to(s1.shape, s2) - - m1 = torch.linalg.vector_norm(s1, dim=(1)) - m2 = torch.linalg.vector_norm(s2, dim=(1)) - - s1 = torch.nan_to_num(s1 / m1) - s2 = torch.nan_to_num(s2 / m2) - - t = (s1 * ratio + s2 * (1.0 - ratio)) - mt = torch.linalg.vector_norm(t, dim=(1)) - st = torch.nan_to_num(t / mt) - - samples_out["samples"] = st * (m1 * ratio + m2 * (1.0 - ratio)) - return io.NodeOutput(samples_out) - - -class LatentMultiply(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="LatentMultiply_V3", - category="latent/advanced", + node_id="LatentApplyOperation_V3", + category="latent/advanced/operations", + is_experimental=True, inputs=[ io.Latent.Input("samples"), - io.Float.Input("multiplier", default=1.0, min=-10.0, max=10.0, step=0.01), + io.LatentOperation.Input("operation"), ], outputs=[ io.Latent.Output(), @@ -214,14 +209,81 @@ class LatentMultiply(io.ComfyNode): ) @classmethod - def execute(cls, samples, multiplier): + def execute(cls, samples, operation): samples_out = samples.copy() s1 = samples["samples"] - samples_out["samples"] = s1 * multiplier + samples_out["samples"] = operation(latent=s1) return io.NodeOutput(samples_out) +class LatentApplyOperationCFG(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="LatentApplyOperationCFG_V3", + category="latent/advanced/operations", + is_experimental=True, + inputs=[ + io.Model.Input("model"), + io.LatentOperation.Input("operation"), + ], + outputs=[ + io.Model.Output(), + ], + ) + + @classmethod + def execute(cls, model, operation): + m = model.clone() + + def pre_cfg_function(args): + conds_out = args["conds_out"] + if len(conds_out) == 2: + conds_out[0] = operation(latent=(conds_out[0] - conds_out[1])) + conds_out[1] + else: + conds_out[0] = operation(latent=conds_out[0]) + return conds_out + + m.set_model_sampler_pre_cfg_function(pre_cfg_function) + return io.NodeOutput(m) + + +class LatentOperationTonemapReinhard(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="LatentOperationTonemapReinhard_V3", + category="latent/advanced/operations", + is_experimental=True, + inputs=[ + io.Float.Input("multiplier", default=1.0, min=0.0, max=100.0, step=0.01), + ], + outputs=[ + io.LatentOperation.Output(), + ], + ) + + @classmethod + def execute(cls, multiplier): + def tonemap_reinhard(latent, **kwargs): + latent_vector_magnitude = (torch.linalg.vector_norm(latent, dim=(1)) + 0.0000000001)[:,None] + normalized_latent = latent / latent_vector_magnitude + + mean = torch.mean(latent_vector_magnitude, dim=(1,2,3), keepdim=True) + std = torch.std(latent_vector_magnitude, dim=(1,2,3), keepdim=True) + + top = (std * 5 + mean) * multiplier + + #reinhard + latent_vector_magnitude *= (1.0 / top) + new_magnitude = latent_vector_magnitude / (latent_vector_magnitude + 1.0) + new_magnitude *= top + + return normalized_latent * new_magnitude + return io.NodeOutput(tonemap_reinhard) + + class LatentOperationSharpen(io.ComfyNode): @classmethod def define_schema(cls): @@ -264,69 +326,7 @@ class LatentOperationSharpen(io.ComfyNode): return io.NodeOutput(sharpen) -class LatentOperationTonemapReinhard(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="LatentOperationTonemapReinhard_V3", - category="latent/advanced/operations", - is_experimental=True, - inputs=[ - io.Float.Input("multiplier", default=1.0, min=0.0, max=100.0, step=0.01), - ], - outputs=[ - io.LatentOperation.Output(), - ], - ) - - @classmethod - def execute(cls, multiplier): - def tonemap_reinhard(latent, **kwargs): - latent_vector_magnitude = (torch.linalg.vector_norm(latent, dim=(1)) + 0.0000000001)[:,None] - normalized_latent = latent / latent_vector_magnitude - - mean = torch.mean(latent_vector_magnitude, dim=(1,2,3), keepdim=True) - std = torch.std(latent_vector_magnitude, dim=(1,2,3), keepdim=True) - - top = (std * 5 + mean) * multiplier - - #reinhard - latent_vector_magnitude *= (1.0 / top) - new_magnitude = latent_vector_magnitude / (latent_vector_magnitude + 1.0) - new_magnitude *= top - - return normalized_latent * new_magnitude - return io.NodeOutput(tonemap_reinhard) - - -class LatentSubtract(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="LatentSubtract_V3", - category="latent/advanced", - inputs=[ - io.Latent.Input("samples1"), - io.Latent.Input("samples2"), - ], - outputs=[ - io.Latent.Output(), - ], - ) - - @classmethod - def execute(cls, samples1, samples2): - samples_out = samples1.copy() - - s1 = samples1["samples"] - s2 = samples2["samples"] - - s2 = reshape_latent_to(s1.shape, s2) - samples_out["samples"] = s1 - s2 - return io.NodeOutput(samples_out) - - -NODES_LIST = [ +NODES_LIST: list[type[io.ComfyNode]] = [ LatentAdd, LatentApplyOperation, LatentApplyOperationCFG, diff --git a/comfy_extras/v3/nodes_load_3d.py b/comfy_extras/v3/nodes_load_3d.py index b0ab3db16..d83100a80 100644 --- a/comfy_extras/v3/nodes_load_3d.py +++ b/comfy_extras/v3/nodes_load_3d.py @@ -172,7 +172,7 @@ class Preview3DAnimation(io.ComfyNode): return io.NodeOutput(ui=ui.PreviewUI3D(model_file, camera_info, cls=cls)) -NODES_LIST = [ +NODES_LIST: list[type[io.ComfyNode]] = [ Load3D, Load3DAnimation, Preview3D, diff --git a/comfy_extras/v3/nodes_lt.py b/comfy_extras/v3/nodes_lt.py index fe5db004e..3b6560499 100644 --- a/comfy_extras/v3/nodes_lt.py +++ b/comfy_extras/v3/nodes_lt.py @@ -516,7 +516,7 @@ class ModelSamplingLTXV(io.ComfyNode): return io.NodeOutput(m) -NODES_LIST = [ +NODES_LIST: list[type[io.ComfyNode]] = [ EmptyLTXVLatentVideo, LTXVAddGuide, LTXVConditioning, diff --git a/comfy_extras/v3/nodes_optimalsteps.py b/comfy_extras/v3/nodes_optimalsteps.py index f8f6e3b07..1fd9c1c4d 100644 --- a/comfy_extras/v3/nodes_optimalsteps.py +++ b/comfy_extras/v3/nodes_optimalsteps.py @@ -59,4 +59,6 @@ class OptimalStepsScheduler(io.ComfyNode): return io.NodeOutput(torch.FloatTensor(sigmas)) -NODES_LIST = [OptimalStepsScheduler] +NODES_LIST = [ + OptimalStepsScheduler, +] diff --git a/comfy_extras/v3/nodes_pag.py b/comfy_extras/v3/nodes_pag.py index 4ea7b07a5..c438363c4 100644 --- a/comfy_extras/v3/nodes_pag.py +++ b/comfy_extras/v3/nodes_pag.py @@ -57,4 +57,6 @@ class PerturbedAttentionGuidance(io.ComfyNode): return io.NodeOutput(m) -NODES_LIST = [PerturbedAttentionGuidance] +NODES_LIST = [ + PerturbedAttentionGuidance, +] diff --git a/comfy_extras/v3/nodes_perpneg.py b/comfy_extras/v3/nodes_perpneg.py index a539320c3..dcb5ac5b6 100644 --- a/comfy_extras/v3/nodes_perpneg.py +++ b/comfy_extras/v3/nodes_perpneg.py @@ -109,4 +109,6 @@ class PerpNegGuider(io.ComfyNode): return io.NodeOutput(guider) -NODES_LIST = [PerpNegGuider] +NODES_LIST = [ + PerpNegGuider, +]