Skip to content
代码片段 群组 项目
nodes.py 77.2 KB
更新 更旧
  • 了解如何忽略特定修订
  • comfyanonymous's avatar
    comfyanonymous 已提交
    import torch
    
    import os
    import sys
    import json
    
    import traceback
    
    import logging
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    
    
    from PIL import Image, ImageOps, ImageSequence, ImageFile
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    from PIL.PngImagePlugin import PngInfo
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    import numpy as np
    
    import safetensors.torch
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    
    
    sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "comfy"))
    
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    import comfy.samplers
    
    import comfy.sample
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    import comfy.sd
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    import comfy.utils
    
    import comfy.controlnet
    
    import comfy.clip_vision
    
    import comfy.model_management
    
    import importlib
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    
    
    import latent_preview
    
    import node_helpers
    
    def before_node_execution():
    
        comfy.model_management.throw_exception_if_processing_interrupted()
    
    def interrupt_processing(value=True):
    
        comfy.model_management.interrupt_current_processing(value)
    
    MAX_RESOLUTION=16384
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    class CLIPTextEncode:
        @classmethod
        def INPUT_TYPES(s):
    
            return {"required": {"text": ("STRING", {"multiline": True, "dynamicPrompts": True}), "clip": ("CLIP", )}}
    
    comfyanonymous's avatar
    comfyanonymous 已提交
        RETURN_TYPES = ("CONDITIONING",)
        FUNCTION = "encode"
    
    
        CATEGORY = "conditioning"
    
    
    comfyanonymous's avatar
    comfyanonymous 已提交
        def encode(self, clip, text):
    
            tokens = clip.tokenize(text)
    
            output = clip.encode_from_tokens(tokens, return_pooled=True, return_dict=True)
            cond = output.pop("cond")
            return ([[cond, output]], )
    
    
    class ConditioningCombine:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": {"conditioning_1": ("CONDITIONING", ), "conditioning_2": ("CONDITIONING", )}}
        RETURN_TYPES = ("CONDITIONING",)
        FUNCTION = "combine"
    
    
        CATEGORY = "conditioning"
    
    
        def combine(self, conditioning_1, conditioning_2):
            return (conditioning_1 + conditioning_2, )
    
    
    class ConditioningAverage :
        @classmethod
        def INPUT_TYPES(s):
    
            return {"required": {"conditioning_to": ("CONDITIONING", ), "conditioning_from": ("CONDITIONING", ),
                                  "conditioning_to_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01})
    
                                 }}
        RETURN_TYPES = ("CONDITIONING",)
        FUNCTION = "addWeighted"
    
        CATEGORY = "conditioning"
    
    
        def addWeighted(self, conditioning_to, conditioning_from, conditioning_to_strength):
    
            out = []
    
    
            if len(conditioning_from) > 1:
    
                logging.warning("Warning: ConditioningAverage conditioning_from contains more than 1 cond, only the first one will actually be applied to conditioning_to.")
    
    
            cond_from = conditioning_from[0][0]
    
            pooled_output_from = conditioning_from[0][1].get("pooled_output", None)
    
    
            for i in range(len(conditioning_to)):
                t1 = conditioning_to[i][0]
    
                pooled_output_to = conditioning_to[i][1].get("pooled_output", pooled_output_from)
    
                t0 = cond_from[:,:t1.shape[1]]
                if t0.shape[1] < t1.shape[1]:
                    t0 = torch.cat([t0] + [torch.zeros((1, (t1.shape[1] - t0.shape[1]), t1.shape[2]))], dim=1)
    
                tw = torch.mul(t1, conditioning_to_strength) + torch.mul(t0, (1.0 - conditioning_to_strength))
    
                t_to = conditioning_to[i][1].copy()
                if pooled_output_from is not None and pooled_output_to is not None:
                    t_to["pooled_output"] = torch.mul(pooled_output_to, conditioning_to_strength) + torch.mul(pooled_output_from, (1.0 - conditioning_to_strength))
                elif pooled_output_from is not None:
                    t_to["pooled_output"] = pooled_output_from
    
                n = [tw, t_to]
    
                out.append(n)
            return (out, )
    
    
    class ConditioningConcat:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": {
                "conditioning_to": ("CONDITIONING",),
                "conditioning_from": ("CONDITIONING",),
                }}
        RETURN_TYPES = ("CONDITIONING",)
        FUNCTION = "concat"
    
    
        CATEGORY = "conditioning"
    
    
        def concat(self, conditioning_to, conditioning_from):
            out = []
    
            if len(conditioning_from) > 1:
    
                logging.warning("Warning: ConditioningConcat conditioning_from contains more than 1 cond, only the first one will actually be applied to conditioning_to.")
    
    
            cond_from = conditioning_from[0][0]
    
            for i in range(len(conditioning_to)):
                t1 = conditioning_to[i][0]
                tw = torch.cat((t1, cond_from),1)
                n = [tw, conditioning_to[i][1].copy()]
                out.append(n)
    
            return (out, )
    
    
    class ConditioningSetArea:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": {"conditioning": ("CONDITIONING", ),
    
                                  "width": ("INT", {"default": 64, "min": 64, "max": MAX_RESOLUTION, "step": 8}),
                                  "height": ("INT", {"default": 64, "min": 64, "max": MAX_RESOLUTION, "step": 8}),
                                  "x": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}),
                                  "y": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}),
    
                                  "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
                                 }}
        RETURN_TYPES = ("CONDITIONING",)
        FUNCTION = "append"
    
    
        CATEGORY = "conditioning"
    
    
        def append(self, conditioning, width, height, x, y, strength):
    
            c = node_helpers.conditioning_set_values(conditioning, {"area": (height // 8, width // 8, y // 8, x // 8),
                                                                    "strength": strength,
                                                                    "set_area_to_bounds": False})
    
            return (c, )
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    
    
    class ConditioningSetAreaPercentage:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": {"conditioning": ("CONDITIONING", ),
                                  "width": ("FLOAT", {"default": 1.0, "min": 0, "max": 1.0, "step": 0.01}),
                                  "height": ("FLOAT", {"default": 1.0, "min": 0, "max": 1.0, "step": 0.01}),
                                  "x": ("FLOAT", {"default": 0, "min": 0, "max": 1.0, "step": 0.01}),
                                  "y": ("FLOAT", {"default": 0, "min": 0, "max": 1.0, "step": 0.01}),
                                  "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
                                 }}
        RETURN_TYPES = ("CONDITIONING",)
        FUNCTION = "append"
    
        CATEGORY = "conditioning"
    
        def append(self, conditioning, width, height, x, y, strength):
    
            c = node_helpers.conditioning_set_values(conditioning, {"area": ("percentage", height, width, y, x),
                                                                    "strength": strength,
                                                                    "set_area_to_bounds": False})
    
    class ConditioningSetAreaStrength:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": {"conditioning": ("CONDITIONING", ),
                                  "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
                                 }}
        RETURN_TYPES = ("CONDITIONING",)
        FUNCTION = "append"
    
        CATEGORY = "conditioning"
    
        def append(self, conditioning, strength):
    
            c = node_helpers.conditioning_set_values(conditioning, {"strength": strength})
    
    class ConditioningSetMask:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": {"conditioning": ("CONDITIONING", ),
                                  "mask": ("MASK", ),
                                  "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
    
                                  "set_cond_area": (["default", "mask bounds"],),
    
                                 }}
        RETURN_TYPES = ("CONDITIONING",)
        FUNCTION = "append"
    
        CATEGORY = "conditioning"
    
    
        def append(self, conditioning, mask, set_cond_area, strength):
            set_area_to_bounds = False
            if set_cond_area != "default":
                set_area_to_bounds = True
    
            if len(mask.shape) < 3:
                mask = mask.unsqueeze(0)
    
    
            c = node_helpers.conditioning_set_values(conditioning, {"mask": mask,
                                                                    "set_area_to_bounds": set_area_to_bounds,
                                                                    "mask_strength": strength})
    
            return (c, )
    
    
    class ConditioningZeroOut:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": {"conditioning": ("CONDITIONING", )}}
        RETURN_TYPES = ("CONDITIONING",)
        FUNCTION = "zero_out"
    
        CATEGORY = "advanced/conditioning"
    
        def zero_out(self, conditioning):
            c = []
            for t in conditioning:
                d = t[1].copy()
    
                pooled_output = d.get("pooled_output", None)
                if pooled_output is not None:
                    d["pooled_output"] = torch.zeros_like(pooled_output)
    
                n = [torch.zeros_like(t[0]), d]
                c.append(n)
            return (c, )
    
    
    class ConditioningSetTimestepRange:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": {"conditioning": ("CONDITIONING", ),
    
                                 "start": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
                                 "end": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001})
    
                                 }}
        RETURN_TYPES = ("CONDITIONING",)
        FUNCTION = "set_range"
    
        CATEGORY = "advanced/conditioning"
    
        def set_range(self, conditioning, start, end):
    
            c = node_helpers.conditioning_set_values(conditioning, {"start_percent": start,
                                                                    "end_percent": end})
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    class VAEDecode:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": { "samples": ("LATENT", ), "vae": ("VAE", )}}
        RETURN_TYPES = ("IMAGE",)
        FUNCTION = "decode"
    
    
        CATEGORY = "latent"
    
    
    comfyanonymous's avatar
    comfyanonymous 已提交
        def decode(self, vae, samples):
    
            return (vae.decode(samples["samples"]), )
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    
    
    class VAEDecodeTiled:
        @classmethod
        def INPUT_TYPES(s):
    
            return {"required": {"samples": ("LATENT", ), "vae": ("VAE", ),
    
                                 "tile_size": ("INT", {"default": 512, "min": 320, "max": 4096, "step": 64})
    
        RETURN_TYPES = ("IMAGE",)
        FUNCTION = "decode"
    
        CATEGORY = "_for_testing"
    
    
        def decode(self, vae, samples, tile_size):
    
            return (vae.decode_tiled(samples["samples"], tile_x=tile_size // 8, tile_y=tile_size // 8, ), )
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    class VAEEncode:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": { "pixels": ("IMAGE", ), "vae": ("VAE", )}}
        RETURN_TYPES = ("LATENT",)
        FUNCTION = "encode"
    
    
        CATEGORY = "latent"
    
    
        def encode(self, vae, pixels):
            t = vae.encode(pixels[:,:,:,:3])
    
            return ({"samples":t}, )
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    
    
    class VAEEncodeTiled:
        @classmethod
        def INPUT_TYPES(s):
    
            return {"required": {"pixels": ("IMAGE", ), "vae": ("VAE", ),
    
                                 "tile_size": ("INT", {"default": 512, "min": 320, "max": 4096, "step": 64})
    
        RETURN_TYPES = ("LATENT",)
        FUNCTION = "encode"
    
        CATEGORY = "_for_testing"
    
    
        def encode(self, vae, pixels, tile_size):
            t = vae.encode_tiled(pixels[:,:,:,:3], tile_x=tile_size, tile_y=tile_size, )
    
            return ({"samples":t}, )
    
    class VAEEncodeForInpaint:
        @classmethod
        def INPUT_TYPES(s):
    
            return {"required": { "pixels": ("IMAGE", ), "vae": ("VAE", ), "mask": ("MASK", ), "grow_mask_by": ("INT", {"default": 6, "min": 0, "max": 64, "step": 1}),}}
    
        RETURN_TYPES = ("LATENT",)
        FUNCTION = "encode"
    
        CATEGORY = "latent/inpaint"
    
    
        def encode(self, vae, pixels, mask, grow_mask_by=6):
    
            x = (pixels.shape[1] // vae.downscale_ratio) * vae.downscale_ratio
            y = (pixels.shape[2] // vae.downscale_ratio) * vae.downscale_ratio
    
            mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(pixels.shape[1], pixels.shape[2]), mode="bilinear")
    
            pixels = pixels.clone()
    
            if pixels.shape[1] != x or pixels.shape[2] != y:
    
                x_offset = (pixels.shape[1] % vae.downscale_ratio) // 2
                y_offset = (pixels.shape[2] % vae.downscale_ratio) // 2
    
                pixels = pixels[:,x_offset:x + x_offset, y_offset:y + y_offset,:]
                mask = mask[:,:,x_offset:x + x_offset, y_offset:y + y_offset]
    
            #grow mask by a few pixels to keep things seamless in latent space
    
            if grow_mask_by == 0:
                mask_erosion = mask
            else:
                kernel_tensor = torch.ones((1, 1, grow_mask_by, grow_mask_by))
                padding = math.ceil((grow_mask_by - 1) / 2)
    
                mask_erosion = torch.clamp(torch.nn.functional.conv2d(mask.round(), kernel_tensor, padding=padding), 0, 1)
    
    
            m = (1.0 - mask.round()).squeeze(1)
    
            for i in range(3):
                pixels[:,:,:,i] -= 0.5
    
                pixels[:,:,:,i] *= m
    
                pixels[:,:,:,i] += 0.5
            t = vae.encode(pixels)
    
    
            return ({"samples":t, "noise_mask": (mask_erosion[:,:,:x,:y].round())}, )
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    
    
    
    class InpaintModelConditioning:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": {"positive": ("CONDITIONING", ),
                                 "negative": ("CONDITIONING", ),
                                 "vae": ("VAE", ),
                                 "pixels": ("IMAGE", ),
                                 "mask": ("MASK", ),
                                 }}
    
        RETURN_TYPES = ("CONDITIONING","CONDITIONING","LATENT")
        RETURN_NAMES = ("positive", "negative", "latent")
        FUNCTION = "encode"
    
        CATEGORY = "conditioning/inpaint"
    
        def encode(self, positive, negative, pixels, vae, mask):
            x = (pixels.shape[1] // 8) * 8
            y = (pixels.shape[2] // 8) * 8
            mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(pixels.shape[1], pixels.shape[2]), mode="bilinear")
    
            orig_pixels = pixels
            pixels = orig_pixels.clone()
            if pixels.shape[1] != x or pixels.shape[2] != y:
                x_offset = (pixels.shape[1] % 8) // 2
                y_offset = (pixels.shape[2] % 8) // 2
                pixels = pixels[:,x_offset:x + x_offset, y_offset:y + y_offset,:]
                mask = mask[:,:,x_offset:x + x_offset, y_offset:y + y_offset]
    
            m = (1.0 - mask.round()).squeeze(1)
            for i in range(3):
                pixels[:,:,:,i] -= 0.5
                pixels[:,:,:,i] *= m
                pixels[:,:,:,i] += 0.5
            concat_latent = vae.encode(pixels)
            orig_latent = vae.encode(orig_pixels)
    
            out_latent = {}
    
            out_latent["samples"] = orig_latent
            out_latent["noise_mask"] = mask
    
            out = []
            for conditioning in [positive, negative]:
    
                c = node_helpers.conditioning_set_values(conditioning, {"concat_latent_image": concat_latent,
                                                                        "concat_mask": mask})
    
                out.append(c)
            return (out[0], out[1], out_latent)
    
    
    
    class SaveLatent:
        def __init__(self):
    
            self.output_dir = folder_paths.get_output_directory()
    
    
        @classmethod
        def INPUT_TYPES(s):
            return {"required": { "samples": ("LATENT", ),
    
                                  "filename_prefix": ("STRING", {"default": "latents/ComfyUI"})},
    
                    "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
                    }
        RETURN_TYPES = ()
        FUNCTION = "save"
    
        OUTPUT_NODE = True
    
        CATEGORY = "_for_testing"
    
        def save(self, samples, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None):
    
            full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir)
    
    
            # support save metadata for latent sharing
            prompt_info = ""
            if prompt is not None:
                prompt_info = json.dumps(prompt)
    
    
            metadata = None
            if not args.disable_metadata:
                metadata = {"prompt": prompt_info}
                if extra_pnginfo is not None:
                    for x in extra_pnginfo:
                        metadata[x] = json.dumps(extra_pnginfo[x])
    
    
            file = f"{filename}_{counter:05}_.latent"
    
    
            results = list()
            results.append({
                "filename": file,
                "subfolder": subfolder,
                "type": "output"
            })
    
    
            file = os.path.join(full_output_folder, file)
    
    
            output = {}
            output["latent_tensor"] = samples["samples"]
    
            output["latent_format_version_0"] = torch.tensor([])
    
            comfy.utils.save_torch_file(output, file, metadata=metadata)
    
            return { "ui": { "latents": results } }
    
    
    
    class LoadLatent:
        @classmethod
        def INPUT_TYPES(s):
    
            input_dir = folder_paths.get_input_directory()
            files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f)) and f.endswith(".latent")]
    
            return {"required": {"latent": [sorted(files), ]}, }
    
        CATEGORY = "_for_testing"
    
        RETURN_TYPES = ("LATENT", )
        FUNCTION = "load"
    
        def load(self, latent):
    
            latent_path = folder_paths.get_annotated_filepath(latent)
            latent = safetensors.torch.load_file(latent_path, device="cpu")
    
            multiplier = 1.0
            if "latent_format_version_0" not in latent:
                multiplier = 1.0 / 0.18215
            samples = {"samples": latent["latent_tensor"].float() * multiplier}
    
            return (samples, )
    
        @classmethod
        def IS_CHANGED(s, latent):
            image_path = folder_paths.get_annotated_filepath(latent)
            m = hashlib.sha256()
            with open(image_path, 'rb') as f:
                m.update(f.read())
            return m.digest().hex()
    
        @classmethod
        def VALIDATE_INPUTS(s, latent):
            if not folder_paths.exists_annotated_filepath(latent):
                return "Invalid latent file: {}".format(latent)
            return True
    
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    class CheckpointLoader:
        @classmethod
        def INPUT_TYPES(s):
    
            return {"required": { "config_name": (folder_paths.get_filename_list("configs"), ),
                                  "ckpt_name": (folder_paths.get_filename_list("checkpoints"), )}}
    
    comfyanonymous's avatar
    comfyanonymous 已提交
        RETURN_TYPES = ("MODEL", "CLIP", "VAE")
        FUNCTION = "load_checkpoint"
    
    
        CATEGORY = "advanced/loaders"
    
        def load_checkpoint(self, config_name, ckpt_name):
    
            config_path = folder_paths.get_full_path("configs", config_name)
            ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
    
            return comfy.sd.load_checkpoint(config_path, ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    
    
    class CheckpointLoaderSimple:
        @classmethod
        def INPUT_TYPES(s):
    
            return {"required": { "ckpt_name": (folder_paths.get_filename_list("checkpoints"), ),
    
                                 }}
        RETURN_TYPES = ("MODEL", "CLIP", "VAE")
        FUNCTION = "load_checkpoint"
    
    
        def load_checkpoint(self, ckpt_name):
    
            ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
    
            out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
    
    sALTaccount's avatar
    sALTaccount 已提交
    class DiffusersLoader:
        @classmethod
        def INPUT_TYPES(cls):
    
    sALTaccount's avatar
    sALTaccount 已提交
            for search_path in folder_paths.get_folder_paths("diffusers"):
    
                if os.path.exists(search_path):
    
                    for root, subdir, files in os.walk(search_path, followlinks=True):
                        if "model_index.json" in files:
                            paths.append(os.path.relpath(root, start=search_path))
    
    
            return {"required": {"model_path": (paths,), }}
    
    sALTaccount's avatar
    sALTaccount 已提交
        RETURN_TYPES = ("MODEL", "CLIP", "VAE")
        FUNCTION = "load_checkpoint"
    
    
        CATEGORY = "advanced/loaders/deprecated"
    
    sALTaccount's avatar
    sALTaccount 已提交
    
        def load_checkpoint(self, model_path, output_vae=True, output_clip=True):
    
    sALTaccount's avatar
    sALTaccount 已提交
            for search_path in folder_paths.get_folder_paths("diffusers"):
                if os.path.exists(search_path):
    
                    path = os.path.join(search_path, model_path)
                    if os.path.exists(path):
                        model_path = path
    
    sALTaccount's avatar
    sALTaccount 已提交
                        break
    
            return comfy.diffusers_load.load_diffusers(model_path, output_vae=output_vae, output_clip=output_clip, embedding_directory=folder_paths.get_folder_paths("embeddings"))
    
    class unCLIPCheckpointLoader:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": { "ckpt_name": (folder_paths.get_filename_list("checkpoints"), ),
                                 }}
        RETURN_TYPES = ("MODEL", "CLIP", "VAE", "CLIP_VISION")
        FUNCTION = "load_checkpoint"
    
    
        CATEGORY = "loaders"
    
    
        def load_checkpoint(self, ckpt_name, output_vae=True, output_clip=True):
            ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
            out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, output_clipvision=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
            return out
    
    
    class CLIPSetLastLayer:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": { "clip": ("CLIP", ),
                                  "stop_at_clip_layer": ("INT", {"default": -1, "min": -24, "max": -1, "step": 1}),
                                  }}
        RETURN_TYPES = ("CLIP",)
        FUNCTION = "set_last_layer"
    
        CATEGORY = "conditioning"
    
        def set_last_layer(self, clip, stop_at_clip_layer):
            clip = clip.clone()
            clip.clip_layer(stop_at_clip_layer)
            return (clip,)
    
    
        def __init__(self):
            self.loaded_lora = None
    
    
        @classmethod
        def INPUT_TYPES(s):
            return {"required": { "model": ("MODEL",),
                                  "clip": ("CLIP", ),
    
                                  "lora_name": (folder_paths.get_filename_list("loras"), ),
    
                                  "strength_model": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01}),
                                  "strength_clip": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01}),
    
                                  }}
        RETURN_TYPES = ("MODEL", "CLIP")
        FUNCTION = "load_lora"
    
        CATEGORY = "loaders"
    
        def load_lora(self, model, clip, lora_name, strength_model, strength_clip):
    
            if strength_model == 0 and strength_clip == 0:
                return (model, clip)
    
    
            lora_path = folder_paths.get_full_path("loras", lora_name)
    
            lora = None
            if self.loaded_lora is not None:
                if self.loaded_lora[0] == lora_path:
                    lora = self.loaded_lora[1]
                else:
    
                    temp = self.loaded_lora
                    self.loaded_lora = None
                    del temp
    
    
            if lora is None:
                lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
                self.loaded_lora = (lora_path, lora)
    
            model_lora, clip_lora = comfy.sd.load_lora_for_models(model, clip, lora, strength_model, strength_clip)
    
            return (model_lora, clip_lora)
    
    
    class LoraLoaderModelOnly(LoraLoader):
        @classmethod
        def INPUT_TYPES(s):
            return {"required": { "model": ("MODEL",),
                                  "lora_name": (folder_paths.get_filename_list("loras"), ),
    
                                  "strength_model": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01}),
    
                                  }}
        RETURN_TYPES = ("MODEL",)
        FUNCTION = "load_lora_model_only"
    
        def load_lora_model_only(self, model, lora_name, strength_model):
            return (self.load_lora(model, None, lora_name, strength_model, 0)[0],)
    
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    class VAELoader:
    
        @staticmethod
        def vae_list():
            vaes = folder_paths.get_filename_list("vae")
            approx_vaes = folder_paths.get_filename_list("vae_approx")
            sdxl_taesd_enc = False
            sdxl_taesd_dec = False
            sd1_taesd_enc = False
            sd1_taesd_dec = False
    
    Dr.Lt.Data's avatar
    Dr.Lt.Data 已提交
            sd3_taesd_enc = False
            sd3_taesd_dec = False
    
    
            for v in approx_vaes:
                if v.startswith("taesd_decoder."):
                    sd1_taesd_dec = True
                elif v.startswith("taesd_encoder."):
                    sd1_taesd_enc = True
                elif v.startswith("taesdxl_decoder."):
                    sdxl_taesd_dec = True
                elif v.startswith("taesdxl_encoder."):
                    sdxl_taesd_enc = True
    
    Dr.Lt.Data's avatar
    Dr.Lt.Data 已提交
                elif v.startswith("taesd3_decoder."):
                    sd3_taesd_dec = True
                elif v.startswith("taesd3_encoder."):
                    sd3_taesd_enc = True
    
            if sd1_taesd_dec and sd1_taesd_enc:
                vaes.append("taesd")
            if sdxl_taesd_dec and sdxl_taesd_enc:
                vaes.append("taesdxl")
    
    Dr.Lt.Data's avatar
    Dr.Lt.Data 已提交
            if sd3_taesd_dec and sd3_taesd_enc:
                vaes.append("taesd3")
    
            return vaes
    
        @staticmethod
        def load_taesd(name):
            sd = {}
            approx_vaes = folder_paths.get_filename_list("vae_approx")
    
            encoder = next(filter(lambda a: a.startswith("{}_encoder.".format(name)), approx_vaes))
            decoder = next(filter(lambda a: a.startswith("{}_decoder.".format(name)), approx_vaes))
    
            enc = comfy.utils.load_torch_file(folder_paths.get_full_path("vae_approx", encoder))
            for k in enc:
                sd["taesd_encoder.{}".format(k)] = enc[k]
    
            dec = comfy.utils.load_torch_file(folder_paths.get_full_path("vae_approx", decoder))
            for k in dec:
                sd["taesd_decoder.{}".format(k)] = dec[k]
    
            if name == "taesd":
                sd["vae_scale"] = torch.tensor(0.18215)
    
                sd["vae_shift"] = torch.tensor(0.0)
    
            elif name == "taesdxl":
                sd["vae_scale"] = torch.tensor(0.13025)
    
                sd["vae_shift"] = torch.tensor(0.0)
    
    Dr.Lt.Data's avatar
    Dr.Lt.Data 已提交
            elif name == "taesd3":
                sd["vae_scale"] = torch.tensor(1.5305)
    
                sd["vae_shift"] = torch.tensor(0.0609)
    
    comfyanonymous's avatar
    comfyanonymous 已提交
        @classmethod
        def INPUT_TYPES(s):
    
            return {"required": { "vae_name": (s.vae_list(), )}}
    
    comfyanonymous's avatar
    comfyanonymous 已提交
        RETURN_TYPES = ("VAE",)
        FUNCTION = "load_vae"
    
    
        CATEGORY = "loaders"
    
    
    comfyanonymous's avatar
    comfyanonymous 已提交
        #TODO: scale factor?
        def load_vae(self, vae_name):
    
    Dr.Lt.Data's avatar
    Dr.Lt.Data 已提交
            if vae_name in ["taesd", "taesdxl", "taesd3"]:
    
                sd = self.load_taesd(vae_name)
            else:
                vae_path = folder_paths.get_full_path("vae", vae_name)
                sd = comfy.utils.load_torch_file(vae_path)
    
            vae = comfy.sd.VAE(sd=sd)
    
    comfyanonymous's avatar
    comfyanonymous 已提交
            return (vae,)
    
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    class ControlNetLoader:
        @classmethod
        def INPUT_TYPES(s):
    
            return {"required": { "control_net_name": (folder_paths.get_filename_list("controlnet"), )}}
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    
        RETURN_TYPES = ("CONTROL_NET",)
        FUNCTION = "load_controlnet"
    
        CATEGORY = "loaders"
    
        def load_controlnet(self, control_net_name):
    
            controlnet_path = folder_paths.get_full_path("controlnet", control_net_name)
    
            controlnet = comfy.controlnet.load_controlnet(controlnet_path)
    
    comfyanonymous's avatar
    comfyanonymous 已提交
            return (controlnet,)
    
    
    class DiffControlNetLoader:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": { "model": ("MODEL",),
    
                                  "control_net_name": (folder_paths.get_filename_list("controlnet"), )}}
    
    
        RETURN_TYPES = ("CONTROL_NET",)
        FUNCTION = "load_controlnet"
    
        CATEGORY = "loaders"
    
        def load_controlnet(self, model, control_net_name):
    
            controlnet_path = folder_paths.get_full_path("controlnet", control_net_name)
    
            controlnet = comfy.controlnet.load_controlnet(controlnet_path, model)
    
            return (controlnet,)
    
    
    comfyanonymous's avatar
    comfyanonymous 已提交
    
    class ControlNetApply:
        @classmethod
        def INPUT_TYPES(s):
    
            return {"required": {"conditioning": ("CONDITIONING", ),
                                 "control_net": ("CONTROL_NET", ),
                                 "image": ("IMAGE", ),
                                 "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01})
                                 }}
    
    comfyanonymous's avatar
    comfyanonymous 已提交
        RETURN_TYPES = ("CONDITIONING",)
        FUNCTION = "apply_controlnet"
    
        CATEGORY = "conditioning"
    
    
        def apply_controlnet(self, conditioning, control_net, image, strength):
    
            if strength == 0:
                return (conditioning, )
    
    
    comfyanonymous's avatar
    comfyanonymous 已提交
            c = []
            control_hint = image.movedim(-1,1)
            for t in conditioning:
                n = [t[0], t[1].copy()]
    
                c_net = control_net.copy().set_cond_hint(control_hint, strength)
                if 'control' in t[1]:
                    c_net.set_previous_controlnet(t[1]['control'])
                n[1]['control'] = c_net
    
                n[1]['control_apply_to_uncond'] = True
    
    comfyanonymous's avatar
    comfyanonymous 已提交
                c.append(n)
            return (c, )
    
    
    
    class ControlNetApplyAdvanced:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": {"positive": ("CONDITIONING", ),
                                 "negative": ("CONDITIONING", ),
                                 "control_net": ("CONTROL_NET", ),
                                 "image": ("IMAGE", ),
                                 "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
    
                                 "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
                                 "end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001})
    
                                 }}
    
        RETURN_TYPES = ("CONDITIONING","CONDITIONING")
        RETURN_NAMES = ("positive", "negative")
        FUNCTION = "apply_controlnet"
    
        CATEGORY = "conditioning"
    
    
        def apply_controlnet(self, positive, negative, control_net, image, strength, start_percent, end_percent, vae=None):
    
            if strength == 0:
                return (positive, negative)
    
            control_hint = image.movedim(-1,1)
            cnets = {}
    
            out = []
            for conditioning in [positive, negative]:
                c = []
                for t in conditioning:
                    d = t[1].copy()
    
                    prev_cnet = d.get('control', None)
                    if prev_cnet in cnets:
                        c_net = cnets[prev_cnet]
                    else:
    
                        c_net = control_net.copy().set_cond_hint(control_hint, strength, (start_percent, end_percent), vae)
    
                        c_net.set_previous_controlnet(prev_cnet)
                        cnets[prev_cnet] = c_net
    
                    d['control'] = c_net
                    d['control_apply_to_uncond'] = False
                    n = [t[0], d]
                    c.append(n)
                out.append(c)
            return (out[0], out[1])
    
    
    
    class UNETLoader:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": { "unet_name": (folder_paths.get_filename_list("unet"), ),
                                 }}
        RETURN_TYPES = ("MODEL",)
        FUNCTION = "load_unet"
    
        CATEGORY = "advanced/loaders"
    
        def load_unet(self, unet_name):
            unet_path = folder_paths.get_full_path("unet", unet_name)
            model = comfy.sd.load_unet(unet_path)
            return (model,)
    
    
    class CLIPLoader:
        @classmethod
        def INPUT_TYPES(s):
    
            return {"required": { "clip_name": (folder_paths.get_filename_list("clip"), ),
    
                                  "type": (["stable_diffusion", "stable_cascade", "sd3", "stable_audio"], ),
    
                                 }}
        RETURN_TYPES = ("CLIP",)
        FUNCTION = "load_clip"
    
    
        CATEGORY = "advanced/loaders"
    
        def load_clip(self, clip_name, type="stable_diffusion"):
            if type == "stable_cascade":
                clip_type = comfy.sd.CLIPType.STABLE_CASCADE
    
            elif type == "sd3":
                clip_type = comfy.sd.CLIPType.SD3
    
            elif type == "stable_audio":
                clip_type = comfy.sd.CLIPType.STABLE_AUDIO
            else:
                clip_type = comfy.sd.CLIPType.STABLE_DIFFUSION
    
            clip_path = folder_paths.get_full_path("clip", clip_name)
    
            clip = comfy.sd.load_clip(ckpt_paths=[clip_path], embedding_directory=folder_paths.get_folder_paths("embeddings"), clip_type=clip_type)
    
            return (clip,)
    
    class DualCLIPLoader:
        @classmethod
        def INPUT_TYPES(s):
    
            return {"required": { "clip_name1": (folder_paths.get_filename_list("clip"), ),
                                  "clip_name2": (folder_paths.get_filename_list("clip"), ),
                                  "type": (["sdxl", "sd3"], ),
    
                                 }}
        RETURN_TYPES = ("CLIP",)
        FUNCTION = "load_clip"
    
        CATEGORY = "advanced/loaders"
    
    
        def load_clip(self, clip_name1, clip_name2, type):
    
            clip_path1 = folder_paths.get_full_path("clip", clip_name1)
            clip_path2 = folder_paths.get_full_path("clip", clip_name2)
    
            if type == "sdxl":
                clip_type = comfy.sd.CLIPType.STABLE_DIFFUSION
            elif type == "sd3":
                clip_type = comfy.sd.CLIPType.SD3
    
            clip = comfy.sd.load_clip(ckpt_paths=[clip_path1, clip_path2], embedding_directory=folder_paths.get_folder_paths("embeddings"), clip_type=clip_type)
    
    class CLIPVisionLoader:
        @classmethod
        def INPUT_TYPES(s):
    
            return {"required": { "clip_name": (folder_paths.get_filename_list("clip_vision"), ),
    
                                 }}
        RETURN_TYPES = ("CLIP_VISION",)
        FUNCTION = "load_clip"
    
        CATEGORY = "loaders"
    
        def load_clip(self, clip_name):
    
            clip_path = folder_paths.get_full_path("clip_vision", clip_name)
    
            clip_vision = comfy.clip_vision.load(clip_path)
    
            return (clip_vision,)
    
    class CLIPVisionEncode:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": { "clip_vision": ("CLIP_VISION",),
                                  "image": ("IMAGE",)
                                 }}
    
        RETURN_TYPES = ("CLIP_VISION_OUTPUT",)
    
        FUNCTION = "encode"
    
    
        CATEGORY = "conditioning"
    
    
        def encode(self, clip_vision, image):
            output = clip_vision.encode_image(image)
            return (output,)
    
    class StyleModelLoader:
        @classmethod
        def INPUT_TYPES(s):
    
            return {"required": { "style_model_name": (folder_paths.get_filename_list("style_models"), )}}
    
    
        RETURN_TYPES = ("STYLE_MODEL",)
        FUNCTION = "load_style_model"
    
        CATEGORY = "loaders"
    
        def load_style_model(self, style_model_name):
    
            style_model_path = folder_paths.get_full_path("style_models", style_model_name)
    
            style_model = comfy.sd.load_style_model(style_model_path)
            return (style_model,)
    
    
    class StyleModelApply:
        @classmethod
        def INPUT_TYPES(s):
    
            return {"required": {"conditioning": ("CONDITIONING", ),
                                 "style_model": ("STYLE_MODEL", ),
                                 "clip_vision_output": ("CLIP_VISION_OUTPUT", ),
    
                                 }}
        RETURN_TYPES = ("CONDITIONING",)
        FUNCTION = "apply_stylemodel"
    
    
        CATEGORY = "conditioning/style_model"
    
        def apply_stylemodel(self, clip_vision_output, style_model, conditioning):
    
            cond = style_model.get_cond(clip_vision_output).flatten(start_dim=0, end_dim=1).unsqueeze(dim=0)
    
            for t in conditioning:
                n = [torch.cat((t[0], cond), dim=1), t[1].copy()]
    
                c.append(n)
            return (c, )
    
    
    class unCLIPConditioning:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": {"conditioning": ("CONDITIONING", ),
                                 "clip_vision_output": ("CLIP_VISION_OUTPUT", ),
                                 "strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
    
                                 "noise_augmentation": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
    
                                 }}
        RETURN_TYPES = ("CONDITIONING",)
        FUNCTION = "apply_adm"
    
    
        CATEGORY = "conditioning"
    
        def apply_adm(self, conditioning, clip_vision_output, strength, noise_augmentation):
    
            if strength == 0:
                return (conditioning, )
    
    
            c = []
            for t in conditioning:
                o = t[1].copy()
    
                x = {"clip_vision_output": clip_vision_output, "strength": strength, "noise_augmentation": noise_augmentation}
                if "unclip_conditioning" in o:
                    o["unclip_conditioning"] = o["unclip_conditioning"][:] + [x]
    
                    o["unclip_conditioning"] = [x]
    
                n = [t[0], o]
                c.append(n)
            return (c, )
    
    
    class GLIGENLoader:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": { "gligen_name": (folder_paths.get_filename_list("gligen"), )}}
    
        RETURN_TYPES = ("GLIGEN",)
        FUNCTION = "load_gligen"
    
    
        CATEGORY = "loaders"
    
    
        def load_gligen(self, gligen_name):
            gligen_path = folder_paths.get_full_path("gligen", gligen_name)
            gligen = comfy.sd.load_gligen(gligen_path)
            return (gligen,)
    
    class GLIGENTextBoxApply:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": {"conditioning_to": ("CONDITIONING", ),
                                  "clip": ("CLIP", ),
                                  "gligen_textbox_model": ("GLIGEN", ),
    
                                  "text": ("STRING", {"multiline": True, "dynamicPrompts": True}),
    
                                  "width": ("INT", {"default": 64, "min": 8, "max": MAX_RESOLUTION, "step": 8}),
                                  "height": ("INT", {"default": 64, "min": 8, "max": MAX_RESOLUTION, "step": 8}),
                                  "x": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}),
                                  "y": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}),