Files
yolain-ComfyUI-Easy-Use/py/easyNodes.py
T

6863 lines
299 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import sys, os, re, json, time, math, copy
import torch
import folder_paths
import comfy.utils, comfy.sample, comfy.samplers, comfy.controlnet, comfy.model_base, comfy.model_management
try:
import comfy.sampler_helpers
except:
pass
from comfy.sd import CLIP, VAE
from comfy.model_patcher import ModelPatcher
from comfy_extras.chainner_models import model_loading
from comfy_extras.nodes_mask import LatentCompositeMasked
from comfy.clip_vision import load as load_clip_vision
from urllib.request import urlopen
from PIL import Image
from server import PromptServer
from nodes import MAX_RESOLUTION, LatentFromBatch, RepeatLatentBatch, NODE_CLASS_MAPPINGS as ALL_NODE_CLASS_MAPPINGS, ConditioningSetMask, ConditioningConcat, CLIPTextEncode, VAEEncodeForInpaint, InpaintModelConditioning
from .config import MAX_SEED_NUM, BASE_RESOLUTIONS, RESOURCES_DIR, INPAINT_DIR, FOOOCUS_STYLES_DIR, FOOOCUS_INPAINT_HEAD, FOOOCUS_INPAINT_PATCH, IPADAPTER_DIR, IPADAPTER_MODELS, DYNAMICRAFTER_DIR, DYNAMICRAFTER_MODELS
from .log import log_node_info, log_node_error, log_node_warn
from .wildcards import process_with_loras, get_wildcard_list, process
from .adv_encode import advanced_encode
from .layer_diffuse.func import LayerDiffuse, LayerMethod
from .libs.utils import find_wildcards_seed, is_linked_styles_selector, easySave, get_local_filepath, add_folder_path_and_extensions, AlwaysEqualProxy
from .libs.loader import easyLoader
from .libs.sampler import easySampler
from .libs.xyplot import easyXYPlot
from .libs.controlnet import easyControlnet
from .libs.conditioning import prompt_to_cond, set_cond
from .libs import cache as backend_cache
from .libs.easing import EasingBase
sampler = easySampler()
easyCache = easyLoader()
default_calculate_weight = copy.copy(ModelPatcher.calculate_weight)
image_suffixs = set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".tiff", ".svg", ".ico", ".apng", ".tif", ".hdr", ".exr"])
model_path = folder_paths.models_dir
add_folder_path_and_extensions("ultralytics_bbox", [os.path.join(model_path, "ultralytics", "bbox")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("ultralytics_segm", [os.path.join(model_path, "ultralytics", "segm")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("ultralytics", [os.path.join(model_path, "ultralytics")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("mmdets_bbox", [os.path.join(model_path, "mmdets", "bbox")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("mmdets_segm", [os.path.join(model_path, "mmdets", "segm")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("mmdets", [os.path.join(model_path, "mmdets")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("sams", [os.path.join(model_path, "sams")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("onnx", [os.path.join(model_path, "onnx")], {'.onnx'})
add_folder_path_and_extensions("instantid", [os.path.join(model_path, "instantid")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("layer_model", [os.path.join(model_path, "layer_model")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("rembg", [os.path.join(model_path, "rembg")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("ipadapter", [os.path.join(model_path, "ipadapter")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("dynamicrafter_models", [os.path.join(model_path, "dynamicrafter_models")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("checkpoints_thumb", [os.path.join(model_path, "checkpoints")], image_suffixs)
add_folder_path_and_extensions("loras_thumb", [os.path.join(model_path, "loras")], image_suffixs)
# ---------------------------------------------------------------提示词 开始----------------------------------------------------------------------#
# 正面提示词
class positivePrompt:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {"required": {
"positive": ("STRING", {"default": "", "multiline": True, "placeholder": "Positive"}),}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("positive",)
FUNCTION = "main"
CATEGORY = "EasyUse/Prompt"
@staticmethod
def main(positive):
return positive,
# 通配符提示词
class wildcardsPrompt:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
wildcard_list = get_wildcard_list()
return {"required": {
"text": ("STRING", {"default": "", "multiline": True, "dynamicPrompts": False, "placeholder": "(Support Lora Block Weight and wildcard)"}),
"Select to add LoRA": (["Select the LoRA to add to the text"] + folder_paths.get_filename_list("loras"),),
"Select to add Wildcard": (["Select the Wildcard to add to the text"] + wildcard_list,),
"seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
"multiline_mode": ("BOOLEAN", {"default": False}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("text", "populated_text")
OUTPUT_IS_LIST = (True, True)
OUTPUT_NODE = True
FUNCTION = "main"
CATEGORY = "EasyUse/Prompt"
@staticmethod
def main(*args, **kwargs):
prompt = kwargs["prompt"] if "prompt" in kwargs else None
seed = kwargs["seed"]
# Clean loaded_objects
if prompt:
easyCache.update_loaded_objects(prompt)
text = kwargs['text']
if "multiline_mode" in kwargs and kwargs["multiline_mode"]:
populated_text = []
text = text.split("\n")
for t in text:
populated_text.append(process(t, seed))
else:
populated_text = [process(text, seed)]
text = [text]
return {"ui": {"value": [seed]}, "result": (text, populated_text)}
# 负面提示词
class negativePrompt:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {"required": {
"negative": ("STRING", {"default": "", "multiline": True, "placeholder": "Negative"}),}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("negative",)
FUNCTION = "main"
CATEGORY = "EasyUse/Prompt"
@staticmethod
def main(negative):
return negative,
# 风格提示词选择器
class stylesPromptSelector:
@classmethod
def INPUT_TYPES(s):
styles = ["fooocus_styles"]
styles_dir = FOOOCUS_STYLES_DIR
for file_name in os.listdir(styles_dir):
file = os.path.join(styles_dir, file_name)
if os.path.isfile(file) and file_name.endswith(".json") and "styles" in file_name.split(".")[0]:
styles.append(file_name.split(".")[0])
return {
"required": {
"styles": (styles, {"default": "fooocus_styles"}),
},
"optional": {
"positive": ("STRING", {"forceInput": True}),
"negative": ("STRING", {"forceInput": True}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
RETURN_TYPES = ("STRING", "STRING",)
RETURN_NAMES = ("positive", "negative",)
CATEGORY = 'EasyUse/Prompt'
FUNCTION = 'run'
OUTPUT_NODE = True
def replace_repeat(self, prompt):
prompt = prompt.replace(",", ",")
arr = prompt.split(",")
if len(arr) != len(set(arr)):
all_weight_prompt = re.findall(re.compile(r'[(](.*?)[)]', re.S), prompt)
if len(all_weight_prompt) > 0:
# others_prompt = prompt
# for w_prompt in all_weight_prompt:
# others_prompt = others_prompt.replace('(','').replace(')','')
# print(others_prompt)
return prompt
else:
for i in range(len(arr)):
arr[i] = arr[i].strip()
arr = list(set(arr))
return ", ".join(arr)
else:
return prompt
def run(self, styles, positive='', negative='', prompt=None, extra_pnginfo=None, my_unique_id=None):
values = []
all_styles = {}
positive_prompt, negative_prompt = '', negative
if styles == "fooocus_styles":
file = os.path.join(RESOURCES_DIR, styles + '.json')
else:
file = os.path.join(FOOOCUS_STYLES_DIR, styles + '.json')
f = open(file, 'r', encoding='utf-8')
data = json.load(f)
f.close()
for d in data:
all_styles[d['name']] = d
if my_unique_id in prompt:
if prompt[my_unique_id]["inputs"]['select_styles']:
values = prompt[my_unique_id]["inputs"]['select_styles'].split(',')
has_prompt = False
if len(values) == 0:
return (positive, negative)
for index, val in enumerate(values):
if 'prompt' in all_styles[val]:
if "{prompt}" in all_styles[val]['prompt'] and has_prompt == False:
positive_prompt = all_styles[val]['prompt'].format(prompt=positive)
has_prompt = True
else:
positive_prompt += ', ' + all_styles[val]['prompt'].replace(', {prompt}', '').replace('{prompt}', '')
if 'negative_prompt' in all_styles[val]:
negative_prompt += ', ' + all_styles[val]['negative_prompt'] if negative_prompt else all_styles[val]['negative_prompt']
if has_prompt == False and positive:
positive_prompt = positive + ', '
# 去重
positive_prompt = self.replace_repeat(positive_prompt) if positive_prompt else ''
negative_prompt = self.replace_repeat(negative_prompt) if negative_prompt else ''
return (positive_prompt, negative_prompt)
#promptList
class promptList:
@classmethod
def INPUT_TYPES(cls):
return {"required": {
"prompt_1": ("STRING", {"multiline": True, "default": ""}),
"prompt_2": ("STRING", {"multiline": True, "default": ""}),
"prompt_3": ("STRING", {"multiline": True, "default": ""}),
"prompt_4": ("STRING", {"multiline": True, "default": ""}),
"prompt_5": ("STRING", {"multiline": True, "default": ""}),
},
"optional": {
"optional_prompt_list": ("LIST",)
}
}
RETURN_TYPES = ("LIST", "STRING")
RETURN_NAMES = ("prompt_list", "prompt_strings")
OUTPUT_IS_LIST = (False, True)
FUNCTION = "run"
CATEGORY = "EasyUse/Prompt"
def run(self, **kwargs):
prompts = []
if "optional_prompt_list" in kwargs:
for l in kwargs["optional_prompt_list"]:
prompts.append(l)
# Iterate over the received inputs in sorted order.
for k in sorted(kwargs.keys()):
v = kwargs[k]
# Only process string input ports.
if isinstance(v, str) and v != '':
prompts.append(v)
return (prompts, prompts)
#promptLine
class promptLine:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"prompt": ("STRING", {"multiline": True, "default": "text"}),
"start_index": ("INT", {"default": 0, "min": 0, "max": 9999}),
"max_rows": ("INT", {"default": 1000, "min": 1, "max": 9999}),
},
"hidden":{
"workflow_prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"
}
}
RETURN_TYPES = ("STRING", AlwaysEqualProxy('*'))
RETURN_NAMES = ("STRING", "COMBO")
OUTPUT_IS_LIST = (True, True)
FUNCTION = "generate_strings"
CATEGORY = "EasyUse/Prompt"
def generate_strings(self, prompt, start_index, max_rows, workflow_prompt=None, my_unique_id=None):
lines = prompt.split('\n')
start_index = max(0, min(start_index, len(lines) - 1))
end_index = min(start_index + max_rows, len(lines))
rows = lines[start_index:end_index]
return (rows, rows)
class promptConcat:
@classmethod
def INPUT_TYPES(cls):
return {"required": {
},
"optional": {
"prompt1": ("STRING", {"multiline": False, "default": "", "forceInput": True}),
"prompt2": ("STRING", {"multiline": False, "default": "", "forceInput": True}),
"separator": ("STRING", {"multiline": False, "default": ""}),
},
}
RETURN_TYPES = ("STRING", )
RETURN_NAMES = ("prompt", )
FUNCTION = "concat_text"
CATEGORY = "EasyUse/Prompt"
def concat_text(self, prompt1="", prompt2="", separator=""):
return (prompt1 + separator + prompt2,)
class promptReplace:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"multiline": True, "default": "", "forceInput": True}),
},
"optional": {
"find1": ("STRING", {"multiline": False, "default": ""}),
"replace1": ("STRING", {"multiline": False, "default": ""}),
"find2": ("STRING", {"multiline": False, "default": ""}),
"replace2": ("STRING", {"multiline": False, "default": ""}),
"find3": ("STRING", {"multiline": False, "default": ""}),
"replace3": ("STRING", {"multiline": False, "default": ""}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("prompt",)
FUNCTION = "replace_text"
CATEGORY = "EasyUse/Prompt"
def replace_text(self, text, find1="", replace1="", find2="", replace2="", find3="", replace3=""):
text = text.replace(find1, replace1)
text = text.replace(find2, replace2)
text = text.replace(find3, replace3)
return (text,)
# 肖像大师
# Created by AI Wiz Art (Stefano Flore)
# Version: 2.2
# https://stefanoflore.it
# https://ai-wiz.art
class portraitMaster:
@classmethod
def INPUT_TYPES(s):
max_float_value = 1.95
prompt_path = os.path.join(RESOURCES_DIR, 'portrait_prompt.json')
if not os.path.exists(prompt_path):
response = urlopen('https://raw.githubusercontent.com/yolain/ComfyUI-Easy-Use/main/resources/portrait_prompt.json')
temp_prompt = json.loads(response.read())
prompt_serialized = json.dumps(temp_prompt, indent=4)
with open(prompt_path, "w") as f:
f.write(prompt_serialized)
del response, temp_prompt
# Load local
with open(prompt_path, 'r') as f:
list = json.load(f)
keys = [
['shot', 'COMBO', {"key": "shot_list"}], ['shot_weight', 'FLOAT'],
['gender', 'COMBO', {"default": "Woman", "key": "gender_list"}], ['age', 'INT', {"default": 30, "min": 18, "max": 90, "step": 1, "display": "slider"}],
['nationality_1', 'COMBO', {"default": "Chinese", "key": "nationality_list"}], ['nationality_2', 'COMBO', {"key": "nationality_list"}], ['nationality_mix', 'FLOAT'],
['body_type', 'COMBO', {"key": "body_type_list"}], ['body_type_weight', 'FLOAT'], ['model_pose', 'COMBO', {"key": "model_pose_list"}], ['eyes_color', 'COMBO', {"key": "eyes_color_list"}],
['facial_expression', 'COMBO', {"key": "face_expression_list"}], ['facial_expression_weight', 'FLOAT'], ['face_shape', 'COMBO', {"key": "face_shape_list"}], ['face_shape_weight', 'FLOAT'], ['facial_asymmetry', 'FLOAT'],
['hair_style', 'COMBO', {"key": "hair_style_list"}], ['hair_color', 'COMBO', {"key": "hair_color_list"}], ['disheveled', 'FLOAT'], ['beard', 'COMBO', {"key": "beard_list"}],
['skin_details', 'FLOAT'], ['skin_pores', 'FLOAT'], ['dimples', 'FLOAT'], ['freckles', 'FLOAT'],
['moles', 'FLOAT'], ['skin_imperfections', 'FLOAT'], ['skin_acne', 'FLOAT'], ['tanned_skin', 'FLOAT'],
['eyes_details', 'FLOAT'], ['iris_details', 'FLOAT'], ['circular_iris', 'FLOAT'], ['circular_pupil', 'FLOAT'],
['light_type', 'COMBO', {"key": "light_type_list"}], ['light_direction', 'COMBO', {"key": "light_direction_list"}], ['light_weight', 'FLOAT']
]
widgets = {}
for i, obj in enumerate(keys):
if obj[1] == 'COMBO':
key = obj[2]['key'] if obj[2] and 'key' in obj[2] else obj[0]
_list = list[key].copy()
_list.insert(0, '-')
widgets[obj[0]] = (_list, {**obj[2]})
elif obj[1] == 'FLOAT':
widgets[obj[0]] = ("FLOAT", {"default": 0, "step": 0.05, "min": 0, "max": max_float_value, "display": "slider",})
elif obj[1] == 'INT':
widgets[obj[0]] = (obj[1], obj[2])
del list
return {
"required": {
**widgets,
"photorealism_improvement": (["enable", "disable"],),
"prompt_start": ("STRING", {"multiline": True, "default": "raw photo, (realistic:1.5)"}),
"prompt_additional": ("STRING", {"multiline": True, "default": ""}),
"prompt_end": ("STRING", {"multiline": True, "default": ""}),
"negative_prompt": ("STRING", {"multiline": True, "default": ""}),
}
}
RETURN_TYPES = ("STRING", "STRING",)
RETURN_NAMES = ("positive", "negative",)
FUNCTION = "pm"
CATEGORY = "EasyUse/Prompt"
def pm(self, shot="-", shot_weight=1, gender="-", body_type="-", body_type_weight=0, eyes_color="-",
facial_expression="-", facial_expression_weight=0, face_shape="-", face_shape_weight=0,
nationality_1="-", nationality_2="-", nationality_mix=0.5, age=30, hair_style="-", hair_color="-",
disheveled=0, dimples=0, freckles=0, skin_pores=0, skin_details=0, moles=0, skin_imperfections=0,
wrinkles=0, tanned_skin=0, eyes_details=1, iris_details=1, circular_iris=1, circular_pupil=1,
facial_asymmetry=0, prompt_additional="", prompt_start="", prompt_end="", light_type="-",
light_direction="-", light_weight=0, negative_prompt="", photorealism_improvement="disable", beard="-",
model_pose="-", skin_acne=0):
prompt = []
if gender == "-":
gender = ""
else:
if age <= 25 and gender == 'Woman':
gender = 'girl'
if age <= 25 and gender == 'Man':
gender = 'boy'
gender = " " + gender + " "
if nationality_1 != '-' and nationality_2 != '-':
nationality = f"[{nationality_1}:{nationality_2}:{round(nationality_mix, 2)}]"
elif nationality_1 != '-':
nationality = nationality_1 + " "
elif nationality_2 != '-':
nationality = nationality_2 + " "
else:
nationality = ""
if prompt_start != "":
prompt.append(f"{prompt_start}")
if shot != "-" and shot_weight > 0:
prompt.append(f"({shot}:{round(shot_weight, 2)})")
prompt.append(f"({nationality}{gender}{round(age)}-years-old:1.5)")
if body_type != "-" and body_type_weight > 0:
prompt.append(f"({body_type}, {body_type} body:{round(body_type_weight, 2)})")
if model_pose != "-":
prompt.append(f"({model_pose}:1.5)")
if eyes_color != "-":
prompt.append(f"({eyes_color} eyes:1.25)")
if facial_expression != "-" and facial_expression_weight > 0:
prompt.append(
f"({facial_expression}, {facial_expression} expression:{round(facial_expression_weight, 2)})")
if face_shape != "-" and face_shape_weight > 0:
prompt.append(f"({face_shape} shape face:{round(face_shape_weight, 2)})")
if hair_style != "-":
prompt.append(f"({hair_style} hairstyle:1.25)")
if hair_color != "-":
prompt.append(f"({hair_color} hair:1.25)")
if beard != "-":
prompt.append(f"({beard}:1.15)")
if disheveled != "-" and disheveled > 0:
prompt.append(f"(disheveled:{round(disheveled, 2)})")
if prompt_additional != "":
prompt.append(f"{prompt_additional}")
if skin_details > 0:
prompt.append(f"(skin details, skin texture:{round(skin_details, 2)})")
if skin_pores > 0:
prompt.append(f"(skin pores:{round(skin_pores, 2)})")
if skin_imperfections > 0:
prompt.append(f"(skin imperfections:{round(skin_imperfections, 2)})")
if skin_acne > 0:
prompt.append(f"(acne, skin with acne:{round(skin_acne, 2)})")
if wrinkles > 0:
prompt.append(f"(skin imperfections:{round(wrinkles, 2)})")
if tanned_skin > 0:
prompt.append(f"(tanned skin:{round(tanned_skin, 2)})")
if dimples > 0:
prompt.append(f"(dimples:{round(dimples, 2)})")
if freckles > 0:
prompt.append(f"(freckles:{round(freckles, 2)})")
if moles > 0:
prompt.append(f"(skin pores:{round(moles, 2)})")
if eyes_details > 0:
prompt.append(f"(eyes details:{round(eyes_details, 2)})")
if iris_details > 0:
prompt.append(f"(iris details:{round(iris_details, 2)})")
if circular_iris > 0:
prompt.append(f"(circular iris:{round(circular_iris, 2)})")
if circular_pupil > 0:
prompt.append(f"(circular pupil:{round(circular_pupil, 2)})")
if facial_asymmetry > 0:
prompt.append(f"(facial asymmetry, face asymmetry:{round(facial_asymmetry, 2)})")
if light_type != '-' and light_weight > 0:
if light_direction != '-':
prompt.append(f"({light_type} {light_direction}:{round(light_weight, 2)})")
else:
prompt.append(f"({light_type}:{round(light_weight, 2)})")
if prompt_end != "":
prompt.append(f"{prompt_end}")
prompt = ", ".join(prompt)
prompt = prompt.lower()
if photorealism_improvement == "enable":
prompt = prompt + ", (professional photo, balanced photo, balanced exposure:1.2), (film grain:1.15)"
if photorealism_improvement == "enable":
negative_prompt = negative_prompt + ", (shinny skin, reflections on the skin, skin reflections:1.25)"
log_node_info("Portrait Master as generate the prompt:", prompt)
return (prompt, negative_prompt,)
# 潜空间sigma相乘
class latentNoisy:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
"steps": ("INT", {"default": 10000, "min": 0, "max": 10000}),
"start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}),
"end_at_step": ("INT", {"default": 10000, "min": 1, "max": 10000}),
"source": (["CPU", "GPU"],),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
},
"optional": {
"pipe": ("PIPE_LINE",),
"optional_model": ("MODEL",),
"optional_latent": ("LATENT",)
}}
RETURN_TYPES = ("PIPE_LINE", "LATENT", "FLOAT",)
RETURN_NAMES = ("pipe", "latent", "sigma",)
FUNCTION = "run"
CATEGORY = "EasyUse/Latent"
def run(self, sampler_name, scheduler, steps, start_at_step, end_at_step, source, seed, pipe=None, optional_model=None, optional_latent=None):
model = optional_model if optional_model is not None else pipe["model"]
batch_size = pipe["loader_settings"]["batch_size"]
empty_latent_height = pipe["loader_settings"]["empty_latent_height"]
empty_latent_width = pipe["loader_settings"]["empty_latent_width"]
if optional_latent is not None:
samples = optional_latent
else:
torch.manual_seed(seed)
if source == "CPU":
device = "cpu"
else:
device = comfy.model_management.get_torch_device()
noise = torch.randn((batch_size, 4, empty_latent_height // 8, empty_latent_width // 8), dtype=torch.float32,
device=device).cpu()
samples = {"samples": noise}
device = comfy.model_management.get_torch_device()
end_at_step = min(steps, end_at_step)
start_at_step = min(start_at_step, end_at_step)
comfy.model_management.load_model_gpu(model)
model_patcher = comfy.model_patcher.ModelPatcher(model.model, load_device=device, offload_device=comfy.model_management.unet_offload_device())
sampler = comfy.samplers.KSampler(model_patcher, steps=steps, device=device, sampler=sampler_name,
scheduler=scheduler, denoise=1.0, model_options=model.model_options)
sigmas = sampler.sigmas
sigma = sigmas[start_at_step] - sigmas[end_at_step]
sigma /= model.model.latent_format.scale_factor
sigma = sigma.cpu().numpy()
samples_out = samples.copy()
s1 = samples["samples"]
samples_out["samples"] = s1 * sigma
if pipe is None:
pipe = {}
new_pipe = {
**pipe,
"samples": samples_out
}
del pipe
return (new_pipe, samples_out, sigma)
# Latent遮罩复合
class latentCompositeMaskedWithCond:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"pipe": ("PIPE_LINE",),
"text_combine": ("LIST",),
"source_latent": ("LATENT",),
"source_mask": ("MASK",),
"destination_mask": ("MASK",),
"text_combine_mode": (["add", "replace", "cover"], {"default": "add"}),
"replace_text": ("STRING", {"default": ""})
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
OUTPUT_IS_LIST = (False, False, True)
RETURN_TYPES = ("PIPE_LINE", "LATENT", "CONDITIONING")
RETURN_NAMES = ("pipe", "latent", "conditioning",)
FUNCTION = "run"
OUTPUT_NODE = True
CATEGORY = "EasyUse/Latent"
def run(self, pipe, text_combine, source_latent, source_mask, destination_mask, text_combine_mode, replace_text, prompt=None, extra_pnginfo=None, my_unique_id=None):
positive = None
clip = pipe["clip"]
destination_latent = pipe["samples"]
conds = []
for text in text_combine:
if text_combine_mode == 'cover':
positive = text
elif text_combine_mode == 'replace' and replace_text != '':
positive = pipe["loader_settings"]["positive"].replace(replace_text, text)
else:
positive = pipe["loader_settings"]["positive"] + ',' + text
positive_token_normalization = pipe["loader_settings"]["positive_token_normalization"]
positive_weight_interpretation = pipe["loader_settings"]["positive_weight_interpretation"]
a1111_prompt_style = pipe["loader_settings"]["a1111_prompt_style"]
positive_cond = pipe["positive"]
log_node_warn("正在处理提示词编码...")
steps = pipe["loader_settings"]["steps"] if "steps" in pipe["loader_settings"] else 1
positive_embeddings_final = advanced_encode(clip, positive,
positive_token_normalization,
positive_weight_interpretation, w_max=1.0,
apply_to_pooled='enable', a1111_prompt_style=a1111_prompt_style, steps=steps)
# source cond
(cond_1,) = ConditioningSetMask().append(positive_cond, source_mask, "default", 1)
(cond_2,) = ConditioningSetMask().append(positive_embeddings_final, destination_mask, "default", 1)
positive_cond = cond_1 + cond_2
conds.append(positive_cond)
# latent composite masked
(samples,) = LatentCompositeMasked().composite(destination_latent, source_latent, 0, 0, False)
new_pipe = {
**pipe,
"samples": samples,
"loader_settings": {
**pipe["loader_settings"],
"positive": positive,
}
}
del pipe
return (new_pipe, samples, conds)
# 随机种
class easySeed:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("seed",)
FUNCTION = "doit"
CATEGORY = "EasyUse/Seed"
OUTPUT_NODE = True
def doit(self, seed=0, prompt=None, extra_pnginfo=None, my_unique_id=None):
return seed,
# 全局随机种
class globalSeed:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"value": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
"mode": ("BOOLEAN", {"default": True, "label_on": "control_before_generate", "label_off": "control_after_generate"}),
"action": (["fixed", "increment", "decrement", "randomize",
"increment for each node", "decrement for each node", "randomize for each node"], ),
"last_seed": ("STRING", {"default": ""}),
}
}
RETURN_TYPES = ()
FUNCTION = "doit"
CATEGORY = "EasyUse/Seed"
OUTPUT_NODE = True
def doit(self, **kwargs):
return {}
#---------------------------------------------------------------提示词 结束------------------------------------------------------------------------#
#---------------------------------------------------------------加载器 开始----------------------------------------------------------------------#
# 简易加载器完整
class fullLoader:
@classmethod
def INPUT_TYPES(cls):
resolution_strings = [f"{width} x {height}" for width, height in BASE_RESOLUTIONS]
a1111_prompt_style_default = False
return {"required": {
"ckpt_name": (folder_paths.get_filename_list("checkpoints"),),
"config_name": (["Default", ] + folder_paths.get_filename_list("configs"), {"default": "Default"}),
"vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),),
"clip_skip": ("INT", {"default": -1, "min": -24, "max": 0, "step": 1}),
"lora_name": (["None"] + folder_paths.get_filename_list("loras"),),
"lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"lora_clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"resolution": (resolution_strings,),
"empty_latent_width": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}),
"empty_latent_height": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}),
"positive": ("STRING", {"default": "Positive", "multiline": True}),
"positive_token_normalization": (["none", "mean", "length", "length+mean"],),
"positive_weight_interpretation": (["comfy", "A1111", "comfy++", "compel", "fixed attention"],),
"negative": ("STRING", {"default": "Negative", "multiline": True}),
"negative_token_normalization": (["none", "mean", "length", "length+mean"],),
"negative_weight_interpretation": (["comfy", "A1111", "comfy++", "compel", "fixed attention"],),
"batch_size": ("INT", {"default": 1, "min": 1, "max": 64}),
},
"optional": {"model_override": ("MODEL",), "clip_override": ("CLIP",), "vae_override": ("VAE",), "optional_lora_stack": ("LORA_STACK",), "a1111_prompt_style": ("BOOLEAN", {"default": a1111_prompt_style_default}),},
"hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"}
}
RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE", "CLIP", "CONDITIONING", "CONDITIONING", "LATENT")
RETURN_NAMES = ("pipe", "model", "vae", "clip", "positive", "negative", "latent")
FUNCTION = "adv_pipeloader"
CATEGORY = "EasyUse/Loaders"
def adv_pipeloader(self, ckpt_name, config_name, vae_name, clip_skip,
lora_name, lora_model_strength, lora_clip_strength,
resolution, empty_latent_width, empty_latent_height,
positive, positive_token_normalization, positive_weight_interpretation,
negative, negative_token_normalization, negative_weight_interpretation,
batch_size, model_override=None, clip_override=None, vae_override=None, optional_lora_stack=None, a1111_prompt_style=False, prompt=None,
my_unique_id=None
):
model: ModelPatcher | None = None
clip: CLIP | None = None
vae: VAE | None = None
can_load_lora = True
pipe_lora_stack = []
# resolution
if resolution != "自定义 x 自定义":
try:
width, height = map(int, resolution.split(' x '))
empty_latent_width = width
empty_latent_height = height
except ValueError:
raise ValueError("Invalid base_resolution format.")
# Create Empty Latent
latent = torch.zeros([batch_size, 4, empty_latent_height // 8, empty_latent_width // 8]).cpu()
samples = {"samples": latent}
# Clean models from loaded_objects
easyCache.update_loaded_objects(prompt)
log_node_warn("正在处理模型...")
# 判断是否存在 模型或Lora叠加xyplot, 若存在优先缓存第一个模型
xy_model_id = next((x for x in prompt if str(prompt[x]["class_type"]) in ["easy XYInputs: ModelMergeBlocks", "easy XYInputs: Checkpoint"]), None)
xy_lora_id = next((x for x in prompt if str(prompt[x]["class_type"]) == "easy XYInputs: Lora"), None)
if xy_lora_id is not None:
can_load_lora = False
if xy_model_id is not None:
node = prompt[xy_model_id]
if "ckpt_name_1" in node["inputs"]:
ckpt_name_1 = node["inputs"]["ckpt_name_1"]
model, clip, vae, clip_vision = easyCache.load_checkpoint(ckpt_name_1)
can_load_lora = False
# Load models
elif model_override is not None and clip_override is not None and vae_override is not None:
model = model_override
clip = clip_override
vae = vae_override
elif model_override is not None:
raise Exception(f"[ERROR] clip or vae is missing")
elif vae_override is not None:
raise Exception(f"[ERROR] model or clip is missing")
elif clip_override is not None:
raise Exception(f"[ERROR] model or vae is missing")
else:
model, clip, vae, clip_vision = easyCache.load_checkpoint(ckpt_name, config_name)
if optional_lora_stack is not None and can_load_lora:
for lora in optional_lora_stack:
lora = {"lora_name": lora[0], "model": model, "clip": clip, "model_strength": lora[1], "clip_strength": lora[2]}
model, clip = easyCache.load_lora(lora)
lora['model'] = model
lora['clip'] = clip
pipe_lora_stack.append(lora)
if lora_name != "None" and can_load_lora:
lora = {"lora_name": lora_name, "model": model, "clip": clip, "model_strength": lora_model_strength,
"clip_strength": lora_clip_strength}
model, clip = easyCache.load_lora(lora)
pipe_lora_stack.append(lora)
# Check for custom VAE
if vae_name not in ["Baked VAE", "Baked-VAE"]:
vae = easyCache.load_vae(vae_name)
# CLIP skip
if not clip:
raise Exception("No CLIP found")
positive_embeddings_final, positive_wildcard_prompt, model, clip = prompt_to_cond('positive', model, clip, clip_skip, pipe_lora_stack, positive, positive_token_normalization, positive_weight_interpretation, a1111_prompt_style, my_unique_id, prompt, easyCache)
negative_embeddings_final, negative_wildcard_prompt, model, clip = prompt_to_cond('negative', model, clip, clip_skip, pipe_lora_stack, negative, negative_token_normalization, negative_weight_interpretation, a1111_prompt_style, my_unique_id, prompt, easyCache)
image = easySampler.pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0)))
log_node_warn("处理结束...")
pipe = {"model": model,
"positive": positive_embeddings_final,
"negative": negative_embeddings_final,
"vae": vae,
"clip": clip,
"samples": samples,
"images": image,
"seed": 0,
"loader_settings": {"ckpt_name": ckpt_name,
"vae_name": vae_name,
"lora_name": lora_name,
"lora_model_strength": lora_model_strength,
"lora_clip_strength": lora_clip_strength,
"lora_stack": pipe_lora_stack,
"refiner_ckpt_name": None,
"refiner_vae_name": None,
"refiner_lora_name": None,
"refiner_lora_model_strength": None,
"refiner_lora_clip_strength": None,
"clip_skip": clip_skip,
"a1111_prompt_style": a1111_prompt_style,
"positive": positive,
"positive_l": None,
"positive_g": None,
"positive_token_normalization": positive_token_normalization,
"positive_weight_interpretation": positive_weight_interpretation,
"positive_balance": None,
"negative": negative,
"negative_l": None,
"negative_g": None,
"negative_token_normalization": negative_token_normalization,
"negative_weight_interpretation": negative_weight_interpretation,
"negative_balance": None,
"empty_latent_width": empty_latent_width,
"empty_latent_height": empty_latent_height,
"batch_size": batch_size,
"seed": 0,
"empty_samples": samples, }
}
return {"ui": {"positive": positive_wildcard_prompt, "negative": negative_wildcard_prompt}, "result": (pipe, model, vae, clip, positive_embeddings_final, negative_embeddings_final, samples)}
# A1111简易加载器
class a1111Loader:
@classmethod
def INPUT_TYPES(cls):
resolution_strings = [f"{width} x {height}" for width, height in BASE_RESOLUTIONS]
a1111_prompt_style_default = False
checkpoints = folder_paths.get_filename_list("checkpoints")
loras = ["None"] + folder_paths.get_filename_list("loras")
return {"required": {
"ckpt_name": (checkpoints,),
"vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),),
"clip_skip": ("INT", {"default": -1, "min": -24, "max": 0, "step": 1}),
"lora_name": (loras,),
"lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"lora_clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"resolution": (resolution_strings, {"default": "512 x 512"}),
"empty_latent_width": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}),
"empty_latent_height": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}),
"positive": ("STRING", {"default": "Positive", "multiline": True}),
"negative": ("STRING", {"default": "Negative", "multiline": True}),
"batch_size": ("INT", {"default": 1, "min": 1, "max": 64}),
},
"optional": {"optional_lora_stack": ("LORA_STACK",), "a1111_prompt_style": ("BOOLEAN", {"default": a1111_prompt_style_default})},
"hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"}
}
RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE")
RETURN_NAMES = ("pipe", "model", "vae")
FUNCTION = "adv_pipeloader"
CATEGORY = "EasyUse/Loaders"
def adv_pipeloader(self, ckpt_name, vae_name, clip_skip,
lora_name, lora_model_strength, lora_clip_strength,
resolution, empty_latent_width, empty_latent_height,
positive, negative, batch_size, optional_lora_stack=None, a1111_prompt_style=False, prompt=None,
my_unique_id=None):
return fullLoader.adv_pipeloader(self, ckpt_name, 'Default', vae_name, clip_skip,
lora_name, lora_model_strength, lora_clip_strength,
resolution, empty_latent_width, empty_latent_height,
positive, 'mean', 'A1111',
negative,'mean','A1111',
batch_size, None, None, None, optional_lora_stack, a1111_prompt_style, prompt,
my_unique_id
)
# Comfy简易加载器
class comfyLoader:
@classmethod
def INPUT_TYPES(cls):
resolution_strings = [f"{width} x {height}" for width, height in BASE_RESOLUTIONS]
return {"required": {
"ckpt_name": (folder_paths.get_filename_list("checkpoints"),),
"vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),),
"clip_skip": ("INT", {"default": -1, "min": -24, "max": 0, "step": 1}),
"lora_name": (["None"] + folder_paths.get_filename_list("loras"),),
"lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"lora_clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"resolution": (resolution_strings, {"default": "512 x 512"}),
"empty_latent_width": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}),
"empty_latent_height": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}),
"positive": ("STRING", {"default": "Positive", "multiline": True}),
"negative": ("STRING", {"default": "Negative", "multiline": True}),
"batch_size": ("INT", {"default": 1, "min": 1, "max": 64}),
},
"optional": {"optional_lora_stack": ("LORA_STACK",)},
"hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"}}
RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE")
RETURN_NAMES = ("pipe", "model", "vae")
FUNCTION = "adv_pipeloader"
CATEGORY = "EasyUse/Loaders"
def adv_pipeloader(self, ckpt_name, vae_name, clip_skip,
lora_name, lora_model_strength, lora_clip_strength,
resolution, empty_latent_width, empty_latent_height,
positive, negative, batch_size, optional_lora_stack=None, prompt=None,
my_unique_id=None):
return fullLoader.adv_pipeloader(self, ckpt_name, 'Default', vae_name, clip_skip,
lora_name, lora_model_strength, lora_clip_strength,
resolution, empty_latent_width, empty_latent_height,
positive, 'none', 'comfy',
negative, 'none', 'comfy',
batch_size, None, None, None, optional_lora_stack, False, prompt,
my_unique_id
)
# stable Cascade
class cascadeLoader:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
resolution_strings = [f"{width} x {height}" for width, height in BASE_RESOLUTIONS]
return {"required": {
"stage_c": (folder_paths.get_filename_list("unet") + folder_paths.get_filename_list("checkpoints"),),
"stage_b": (folder_paths.get_filename_list("unet") + folder_paths.get_filename_list("checkpoints"),),
"stage_a": (["Baked VAE"]+folder_paths.get_filename_list("vae"),),
"clip_name": (["None"] + folder_paths.get_filename_list("clip"),),
"lora_name": (["None"] + folder_paths.get_filename_list("loras"),),
"lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"lora_clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"resolution": (resolution_strings, {"default": "1024 x 1024"}),
"empty_latent_width": ("INT", {"default": 1024, "min": 16, "max": MAX_RESOLUTION, "step": 8}),
"empty_latent_height": ("INT", {"default": 1024, "min": 16, "max": MAX_RESOLUTION, "step": 8}),
"compression": ("INT", {"default": 42, "min": 32, "max": 64, "step": 1}),
"positive": ("STRING", {"default": "Positive", "multiline": True}),
"negative": ("STRING", {"default": "", "multiline": True}),
"batch_size": ("INT", {"default": 1, "min": 1, "max": 64}),
},
"optional": {"optional_lora_stack": ("LORA_STACK",), },
"hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"}
}
RETURN_TYPES = ("PIPE_LINE", "MODEL", "LATENT", "VAE")
RETURN_NAMES = ("pipe", "model_c", "latent_c", "vae")
FUNCTION = "adv_pipeloader"
CATEGORY = "EasyUse/Loaders"
def is_ckpt(self, name):
is_ckpt = False
path = folder_paths.get_full_path("checkpoints", name)
if path is not None:
is_ckpt = True
return is_ckpt
def adv_pipeloader(self, stage_c, stage_b, stage_a, clip_name, lora_name, lora_model_strength, lora_clip_strength,
resolution, empty_latent_width, empty_latent_height, compression,
positive, negative, batch_size, optional_lora_stack=None,prompt=None,
my_unique_id=None):
vae: VAE | None = None
model_c: ModelPatcher | None = None
model_b: ModelPatcher | None = None
clip: CLIP | None = None
can_load_lora = True
pipe_lora_stack = []
# resolution
if resolution != "自定义 x 自定义":
try:
width, height = map(int, resolution.split(' x '))
empty_latent_width = width
empty_latent_height = height
except ValueError:
raise ValueError("Invalid base_resolution format.")
# Create Empty Latent
latent_c = torch.zeros([batch_size, 16, empty_latent_height // compression, empty_latent_width // compression])
latent_b = torch.zeros([batch_size, 4, empty_latent_height // 4, empty_latent_width // 4])
samples = ({"samples": latent_c}, {"samples": latent_b})
# Clean models from loaded_objects
easyCache.update_loaded_objects(prompt)
if self.is_ckpt(stage_c):
model_c, clip, vae_c, clip_vision = easyCache.load_checkpoint(stage_c)
else:
model_c = easyCache.load_unet(stage_c)
vae_c = None
if self.is_ckpt(stage_b):
model_b, clip, vae_b, clip_vision = easyCache.load_checkpoint(stage_b)
else:
model_b = easyCache.load_unet(stage_b)
vae_b = None
if optional_lora_stack is not None and can_load_lora:
for lora in optional_lora_stack:
lora = {"lora_name": lora[0], "model": model_c, "clip": clip, "model_strength": lora[1], "clip_strength": lora[2]}
model_c, clip = easyCache.load_lora(lora)
lora['model'] = model_c
lora['clip'] = clip
pipe_lora_stack.append(lora)
if lora_name != "None" and can_load_lora:
lora = {"lora_name": lora_name, "model": model_c, "clip": clip, "model_strength": lora_model_strength,
"clip_strength": lora_clip_strength}
model_c, clip = easyCache.load_lora(lora)
pipe_lora_stack.append(lora)
model = (model_c, model_b)
# Load clip
if clip_name != 'None':
clip = easyCache.load_clip(clip_name, "stable_cascade")
# Load vae
if stage_a not in ["Baked VAE", "Baked-VAE"]:
vae_b = easyCache.load_vae(stage_a)
vae = (vae_c, vae_b)
# 判断是否连接 styles selector
is_positive_linked_styles_selector = is_linked_styles_selector(prompt, my_unique_id, 'positive')
is_negative_linked_styles_selector = is_linked_styles_selector(prompt, my_unique_id, 'negative')
log_node_warn("正在处理提示词...")
positive_seed = find_wildcards_seed(my_unique_id, positive, prompt)
model_c, clip, positive, positive_decode, show_positive_prompt, pipe_lora_stack = process_with_loras(positive,
model_c, clip,
"positive",
positive_seed,
can_load_lora,
pipe_lora_stack,
easyCache)
positive_wildcard_prompt = positive_decode if show_positive_prompt or is_positive_linked_styles_selector else ""
negative_seed = find_wildcards_seed(my_unique_id, negative, prompt)
model_c, clip, negative, negative_decode, show_negative_prompt, pipe_lora_stack = process_with_loras(negative,
model_c, clip,
"negative",
negative_seed,
can_load_lora,
pipe_lora_stack,
easyCache)
negative_wildcard_prompt = negative_decode if show_negative_prompt or is_negative_linked_styles_selector else ""
tokens = clip.tokenize(positive)
cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True)
positive_embeddings_final = [[cond, {"pooled_output": pooled}]]
tokens = clip.tokenize(negative)
cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True)
negative_embeddings_final = [[cond, {"pooled_output": pooled}]]
image = easySampler.pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0)))
log_node_warn("处理结束...")
pipe = {
"model": model,
"positive": positive_embeddings_final,
"negative": negative_embeddings_final,
"vae": vae,
"clip": clip,
"samples": samples,
"images": image,
"seed": 0,
"loader_settings": {
"vae_name": stage_a,
"lora_stack": pipe_lora_stack,
"refiner_ckpt_name": None,
"refiner_vae_name": None,
"refiner_lora_name": None,
"refiner_lora_model_strength": None,
"refiner_lora_clip_strength": None,
"positive": positive,
"positive_l": None,
"positive_g": None,
"positive_token_normalization": 'none',
"positive_weight_interpretation": 'comfy',
"positive_balance": None,
"negative": negative,
"negative_l": None,
"negative_g": None,
"negative_token_normalization": 'none',
"negative_weight_interpretation": 'comfy',
"negative_balance": None,
"empty_latent_width": empty_latent_width,
"empty_latent_height": empty_latent_height,
"batch_size": batch_size,
"seed": 0,
"empty_samples": samples,
"compression": compression
}
}
return {"ui": {"positive": positive_wildcard_prompt, "negative": negative_wildcard_prompt},
"result": (pipe, model_c, model_b, vae)}
# Zero123简易加载器 (3D)
try:
from comfy_extras.nodes_stable3d import camera_embeddings
except FileNotFoundError:
log_node_error("EasyUse[zero123Loader]", "请更新ComfyUI到最新版本")
class zero123Loader:
@classmethod
def INPUT_TYPES(cls):
def get_file_list(filenames):
return [file for file in filenames if file != "put_models_here.txt" and "zero123" in file.lower()]
return {"required": {
"ckpt_name": (get_file_list(folder_paths.get_filename_list("checkpoints")),),
"vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),),
"init_image": ("IMAGE",),
"empty_latent_width": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}),
"empty_latent_height": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}),
"batch_size": ("INT", {"default": 1, "min": 1, "max": 64}),
"elevation": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0}),
"azimuth": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0}),
},
"hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"}
}
RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE")
RETURN_NAMES = ("pipe", "model", "vae")
FUNCTION = "adv_pipeloader"
CATEGORY = "EasyUse/Loaders"
def adv_pipeloader(self, ckpt_name, vae_name, init_image, empty_latent_width, empty_latent_height, batch_size, elevation, azimuth, prompt=None, my_unique_id=None):
model: ModelPatcher | None = None
vae: VAE | None = None
clip: CLIP | None = None
clip_vision = None
# Clean models from loaded_objects
easyCache.update_loaded_objects(prompt)
model, clip, vae, clip_vision = easyCache.load_checkpoint(ckpt_name, "Default", True)
output = clip_vision.encode_image(init_image)
pooled = output.image_embeds.unsqueeze(0)
pixels = comfy.utils.common_upscale(init_image.movedim(-1, 1), empty_latent_width, empty_latent_height, "bilinear", "center").movedim(1, -1)
encode_pixels = pixels[:, :, :, :3]
t = vae.encode(encode_pixels)
cam_embeds = camera_embeddings(elevation, azimuth)
cond = torch.cat([pooled, cam_embeds.repeat((pooled.shape[0], 1, 1))], dim=-1)
positive = [[cond, {"concat_latent_image": t}]]
negative = [[torch.zeros_like(pooled), {"concat_latent_image": torch.zeros_like(t)}]]
latent = torch.zeros([batch_size, 4, empty_latent_height // 8, empty_latent_width // 8])
samples = {"samples": latent}
image = easySampler.pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0)))
pipe = {"model": model,
"positive": positive,
"negative": negative,
"vae": vae,
"clip": clip,
"samples": samples,
"images": image,
"seed": 0,
"loader_settings": {"ckpt_name": ckpt_name,
"vae_name": vae_name,
"positive": positive,
"positive_l": None,
"positive_g": None,
"positive_balance": None,
"negative": negative,
"negative_l": None,
"negative_g": None,
"negative_balance": None,
"empty_latent_width": empty_latent_width,
"empty_latent_height": empty_latent_height,
"batch_size": batch_size,
"seed": 0,
"empty_samples": samples, }
}
return (pipe, model, vae)
# SV3D加载器
class sv3DLoader(EasingBase):
def __init__(self):
super().__init__()
@classmethod
def INPUT_TYPES(cls):
def get_file_list(filenames):
return [file for file in filenames if file != "put_models_here.txt" and "sv3d" in file]
return {"required": {
"ckpt_name": (get_file_list(folder_paths.get_filename_list("checkpoints")),),
"vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),),
"init_image": ("IMAGE",),
"empty_latent_width": ("INT", {"default": 576, "min": 16, "max": MAX_RESOLUTION, "step": 8}),
"empty_latent_height": ("INT", {"default": 576, "min": 16, "max": MAX_RESOLUTION, "step": 8}),
"batch_size": ("INT", {"default": 21, "min": 1, "max": 4096}),
"interp_easing": (["linear", "ease_in", "ease_out", "ease_in_out"], {"default": "linear"}),
"easing_mode": (["azimuth", "elevation", "custom"], {"default": "azimuth"}),
},
"optional": {"scheduler": ("STRING", {"default": "", "multiline": True})},
"hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"}
}
RETURN_TYPES = ("PIPE_LINE", "MODEL", "STRING")
RETURN_NAMES = ("pipe", "model", "interp_log")
FUNCTION = "adv_pipeloader"
CATEGORY = "EasyUse/Loaders"
def adv_pipeloader(self, ckpt_name, vae_name, init_image, empty_latent_width, empty_latent_height, batch_size, interp_easing, easing_mode, scheduler='',prompt=None, my_unique_id=None):
model: ModelPatcher | None = None
vae: VAE | None = None
clip: CLIP | None = None
# Clean models from loaded_objects
easyCache.update_loaded_objects(prompt)
model, clip, vae, clip_vision = easyCache.load_checkpoint(ckpt_name, "Default", True)
output = clip_vision.encode_image(init_image)
pooled = output.image_embeds.unsqueeze(0)
pixels = comfy.utils.common_upscale(init_image.movedim(-1, 1), empty_latent_width, empty_latent_height, "bilinear", "center").movedim(1,
-1)
encode_pixels = pixels[:, :, :, :3]
t = vae.encode(encode_pixels)
azimuth_points = []
elevation_points = []
if easing_mode == 'azimuth':
azimuth_points = [(0, 0), (batch_size-1, 360)]
elevation_points = [(0, 0)] * batch_size
elif easing_mode == 'elevation':
azimuth_points = [(0, 0)] * batch_size
elevation_points = [(0, -90), (batch_size-1, 90)]
else:
schedulers = scheduler.rstrip('\n')
for line in schedulers.split('\n'):
frame_str, point_str = line.split(':')
point_str = point_str.strip()[1:-1]
point = point_str.split(',')
azimuth_point = point[0]
elevation_point = point[1] if point[1] else 0.0
frame = int(frame_str.strip())
azimuth = float(azimuth_point)
azimuth_points.append((frame, azimuth))
elevation_val = float(elevation_point)
elevation_points.append((frame, elevation_val))
azimuth_points.sort(key=lambda x: x[0])
elevation_points.sort(key=lambda x: x[0])
#interpolation
next_point = 1
next_elevation_point = 1
elevations = []
azimuths = []
# For azimuth interpolation
for i in range(batch_size):
# Find the interpolated azimuth for the current frame
while next_point < len(azimuth_points) and i >= azimuth_points[next_point][0]:
next_point += 1
if next_point == len(azimuth_points):
next_point -= 1
prev_point = max(next_point - 1, 0)
if azimuth_points[next_point][0] != azimuth_points[prev_point][0]:
timing = (i - azimuth_points[prev_point][0]) / (
azimuth_points[next_point][0] - azimuth_points[prev_point][0])
interpolated_azimuth = self.ease(azimuth_points[prev_point][1], azimuth_points[next_point][1], self.easing(timing, interp_easing))
else:
interpolated_azimuth = azimuth_points[prev_point][1]
# Interpolate the elevation
next_elevation_point = 1
while next_elevation_point < len(elevation_points) and i >= elevation_points[next_elevation_point][0]:
next_elevation_point += 1
if next_elevation_point == len(elevation_points):
next_elevation_point -= 1
prev_elevation_point = max(next_elevation_point - 1, 0)
if elevation_points[next_elevation_point][0] != elevation_points[prev_elevation_point][0]:
timing = (i - elevation_points[prev_elevation_point][0]) / (
elevation_points[next_elevation_point][0] - elevation_points[prev_elevation_point][0])
interpolated_elevation = self.ease(elevation_points[prev_point][1], elevation_points[next_point][1], self.easing(timing, interp_easing))
else:
interpolated_elevation = elevation_points[prev_elevation_point][1]
azimuths.append(interpolated_azimuth)
elevations.append(interpolated_elevation)
log_node_info("easy sv3dLoader", "azimuths:" + str(azimuths))
log_node_info("easy sv3dLoader", "elevations:" + str(elevations))
log = 'azimuths:' + str(azimuths) + '\n\n' + "elevations:" + str(elevations)
# Structure the final output
positive = [[pooled, {"concat_latent_image": t, "elevation": elevations, "azimuth": azimuths}]]
negative = [[torch.zeros_like(pooled),
{"concat_latent_image": torch.zeros_like(t), "elevation": elevations, "azimuth": azimuths}]]
latent = torch.zeros([batch_size, 4, empty_latent_height // 8, empty_latent_width // 8])
samples = {"samples": latent}
image = easySampler.pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0)))
pipe = {"model": model,
"positive": positive,
"negative": negative,
"vae": vae,
"clip": clip,
"samples": samples,
"images": image,
"seed": 0,
"loader_settings": {"ckpt_name": ckpt_name,
"vae_name": vae_name,
"positive": positive,
"positive_l": None,
"positive_g": None,
"positive_balance": None,
"negative": negative,
"negative_l": None,
"negative_g": None,
"negative_balance": None,
"empty_latent_width": empty_latent_width,
"empty_latent_height": empty_latent_height,
"batch_size": batch_size,
"seed": 0,
"empty_samples": samples, }
}
return (pipe, model, log)
#svd加载器
class svdLoader:
@classmethod
def INPUT_TYPES(cls):
resolution_strings = [f"{width} x {height}" for width, height in BASE_RESOLUTIONS]
def get_file_list(filenames):
return [file for file in filenames if file != "put_models_here.txt" and "svd" in file.lower()]
return {"required": {
"ckpt_name": (get_file_list(folder_paths.get_filename_list("checkpoints")),),
"vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),),
"clip_name": (["None"] + folder_paths.get_filename_list("clip"),),
"init_image": ("IMAGE",),
"resolution": (resolution_strings, {"default": "1024 x 576"}),
"empty_latent_width": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}),
"empty_latent_height": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}),
"video_frames": ("INT", {"default": 14, "min": 1, "max": 4096}),
"motion_bucket_id": ("INT", {"default": 127, "min": 1, "max": 1023}),
"fps": ("INT", {"default": 6, "min": 1, "max": 1024}),
"augmentation_level": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.01})
},
"optional": {
"optional_positive": ("STRING", {"default": "", "multiline": True}),
"optional_negative": ("STRING", {"default": "", "multiline": True}),
},
"hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"}
}
RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE")
RETURN_NAMES = ("pipe", "model", "vae")
FUNCTION = "adv_pipeloader"
CATEGORY = "EasyUse/Loaders"
def adv_pipeloader(self, ckpt_name, vae_name, clip_name, init_image, resolution, empty_latent_width, empty_latent_height, video_frames, motion_bucket_id, fps, augmentation_level, optional_positive=None, optional_negative=None, prompt=None, my_unique_id=None):
model: ModelPatcher | None = None
vae: VAE | None = None
clip: CLIP | None = None
clip_vision = None
# resolution
if resolution != "自定义 x 自定义":
try:
width, height = map(int, resolution.split(' x '))
empty_latent_width = width
empty_latent_height = height
except ValueError:
raise ValueError("Invalid base_resolution format.")
# Clean models from loaded_objects
easyCache.update_loaded_objects(prompt)
model, clip, vae, clip_vision = easyCache.load_checkpoint(ckpt_name, "Default", True)
output = clip_vision.encode_image(init_image)
pooled = output.image_embeds.unsqueeze(0)
pixels = comfy.utils.common_upscale(init_image.movedim(-1, 1), empty_latent_width, empty_latent_height, "bilinear", "center").movedim(1, -1)
encode_pixels = pixels[:, :, :, :3]
if augmentation_level > 0:
encode_pixels += torch.randn_like(pixels) * augmentation_level
t = vae.encode(encode_pixels)
positive = [[pooled,
{"motion_bucket_id": motion_bucket_id, "fps": fps, "augmentation_level": augmentation_level,
"concat_latent_image": t}]]
negative = [[torch.zeros_like(pooled),
{"motion_bucket_id": motion_bucket_id, "fps": fps, "augmentation_level": augmentation_level,
"concat_latent_image": torch.zeros_like(t)}]]
if optional_positive is not None and optional_positive != '':
if clip_name == 'None':
raise Exception("You need choose a open_clip model when positive is not empty")
clip = easyCache.load_clip(clip_name)
positive_embeddings_final, = CLIPTextEncode().encode(clip, optional_positive)
positive, = ConditioningConcat().concat(positive, positive_embeddings_final)
if optional_negative is not None and optional_negative != '':
if clip_name == 'None':
raise Exception("You need choose a open_clip model when negative is not empty")
negative_embeddings_final, = CLIPTextEncode().encode(clip, optional_negative)
negative, = ConditioningConcat().concat(negative, negative_embeddings_final)
latent = torch.zeros([video_frames, 4, empty_latent_height // 8, empty_latent_width // 8])
samples = {"samples": latent}
image = easySampler.pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0)))
pipe = {"model": model,
"positive": positive,
"negative": negative,
"vae": vae,
"clip": clip,
"samples": samples,
"images": image,
"seed": 0,
"loader_settings": {"ckpt_name": ckpt_name,
"vae_name": vae_name,
"positive": positive,
"positive_l": None,
"positive_g": None,
"positive_balance": None,
"negative": negative,
"negative_l": None,
"negative_g": None,
"negative_balance": None,
"empty_latent_width": empty_latent_width,
"empty_latent_height": empty_latent_height,
"batch_size": 1,
"seed": 0,
"empty_samples": samples, }
}
return (pipe, model, vae)
#dynamiCrafter加载器
from .dynamiCrafter import DynamiCrafter
class dynamiCrafterLoader(DynamiCrafter):
def __init__(self):
super().__init__()
@classmethod
def INPUT_TYPES(cls):
resolution_strings = [f"{width} x {height}" for width, height in BASE_RESOLUTIONS]
return {"required": {
"model_name": (list(DYNAMICRAFTER_MODELS.keys()),),
"clip_skip": ("INT", {"default": -2, "min": -24, "max": 0, "step": 1}),
"init_image": ("IMAGE",),
"resolution": (resolution_strings, {"default": "512 x 512"}),
"empty_latent_width": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}),
"empty_latent_height": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}),
"positive": ("STRING", {"default": "", "multiline": True}),
"negative": ("STRING", {"default": "", "multiline": True}),
"use_interpolate": ("BOOLEAN", {"default": False}),
"fps": ("INT", {"default": 15, "min": 1, "max": 30, "step": 1},),
"frames": ("INT", {"default": 16}),
"scale_latents": ("BOOLEAN", {"default": False})
},
"optional": {
"optional_vae": ("VAE",),
},
"hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"}
}
RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE")
RETURN_NAMES = ("pipe", "model", "vae")
FUNCTION = "adv_pipeloader"
CATEGORY = "EasyUse/Loaders"
def get_clip_file(self, node_name):
clip_list = folder_paths.get_filename_list("clip")
pattern = 'sd2-1-open-clip|model\.(safetensors|bin)$'
clip_files = [e for e in clip_list if re.search(pattern, e, re.IGNORECASE)]
clip_name = clip_files[0] if len(clip_files)>0 else None
clip_file = folder_paths.get_full_path("clip", clip_name) if clip_name else None
if clip_name is not None:
log_node_info(node_name, f"Using {clip_name}")
return clip_file, clip_name
def get_clipvision_file(self, node_name):
clipvision_list = folder_paths.get_filename_list("clip_vision")
pattern = '(ViT.H.14.*s32B.b79K|ipadapter.*sd15|sd1.?5.*model|open_clip_pytorch_model\.(bin|safetensors))'
clipvision_files = [e for e in clipvision_list if re.search(pattern, e, re.IGNORECASE)]
clipvision_name = clipvision_files[0] if len(clipvision_files)>0 else None
clipvision_file = folder_paths.get_full_path("clip_vision", clipvision_name) if clipvision_name else None
if clipvision_name is not None:
log_node_info(node_name, f"Using {clipvision_name}")
return clipvision_file, clipvision_name
def get_vae_file(self, node_name):
vae_list = folder_paths.get_filename_list("vae")
pattern = 'vae-ft-mse-840000-ema-pruned\.(pt|bin|safetensors)$'
vae_files = [e for e in vae_list if re.search(pattern, e, re.IGNORECASE)]
vae_name = vae_files[0] if len(vae_files)>0 else None
vae_file = folder_paths.get_full_path("vae", vae_name) if vae_name else None
if vae_name is not None:
log_node_info(node_name, f"Using {vae_name}")
return vae_file, vae_name
def adv_pipeloader(self, model_name, clip_skip, init_image, resolution, empty_latent_width, empty_latent_height, positive, negative, use_interpolate, fps, frames, scale_latents, optional_vae=None, prompt=None, my_unique_id=None):
positive_embeddings_final, negative_embeddings_final = None, None
# resolution
if resolution != "自定义 x 自定义":
try:
width, height = map(int, resolution.split(' x '))
empty_latent_width = width
empty_latent_height = height
except ValueError:
raise ValueError("Invalid base_resolution format.")
# Clean models from loaded_objects
easyCache.update_loaded_objects(prompt)
models_0 = list(DYNAMICRAFTER_MODELS.keys())[0]
if optional_vae:
vae = optional_vae
vae_name = None
else:
vae_file, vae_name = self.get_vae_file("easy dynamiCrafterLoader")
if vae_file is None:
vae_name = "vae-ft-mse-840000-ema-pruned.safetensors"
get_local_filepath(DYNAMICRAFTER_MODELS[models_0]['vae_url'], os.path.join(folder_paths.models_dir, "vae"),
vae_name)
vae = easyCache.load_vae(vae_name)
clip_file, clip_name = self.get_clip_file("easy dynamiCrafterLoader")
if clip_file is None:
clip_name = 'sd2-1-open-clip.safetensors'
get_local_filepath(DYNAMICRAFTER_MODELS[models_0]['clip_url'], os.path.join(folder_paths.models_dir, "clip"),
clip_name)
clip = easyCache.load_clip(clip_name)
# load clip vision
clip_vision_file, clip_vision_name = self.get_clipvision_file("easy dynamiCrafterLoader")
if clip_vision_file is None:
clip_vision_name = 'CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors'
clip_vision_file = get_local_filepath(DYNAMICRAFTER_MODELS[models_0]['clip_vision_url'], os.path.join(folder_paths.models_dir, "clip_vision"),
clip_vision_name)
clip_vision = load_clip_vision(clip_vision_file)
# load unet model
model_path = get_local_filepath(DYNAMICRAFTER_MODELS[model_name]['model_url'], DYNAMICRAFTER_DIR)
model_patcher, image_proj_model = self.load_dynamicrafter(model_path)
# rescale cfg
# apply
model, empty_latent, image_latent = self.process_image_conditioning(model_patcher, clip_vision, vae, image_proj_model, init_image, use_interpolate, fps, frames, scale_latents)
clipped = clip.clone()
if clip_skip != 0:
clipped.clip_layer(clip_skip)
if positive is not None and positive != '':
positive_embeddings_final, = CLIPTextEncode().encode(clipped, positive)
if negative is not None and negative != '':
negative_embeddings_final, = CLIPTextEncode().encode(clipped, negative)
image = easySampler.pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0)))
pipe = {"model": model,
"positive": positive_embeddings_final,
"negative": negative_embeddings_final,
"vae": vae,
"clip": clip,
"clip_vision": clip_vision,
"samples": empty_latent,
"images": image,
"seed": 0,
"loader_settings": {"ckpt_name": model_name,
"vae_name": vae_name,
"positive": positive,
"positive_l": None,
"positive_g": None,
"positive_balance": None,
"negative": negative,
"negative_l": None,
"negative_g": None,
"negative_balance": None,
"empty_latent_width": empty_latent_width,
"empty_latent_height": empty_latent_height,
"batch_size": 1,
"seed": 0,
"empty_samples": empty_latent, }
}
return (pipe, model, vae)
# lora
class loraStackLoader:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
max_lora_num = 10
inputs = {
"required": {
"toggle": ([True, False],),
"mode": (["simple", "advanced"],),
"num_loras": ("INT", {"default": 1, "min": 0, "max": max_lora_num}),
},
"optional": {
"optional_lora_stack": ("LORA_STACK",),
},
}
for i in range(1, max_lora_num+1):
inputs["optional"][f"lora_{i}_name"] = (
["None"] + folder_paths.get_filename_list("loras"), {"default": "None"})
inputs["optional"][f"lora_{i}_strength"] = (
"FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
inputs["optional"][f"lora_{i}_model_strength"] = (
"FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
inputs["optional"][f"lora_{i}_clip_strength"] = (
"FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
return inputs
RETURN_TYPES = ("LORA_STACK",)
RETURN_NAMES = ("lora_stack",)
FUNCTION = "stack"
CATEGORY = "EasyUse/Loaders"
def stack(self, toggle, mode, num_loras, lora_stack=None, **kwargs):
if (toggle in [False, None, "False"]) or not kwargs:
return (None,)
loras = []
# Import Stack values
if lora_stack is not None:
loras.extend([l for l in lora_stack if l[0] != "None"])
# Import Lora values
for i in range(1, num_loras + 1):
lora_name = kwargs.get(f"lora_{i}_name")
if not lora_name or lora_name == "None":
continue
if mode == "simple":
lora_strength = float(kwargs.get(f"lora_{i}_strength"))
loras.append((lora_name, lora_strength, lora_strength))
elif mode == "advanced":
model_strength = float(kwargs.get(f"lora_{i}_model_strength"))
clip_strength = float(kwargs.get(f"lora_{i}_clip_strength"))
loras.append((lora_name, model_strength, clip_strength))
return (loras,)
class controlnetNameStack:
def get_file_list(filenames):
return [file for file in filenames if file != "put_models_here.txt" and "lllite" not in file]
controlnets = ["None"] + get_file_list(folder_paths.get_filename_list("controlnet"))
@classmethod
def INPUT_TYPES(s):
return {"required": {},
"optional": {
"switch_1": (["Off", "On"],),
"controlnet_1": (s.controlnets,),
"controlnet_strength_1": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"start_percent_1": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"end_percent_1": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"switch_2": (["Off", "On"],),
"controlnet_2": (s.controlnets,),
"controlnet_strength_2": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"start_percent_2": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"end_percent_2": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"switch_3": (["Off", "On"],),
"controlnet_3": (s.controlnets,),
"controlnet_strength_3": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"start_percent_3": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"end_percent_3": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"image_1": ("IMAGE",),
"image_2": ("IMAGE",),
"image_3": ("IMAGE",),
"controlnet_stack": ("CONTROL_NET_STACK",)
},
}
# controlnet
class controlnetSimple:
@classmethod
def INPUT_TYPES(s):
def get_file_list(filenames):
return [file for file in filenames if file != "put_models_here.txt" and "lllite" not in file]
return {
"required": {
"pipe": ("PIPE_LINE",),
"image": ("IMAGE",),
"control_net_name": (get_file_list(folder_paths.get_filename_list("controlnet")),),
},
"optional": {
"control_net": ("CONTROL_NET",),
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
"scale_soft_weights": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},),
}
}
RETURN_TYPES = ("PIPE_LINE", "CONDITIONING", "CONDITIONING")
RETURN_NAMES = ("pipe", "positive", "negative")
OUTPUT_NODE = True
FUNCTION = "controlnetApply"
CATEGORY = "EasyUse/Loaders"
def controlnetApply(self, pipe, image, control_net_name, control_net=None, strength=1, scale_soft_weights=1):
positive, negative = easyControlnet().apply(control_net_name, image, pipe["positive"], pipe["negative"], strength, 0, 1, control_net, scale_soft_weights)
new_pipe = {
"model": pipe['model'],
"positive": positive,
"negative": negative,
"vae": pipe['vae'],
"clip": pipe['clip'],
"samples": pipe["samples"],
"images": pipe["images"],
"seed": 0,
"loader_settings": pipe["loader_settings"]
}
del pipe
return (new_pipe, positive, negative)
# controlnetADV
class controlnetAdvanced:
@classmethod
def INPUT_TYPES(s):
def get_file_list(filenames):
return [file for file in filenames if file != "put_models_here.txt" and "lllite" not in file]
return {
"required": {
"pipe": ("PIPE_LINE",),
"image": ("IMAGE",),
"control_net_name": (get_file_list(folder_paths.get_filename_list("controlnet")),),
},
"optional": {
"control_net": ("CONTROL_NET",),
"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}),
"scale_soft_weights": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},),
}
}
RETURN_TYPES = ("PIPE_LINE", "CONDITIONING", "CONDITIONING")
RETURN_NAMES = ("pipe", "positive", "negative")
OUTPUT_NODE = True
FUNCTION = "controlnetApply"
CATEGORY = "EasyUse/Loaders"
def controlnetApply(self, pipe, image, control_net_name, control_net=None, strength=1, start_percent=0, end_percent=1, scale_soft_weights=1):
positive, negative = easyControlnet().apply(control_net_name, image, pipe["positive"], pipe["negative"],
strength, start_percent, end_percent, control_net, scale_soft_weights)
new_pipe = {
"model": pipe['model'],
"positive": positive,
"negative": negative,
"vae": pipe['vae'],
"clip": pipe['clip'],
"samples": pipe["samples"],
"images": pipe["images"],
"seed": 0,
"loader_settings": pipe["loader_settings"]
}
del pipe
return (new_pipe, positive, negative)
# LLLiteLoader
from .lllite import load_control_net_lllite_patch
class LLLiteLoader:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
def get_file_list(filenames):
return [file for file in filenames if file != "put_models_here.txt" and "lllite" in file]
return {
"required": {
"model": ("MODEL",),
"model_name": (get_file_list(folder_paths.get_filename_list("controlnet")),),
"cond_image": ("IMAGE",),
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
"steps": ("INT", {"default": 0, "min": 0, "max": 200, "step": 1}),
"start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.1}),
"end_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.1}),
}
}
RETURN_TYPES = ("MODEL",)
FUNCTION = "load_lllite"
CATEGORY = "EasyUse/Loaders"
def load_lllite(self, model, model_name, cond_image, strength, steps, start_percent, end_percent):
# cond_image is b,h,w,3, 0-1
model_path = os.path.join(folder_paths.get_full_path("controlnet", model_name))
model_lllite = model.clone()
patch = load_control_net_lllite_patch(model_path, cond_image, strength, steps, start_percent, end_percent)
if patch is not None:
model_lllite.set_model_attn1_patch(patch)
model_lllite.set_model_attn2_patch(patch)
return (model_lllite,)
#---------------------------------------------------------------Inpaint 开始----------------------------------------------------------------------#
# FooocusInpaint
from .fooocus import InpaintHead, InpaintWorker
inpaint_head_model = None
class fooocusInpaintLoader:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"head": (list(FOOOCUS_INPAINT_HEAD.keys()),),
"patch": (list(FOOOCUS_INPAINT_PATCH.keys()),),
}
}
RETURN_TYPES = ("INPAINT_PATCH",)
RETURN_NAMES = ("patch",)
CATEGORY = "EasyUse/Inpaint"
FUNCTION = "apply"
def apply(self, head, patch):
global inpaint_head_model
head_file = get_local_filepath(FOOOCUS_INPAINT_HEAD[head]["model_url"], INPAINT_DIR)
if inpaint_head_model is None:
inpaint_head_model = InpaintHead()
sd = torch.load(head_file, map_location='cpu')
inpaint_head_model.load_state_dict(sd)
patch_file = get_local_filepath(FOOOCUS_INPAINT_PATCH[patch]["model_url"], INPAINT_DIR)
inpaint_lora = comfy.utils.load_torch_file(patch_file, safe_load=True)
return ((inpaint_head_model, inpaint_lora),)
#---------------------------------------------------------------适配器 开始----------------------------------------------------------------------#
def insightface_loader(provider):
try:
from insightface.app import FaceAnalysis
except ImportError as e:
raise Exception(e)
path = os.path.join(folder_paths.models_dir, "insightface")
model = FaceAnalysis(name="buffalo_l", root=path, providers=[provider + 'ExecutionProvider', ])
model.prepare(ctx_id=0, det_size=(640, 640))
return model
# Apply Ipadapter
class ipadapter:
def __init__(self):
self.normal_presets = [
'LIGHT - SD1.5 only (low strength)',
'STANDARD (medium strength)',
'VIT-G (medium strength)',
'PLUS (high strength)',
'PLUS FACE (portraits)',
'FULL FACE - SD1.5 only (portraits stronger)',
'COMPOSITION'
]
self.faceid_presets = [
'FACEID',
'FACEID PLUS - SD1.5 only',
'FACEID PLUS V2',
'FACEID PORTRAIT (style transfer)'
]
self.weight_types = ["linear", "ease in", "ease out", 'ease in-out', 'reverse in-out', 'weak input', 'weak output', 'weak middle', 'strong middle', 'style transfer', 'composition']
self.presets = self.normal_presets + self.faceid_presets
def error(self):
raise Exception(f"[ERROR] To use ipadapterApply, you need to install 'ComfyUI_IPAdapter_plus'")
def get_clipvision_file(self, preset, node_name):
preset = preset.lower()
clipvision_list = folder_paths.get_filename_list("clip_vision")
if preset.startswith("vit-g"):
pattern = '(ViT.bigG.14.*39B.b160k|ipadapter.*sdxl|sdxl.*model\.(bin|safetensors))'
else:
pattern = '(ViT.H.14.*s32B.b79K|ipadapter.*sd15|sd1.?5.*model\.(bin|safetensors))'
clipvision_files = [e for e in clipvision_list if re.search(pattern, e, re.IGNORECASE)]
clipvision_name = clipvision_files[0] if len(clipvision_files)>0 else None
clipvision_file = folder_paths.get_full_path("clip_vision", clipvision_name) if clipvision_name else None
# if clipvision_name is not None:
# log_node_info(node_name, f"Using {clipvision_name}")
return clipvision_file, clipvision_name
def get_ipadapter_file(self, preset, is_sdxl, node_name):
preset = preset.lower()
ipadapter_list = folder_paths.get_filename_list("ipadapter")
is_insightface = False
lora_pattern = None
if preset.startswith("light"):
if is_sdxl:
raise Exception("light model is not supported for SDXL")
pattern = 'sd15.light.v11\.(safetensors|bin)$'
# if light model v11 is not found, try with the old version
if not [e for e in ipadapter_list if re.search(pattern, e, re.IGNORECASE)]:
pattern = 'sd15.light\.(safetensors|bin)$'
elif preset.startswith("standard"):
if is_sdxl:
pattern = 'ip.adapter.sdxl.vit.h\.(safetensors|bin)$'
else:
pattern = 'ip.adapter.sd15\.(safetensors|bin)$'
elif preset.startswith("vit-g"):
if is_sdxl:
pattern = 'ip.adapter.sdxl\.(safetensors|bin)$'
else:
pattern = 'sd15.vit.g\.(safetensors|bin)$'
elif preset.startswith("plus ("):
if is_sdxl:
pattern = 'plus.sdxl.vit.h\.(safetensors|bin)$'
else:
pattern = 'ip.adapter.plus.sd15\.(safetensors|bin)$'
elif preset.startswith("plus face"):
if is_sdxl:
pattern = 'plus.face.sdxl.vit.h\.(safetensors|bin)$'
else:
pattern = 'plus.face.sd15\.(safetensors|bin)$'
elif preset.startswith("full"):
if is_sdxl:
raise Exception("full face model is not supported for SDXL")
pattern = 'full.face.sd15\.(safetensors|bin)$'
elif preset.startswith("composition"):
if is_sdxl:
pattern = 'plus.composition.sdxl\.(safetensors|bin)$'
else:
pattern = 'plus.composition.sd15\.(safetensors|bin)$'
elif preset.startswith("faceid portrait"):
if is_sdxl:
pattern = 'portrait.sdxl\.(safetensors|bin)$'
else:
pattern = 'portrait.v11.sd15\.(safetensors|bin)$'
# if v11 is not found, try with the old version
if not [e for e in ipadapter_list if re.search(pattern, e, re.IGNORECASE)]:
pattern = 'portrait.sd15\.(safetensors|bin)$'
is_insightface = True
elif preset == "faceid":
if is_sdxl:
pattern = 'faceid.sdxl\.(safetensors|bin)$'
lora_pattern = 'faceid.sdxl.lora\.safetensors$'
else:
pattern = 'faceid.sd15\.(safetensors|bin)$'
lora_pattern = 'faceid.sd15.lora\.safetensors$'
is_insightface = True
elif preset.startswith("faceid plus -"):
if is_sdxl:
raise Exception("faceid plus model is not supported for SDXL")
pattern = 'faceid.plus.sd15\.(safetensors|bin)$'
lora_pattern = 'faceid.plus.sd15.lora\.safetensors$'
is_insightface = True
elif preset.startswith("faceid plus v2"):
if is_sdxl:
pattern = 'faceid.plusv2.sdxl\.(safetensors|bin)$'
lora_pattern = 'faceid.plusv2.sdxl.lora\.safetensors$'
else:
pattern = 'faceid.plusv2.sd15\.(safetensors|bin)$'
lora_pattern = 'faceid.plusv2.sd15.lora\.safetensors$'
is_insightface = True
else:
raise Exception(f"invalid type '{preset}'")
ipadapter_files = [e for e in ipadapter_list if re.search(pattern, e, re.IGNORECASE)]
ipadapter_name = ipadapter_files[0] if len(ipadapter_files)>0 else None
ipadapter_file = folder_paths.get_full_path("ipadapter", ipadapter_name) if ipadapter_name else None
# if ipadapter_name is not None:
# log_node_info(node_name, f"Using {ipadapter_name}")
return ipadapter_file, ipadapter_name, is_insightface, lora_pattern
def get_lora_file(self, preset, pattern, model_type, model, model_strength, clip_strength, clip=None):
lora_list = folder_paths.get_filename_list("loras")
lora_files = [e for e in lora_list if re.search(pattern, e, re.IGNORECASE)]
lora_name = lora_files[0] if lora_files else None
if lora_name:
return easyCache.load_lora({"model": model, "clip": clip, "lora_name": lora_name, "model_strength":model_strength, "clip_strength":clip_strength},)
else:
if "lora_url" in IPADAPTER_MODELS[preset][model_type]:
lora_name = get_local_filepath(IPADAPTER_MODELS[preset][model_type]["lora_url"], os.path.join(folder_paths.models_dir, "loras"))
return easyCache.load_lora({"model": model, "clip": clip, "lora_name": lora_name, "model_strength":model_strength, "clip_strength":clip_strength},)
return (model, clip)
def ipadapter_model_loader(self, file):
model = comfy.utils.load_torch_file(file, safe_load=True)
if file.lower().endswith(".safetensors"):
st_model = {"image_proj": {}, "ip_adapter": {}}
for key in model.keys():
if key.startswith("image_proj."):
st_model["image_proj"][key.replace("image_proj.", "")] = model[key]
elif key.startswith("ip_adapter."):
st_model["ip_adapter"][key.replace("ip_adapter.", "")] = model[key]
model = st_model
del st_model
if not "ip_adapter" in model.keys() or not model["ip_adapter"]:
raise Exception("invalid IPAdapter model {}".format(file))
if 'plusv2' in file.lower():
model["faceidplusv2"] = True
return model
def load_model(self, model, preset, lora_model_strength, provider="CPU", clip_vision=None, optional_ipadapter=None, cache_mode='none', node_name='easy ipadapterApply'):
pipeline = {"clipvision": {'file': None, 'model': None}, "ipadapter": {'file': None, 'model': None},
"insightface": {'provider': None, 'model': None}}
if optional_ipadapter is not None:
pipeline = optional_ipadapter
# 1. Load the clipvision model
if not clip_vision:
clipvision_file, clipvision_name = self.get_clipvision_file(preset, node_name)
if clipvision_file is None:
raise Exception("ClipVision model not found.")
if clipvision_file == pipeline['clipvision']['file']:
clip_vision = pipeline['clipvision']['model']
elif cache_mode in ["all", "clip_vision only"] and clipvision_name in backend_cache.cache:
log_node_info("easy ipadapterApply", f"Using ClipVisonModel {clipvision_name} Cached")
_, clip_vision = backend_cache.cache[clipvision_name][1]
else:
clip_vision = load_clip_vision(clipvision_file)
log_node_info("easy ipadapterApply", f"Using ClipVisonModel {clipvision_name}")
if cache_mode in ["all", "clip_vision only"]:
backend_cache.update_cache(clipvision_name, 'clip_vision', (False, clip_vision))
pipeline['clipvision']['file'] = clipvision_file
pipeline['clipvision']['model'] = clip_vision
# 2. Load the ipadapter model
is_sdxl = isinstance(model.model, comfy.model_base.SDXL)
ipadapter_file, ipadapter_name, is_insightface, lora_pattern = self.get_ipadapter_file(preset, is_sdxl, node_name)
model_type = 'sdxl' if is_sdxl else 'sd15'
if ipadapter_file is None:
model_url = IPADAPTER_MODELS[preset][model_type]["model_url"]
ipadapter_file = get_local_filepath(model_url, IPADAPTER_DIR)
ipadapter_name = os.path.basename(model_url)
if ipadapter_file == pipeline['ipadapter']['file']:
ipadapter = pipeline['ipadapter']['model']
elif cache_mode in ["all", "ipadapter only"] and ipadapter_name in backend_cache.cache:
log_node_info("easy ipadapterApply", f"Using IpAdapterModel {ipadapter_name} Cached")
_, ipadapter = backend_cache.cache[ipadapter_name][1]
else:
ipadapter = self.ipadapter_model_loader(ipadapter_file)
pipeline['ipadapter']['file'] = ipadapter_file
log_node_info("easy ipadapterApply", f"Using IpAdapterModel {ipadapter_name}")
if cache_mode in ["all", "ipadapter only"]:
backend_cache.update_cache(ipadapter_name, 'ipadapter', (False, ipadapter))
pipeline['ipadapter']['model'] = ipadapter
# 3. Load the lora model if needed
if lora_pattern is not None:
if lora_model_strength > 0:
model, _ = self.get_lora_file(preset, lora_pattern, model_type, model, lora_model_strength, 1)
# 4. Load the insightface model if needed
if is_insightface:
icache_key = 'insightface-' + provider
if provider == pipeline['insightface']['provider']:
insightface = pipeline['insightface']['model']
elif cache_mode in ["all", "insightface only"] and icache_key in backend_cache.cache:
log_node_info("easy ipadapterApply", f"Using InsightFaceModel {icache_key} Cached")
_, insightface = backend_cache.cache[icache_key][1]
else:
insightface = insightface_loader(provider)
if cache_mode in ["all", "insightface only"]:
backend_cache.update_cache(icache_key, 'insightface',(False, insightface))
pipeline['insightface']['provider'] = provider
pipeline['insightface']['model'] = insightface
return (model, pipeline,)
class ipadapterApply(ipadapter):
def __init__(self):
super().__init__()
pass
@classmethod
def INPUT_TYPES(cls):
presets = cls().presets
return {
"required": {
"model": ("MODEL",),
"image": ("IMAGE",),
"preset": (presets,),
"lora_strength": ("FLOAT", {"default": 0.6, "min": 0, "max": 1, "step": 0.01}),
"provider": (["CPU", "CUDA", "ROCM", "DirectML", "OpenVINO", "CoreML"],),
"weight": ("FLOAT", {"default": 1.0, "min": -1, "max": 3, "step": 0.05}),
"weight_faceidv2": ("FLOAT", { "default": 1.0, "min": -1, "max": 5.0, "step": 0.05 }),
"start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"cache_mode": (["insightface only", "clip_vision only", "ipadapter only", "all", "none"], {"default": "insightface only"},),
"use_tiled": ("BOOLEAN", {"default": False},),
},
"optional": {
"attn_mask": ("MASK",),
"optional_ipadapter": ("IPADAPTER",),
}
}
RETURN_TYPES = ("MODEL", "IMAGE", "MASK", "IPADAPTER",)
RETURN_NAMES = ("model", "tiles", "masks", "ipadapter", )
CATEGORY = "EasyUse/Adapter"
FUNCTION = "apply"
def apply(self, model, image, preset, lora_strength, provider, weight, weight_faceidv2, start_at, end_at, cache_mode, use_tiled, attn_mask=None, optional_ipadapter=None):
tiles, masks = image, [None]
model, ipadapter = self.load_model(model, preset, lora_strength, provider, clip_vision=None, optional_ipadapter=optional_ipadapter, cache_mode=cache_mode)
if use_tiled and preset not in self.faceid_presets:
if "IPAdapterTiled" not in ALL_NODE_CLASS_MAPPINGS:
self.error()
cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterTiled"]
model, tiles, masks = cls().apply_tiled(model, ipadapter, image, weight, "linear", start_at, end_at, sharpening=0.0, combine_embeds="concat", image_negative=None, attn_mask=attn_mask, clip_vision=None, embeds_scaling='V only')
else:
if preset in ['FACEID PLUS V2', 'FACEID PORTRAIT (style transfer)']:
if "IPAdapterAdvanced" not in ALL_NODE_CLASS_MAPPINGS:
self.error()
cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterAdvanced"]
model, = cls().apply_ipadapter(model, ipadapter, start_at=start_at, end_at=end_at, weight=weight, weight_type="linear", combine_embeds="concat", weight_faceidv2=weight_faceidv2, image=image, image_negative=None, clip_vision=None, attn_mask=attn_mask, insightface=None, embeds_scaling='V only')
else:
if "IPAdapter" not in ALL_NODE_CLASS_MAPPINGS:
self.error()
cls = ALL_NODE_CLASS_MAPPINGS["IPAdapter"]
model, = cls().apply_ipadapter(model, ipadapter, image, weight, start_at, end_at, weight_type='standard',attn_mask=attn_mask)
return (model, tiles, masks, ipadapter)
class ipadapterApplyAdvanced(ipadapter):
def __init__(self):
super().__init__()
pass
@classmethod
def INPUT_TYPES(cls):
ipa_cls = cls()
presets = ipa_cls.presets
weight_types = ipa_cls.weight_types
return {
"required": {
"model": ("MODEL",),
"image": ("IMAGE",),
"preset": (presets,),
"lora_strength": ("FLOAT", {"default": 0.6, "min": 0, "max": 1, "step": 0.01}),
"provider": (["CPU", "CUDA", "ROCM", "DirectML", "OpenVINO", "CoreML"],),
"weight": ("FLOAT", {"default": 1.0, "min": -1, "max": 3, "step": 0.05}),
"weight_faceidv2": ("FLOAT", {"default": 1.0, "min": -1, "max": 5.0, "step": 0.05 }),
"weight_type": (weight_types,),
"combine_embeds": (["concat", "add", "subtract", "average", "norm average"],),
"start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"embeds_scaling": (['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],),
"cache_mode": (["insightface only", "clip_vision only","ipadapter only", "all", "none"], {"default": "insightface only"},),
"use_tiled": ("BOOLEAN", {"default": False},),
"use_batch": ("BOOLEAN", {"default": False},),
"sharpening": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.05}),
},
"optional": {
"image_negative": ("IMAGE",),
"attn_mask": ("MASK",),
"clip_vision": ("CLIP_VISION",),
"optional_ipadapter": ("IPADAPTER",),
}
}
RETURN_TYPES = ("MODEL", "IMAGE", "MASK", "IPADAPTER",)
RETURN_NAMES = ("model", "tiles", "masks", "ipadapter", )
CATEGORY = "EasyUse/Adapter"
FUNCTION = "apply"
def apply(self, model, image, preset, lora_strength, provider, weight, weight_faceidv2, weight_type, combine_embeds, start_at, end_at, embeds_scaling, cache_mode, use_tiled, use_batch, sharpening, weight_style=1.0, weight_composition=1.0, image_style=None, image_composition=None, expand_style=False, image_negative=None, clip_vision=None, attn_mask=None, optional_ipadapter=None):
tiles, masks = image, [None]
model, ipadapter = self.load_model(model, preset, lora_strength, provider, clip_vision=clip_vision, optional_ipadapter=optional_ipadapter, cache_mode=cache_mode)
if use_tiled:
if use_batch:
if "IPAdapterTiledBatch" not in ALL_NODE_CLASS_MAPPINGS:
self.error()
cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterTiledBatch"]
else:
if "IPAdapterTiled" not in ALL_NODE_CLASS_MAPPINGS:
self.error()
cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterTiled"]
model, tiles, masks = cls().apply_tiled(model, ipadapter, image=image, weight=weight, weight_type=weight_type, start_at=start_at, end_at=end_at, sharpening=sharpening, combine_embeds=combine_embeds, image_negative=image_negative, attn_mask=attn_mask, clip_vision=clip_vision, embeds_scaling=embeds_scaling)
else:
if use_batch:
if "IPAdapterBatch" not in ALL_NODE_CLASS_MAPPINGS:
self.error()
cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterBatch"]
else:
if "IPAdapterAdvanced" not in ALL_NODE_CLASS_MAPPINGS:
self.error()
cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterAdvanced"]
model, = cls().apply_ipadapter(model, ipadapter, weight=weight, weight_type=weight_type, start_at=start_at, end_at=end_at, combine_embeds=combine_embeds, weight_faceidv2=weight_faceidv2, image=image, image_negative=image_negative, weight_style=1.0, weight_composition=1.0, image_style=image_style, image_composition=image_composition, expand_style=expand_style, clip_vision=clip_vision, attn_mask=attn_mask, insightface=None, embeds_scaling=embeds_scaling)
return (model, tiles, masks, ipadapter)
class ipadapterStyleComposition(ipadapter):
def __init__(self):
super().__init__()
pass
@classmethod
def INPUT_TYPES(cls):
ipa_cls = cls()
normal_presets = ipa_cls.normal_presets
weight_types = ipa_cls.weight_types
return {
"required": {
"model": ("MODEL",),
"image_style": ("IMAGE",),
"preset": (normal_presets,),
"weight_style": ("FLOAT", {"default": 1.0, "min": -1, "max": 5, "step": 0.05}),
"weight_composition": ("FLOAT", {"default": 1.0, "min": -1, "max": 5, "step": 0.05}),
"expand_style": ("BOOLEAN", {"default": False}),
"combine_embeds": (["concat", "add", "subtract", "average", "norm average"], {"default": "average"}),
"start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"embeds_scaling": (['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],),
"cache_mode": (["insightface only", "clip_vision only", "ipadapter only", "all", "none"],
{"default": "insightface only"},),
},
"optional": {
"image_composition": ("IMAGE",),
"image_negative": ("IMAGE",),
"attn_mask": ("MASK",),
"clip_vision": ("CLIP_VISION",),
"optional_ipadapter": ("IPADAPTER",),
}
}
CATEGORY = "EasyUse/Adapter"
RETURN_TYPES = ("MODEL", "IPADAPTER",)
RETURN_NAMES = ("model", "ipadapter",)
CATEGORY = "EasyUse/Adapter"
FUNCTION = "apply"
def apply(self, model, preset, weight_style, weight_composition, expand_style, combine_embeds, start_at, end_at, embeds_scaling, cache_mode, image_style=None , image_composition=None, image_negative=None, clip_vision=None, attn_mask=None, optional_ipadapter=None):
model, ipadapter = self.load_model(model, preset, 0, 'CPU', clip_vision=None, optional_ipadapter=optional_ipadapter, cache_mode=cache_mode)
if "IPAdapterAdvanced" not in ALL_NODE_CLASS_MAPPINGS:
self.error()
cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterAdvanced"]
model, = cls().apply_ipadapter(model, ipadapter, start_at=start_at, end_at=end_at, weight_style=weight_style, weight_composition=weight_composition, weight_type='linear', combine_embeds=combine_embeds, weight_faceidv2=weight_composition, image_style=image_style, image_composition=image_composition, image_negative=image_negative, expand_style=expand_style, clip_vision=clip_vision, attn_mask=attn_mask, insightface=None, embeds_scaling=embeds_scaling)
return (model, ipadapter)
class ipadapterApplyEncoder(ipadapter):
def __init__(self):
super().__init__()
pass
@classmethod
def INPUT_TYPES(cls):
ipa_cls = cls()
normal_presets = ipa_cls.normal_presets
max_embeds_num = 3
inputs = {
"required": {
"model": ("MODEL",),
"image1": ("IMAGE",),
"preset": (normal_presets,),
"num_embeds": ("INT", {"default": 2, "min": 1, "max": max_embeds_num}),
},
"optional": {}
}
for i in range(1, max_embeds_num + 1):
if i > 1:
inputs["optional"][f"image{i}"] = ("IMAGE",)
for i in range(1, max_embeds_num + 1):
inputs["optional"][f"mask{i}"] = ("MASK",)
inputs["optional"][f"weight{i}"] = ("FLOAT", {"default": 1.0, "min": -1, "max": 3, "step": 0.05})
inputs["optional"]["combine_method"] = (["concat", "add", "subtract", "average", "norm average", "max", "min"],)
inputs["optional"]["optional_ipadapter"] = ("IPADAPTER",)
inputs["optional"]["pos_embeds"] = ("EMBEDS",)
inputs["optional"]["neg_embeds"] = ("EMBEDS",)
return inputs
RETURN_TYPES = ("MODEL", "IPADAPTER", "EMBEDS", "EMBEDS", )
RETURN_NAMES = ("model", "ipadapter", "pos_embed", "neg_embed", )
CATEGORY = "EasyUse/Adapter"
FUNCTION = "apply"
def batch(self, embeds, method):
if method == 'concat' and len(embeds) == 1:
return (embeds[0],)
embeds = [embed for embed in embeds if embed is not None]
embeds = torch.cat(embeds, dim=0)
if method == "add":
embeds = torch.sum(embeds, dim=0).unsqueeze(0)
elif method == "subtract":
embeds = embeds[0] - torch.mean(embeds[1:], dim=0)
embeds = embeds.unsqueeze(0)
elif method == "average":
embeds = torch.mean(embeds, dim=0).unsqueeze(0)
elif method == "norm average":
embeds = torch.mean(embeds / torch.norm(embeds, dim=0, keepdim=True), dim=0).unsqueeze(0)
elif method == "max":
embeds = torch.max(embeds, dim=0).values.unsqueeze(0)
elif method == "min":
embeds = torch.min(embeds, dim=0).values.unsqueeze(0)
return embeds
def apply(self, **kwargs):
model = kwargs['model']
preset = kwargs['preset']
if 'optional_ipadapter' in kwargs:
ipadapter = kwargs['optional_ipadapter']
else:
model, ipadapter = self.load_model(model, preset, 0, 'CPU', clip_vision=None, optional_ipadapter=None, cache_mode='none')
if "IPAdapterEncoder" not in ALL_NODE_CLASS_MAPPINGS:
self.error()
encoder_cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterEncoder"]
pos_embeds = kwargs["pos_embeds"] if "pos_embeds" in kwargs else []
neg_embeds = kwargs["neg_embeds"] if "neg_embeds" in kwargs else []
for i in range(1, kwargs['num_embeds'] + 1):
if f"image{i}" not in kwargs:
raise Exception(f"image{i} is required")
kwargs[f"mask{i}"] = kwargs[f"mask{i}"] if f"mask{i}" in kwargs else None
kwargs[f"weight{i}"] = kwargs[f"weight{i}"] if f"weight{i}" in kwargs else 1.0
pos, neg = encoder_cls().encode(ipadapter, kwargs[f"image{i}"], kwargs[f"weight{i}"], kwargs[f"mask{i}"], clip_vision=None)
pos_embeds.append(pos)
neg_embeds.append(neg)
pos_embeds = self.batch(pos_embeds, kwargs['combine_method'])
neg_embeds = self.batch(neg_embeds, kwargs['combine_method'])
return (model, ipadapter, pos_embeds, neg_embeds)
class ipadapterApplyEmbeds(ipadapter):
def __init__(self):
super().__init__()
pass
@classmethod
def INPUT_TYPES(cls):
ipa_cls = cls()
weight_types = ipa_cls.weight_types
return {
"required": {
"model": ("MODEL",),
"ipadapter": ("IPADAPTER",),
"pos_embed": ("EMBEDS",),
"weight": ("FLOAT", {"default": 1.0, "min": -1, "max": 3, "step": 0.05}),
"weight_type": (weight_types,),
"start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"embeds_scaling": (['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],),
},
"optional": {
"neg_embed": ("EMBEDS",),
"attn_mask": ("MASK",),
}
}
RETURN_TYPES = ("MODEL", "IPADAPTER",)
RETURN_NAMES = ("model", "ipadapter", )
CATEGORY = "EasyUse/Adapter"
FUNCTION = "apply"
def apply(self, model, ipadapter, pos_embed, weight, weight_type, start_at, end_at, embeds_scaling, attn_mask=None, neg_embed=None,):
if "IPAdapterEmbeds" not in ALL_NODE_CLASS_MAPPINGS:
self.error()
cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterEmbeds"]
model, = cls().apply_ipadapter(model, ipadapter, pos_embed, weight, weight_type, start_at, end_at, neg_embed=neg_embed, attn_mask=attn_mask, clip_vision=None, embeds_scaling=embeds_scaling)
return (model, ipadapter)
#Apply InstantID
class instantID:
def error(self):
raise Exception(f"[ERROR] To use instantIDApply, you need to install 'ComfyUI_InstantID'")
def run(self, pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps=None, mask=None, control_net=None, positive=None, negative=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
instantid_model, insightface_model, face_embeds = None, None, None
model = pipe['model']
# Load InstantID
cache_key = 'instantID'
if cache_key in backend_cache.cache:
log_node_info("easy instantIDApply","Using InstantIDModel Cached")
_, instantid_model = backend_cache.cache[cache_key][1]
if "InstantIDModelLoader" in ALL_NODE_CLASS_MAPPINGS:
load_instant_cls = ALL_NODE_CLASS_MAPPINGS["InstantIDModelLoader"]
instantid_model, = load_instant_cls().load_model(instantid_file)
backend_cache.update_cache(cache_key, 'instantid', (False, instantid_model))
else:
self.error()
icache_key = 'insightface-' + insightface
if icache_key in backend_cache.cache:
log_node_info("easy instantIDApply", f"Using InsightFaceModel {insightface} Cached")
_, insightface_model = backend_cache.cache[icache_key][1]
elif "InstantIDFaceAnalysis" in ALL_NODE_CLASS_MAPPINGS:
load_insightface_cls = ALL_NODE_CLASS_MAPPINGS["InstantIDFaceAnalysis"]
insightface_model, = load_insightface_cls().load_insight_face(insightface)
backend_cache.update_cache(icache_key, 'insightface', (False, insightface_model))
else:
self.error()
# Apply InstantID
if "ApplyInstantID" in ALL_NODE_CLASS_MAPPINGS:
instantid_apply = ALL_NODE_CLASS_MAPPINGS['ApplyInstantID']
control_net = easyControlnet().load_controlnet(control_net_name, control_net, cn_soft_weights)
model, positive, negative = instantid_apply().apply_instantid(instantid_model, insightface_model, control_net, image, model, positive, negative, start_at, end_at, weight=weight, ip_weight=None, cn_strength=cn_strength, noise=noise, image_kps=image_kps, mask=mask)
else:
self.error()
new_pipe = {
"model": model,
"positive": positive,
"negative": negative,
"vae": pipe['vae'],
"clip": pipe['clip'],
"samples": pipe["samples"],
"images": pipe["images"],
"seed": 0,
"loader_settings": pipe["loader_settings"]
}
del pipe
return (new_pipe, model, positive, negative)
class instantIDApply(instantID):
def __init__(self):
super().__init__()
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required":{
"pipe": ("PIPE_LINE",),
"image": ("IMAGE",),
"instantid_file": (folder_paths.get_filename_list("instantid"),),
"insightface": (["CPU", "CUDA", "ROCM"],),
"control_net_name": (folder_paths.get_filename_list("controlnet"),),
"cn_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
"cn_soft_weights": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},),
"weight": ("FLOAT", {"default": .8, "min": 0.0, "max": 5.0, "step": 0.01, }),
"start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001, }),
"end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001, }),
"noise": ("FLOAT", {"default": 0.35, "min": 0.0, "max": 1.0, "step": 0.05, }),
},
"optional": {
"image_kps": ("IMAGE",),
"mask": ("MASK",),
"control_net": ("CONTROL_NET",),
},
"hidden": {
"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"
},
}
RETURN_TYPES = ("PIPE_LINE", "MODEL", "CONDITIONING", "CONDITIONING")
RETURN_NAMES = ("pipe", "model", "positive", "negative")
OUTPUT_NODE = True
FUNCTION = "apply"
CATEGORY = "EasyUse/Adapter"
def apply(self, pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps=None, mask=None, control_net=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
positive = pipe['positive']
negative = pipe['negative']
return self.run(pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps, mask, control_net, positive, negative, prompt, extra_pnginfo, my_unique_id)
#Apply InstantID Advanced
class instantIDApplyAdvanced(instantID):
def __init__(self):
super().__init__()
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required":{
"pipe": ("PIPE_LINE",),
"image": ("IMAGE",),
"instantid_file": (folder_paths.get_filename_list("instantid"),),
"insightface": (["CPU", "CUDA", "ROCM"],),
"control_net_name": (folder_paths.get_filename_list("controlnet"),),
"cn_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
"cn_soft_weights": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},),
"weight": ("FLOAT", {"default": .8, "min": 0.0, "max": 5.0, "step": 0.01, }),
"start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001, }),
"end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001, }),
"noise": ("FLOAT", {"default": 0.35, "min": 0.0, "max": 1.0, "step": 0.05, }),
},
"optional": {
"image_kps": ("IMAGE",),
"mask": ("MASK",),
"control_net": ("CONTROL_NET",),
"positive": ("CONDITIONING",),
"negative": ("CONDITIONING",),
},
"hidden": {
"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"
},
}
RETURN_TYPES = ("PIPE_LINE", "MODEL", "CONDITIONING", "CONDITIONING")
RETURN_NAMES = ("pipe", "model", "positive", "negative")
OUTPUT_NODE = True
FUNCTION = "apply_advanced"
CATEGORY = "EasyUse/Adapter"
def apply_advanced(self, pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps=None, mask=None, control_net=None, positive=None, negative=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
positive = positive if positive is not None else pipe['positive']
negative = negative if negative is not None else pipe['negative']
return self.run(pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps, mask, control_net, positive, negative, prompt, extra_pnginfo, my_unique_id)
#---------------------------------------------------------------预采样 开始----------------------------------------------------------------------#
# 预采样设置(基础)
class samplerSettings:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {"required":
{"pipe": ("PIPE_LINE",),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"optional": {
"image_to_latent": ("IMAGE",),
"latent": ("LATENT",),
},
"hidden":
{"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
RETURN_TYPES = ("PIPE_LINE", )
RETURN_NAMES = ("pipe",)
OUTPUT_NODE = True
FUNCTION = "settings"
CATEGORY = "EasyUse/PreSampling"
def settings(self, pipe, steps, cfg, sampler_name, scheduler, denoise, seed, image_to_latent=None, latent=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
# 图生图转换
vae = pipe["vae"]
batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1
if image_to_latent is not None:
samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])}
samples = RepeatLatentBatch().repeat(samples, batch_size)[0]
images = image_to_latent
elif latent is not None:
samples = latent
images = pipe["images"]
else:
samples = pipe["samples"]
images = pipe["images"]
new_pipe = {
"model": pipe['model'],
"positive": pipe['positive'],
"negative": pipe['negative'],
"vae": pipe['vae'],
"clip": pipe['clip'],
"samples": samples,
"images": images,
"seed": seed,
"loader_settings": {
**pipe["loader_settings"],
"steps": steps,
"cfg": cfg,
"sampler_name": sampler_name,
"scheduler": scheduler,
"denoise": denoise,
"add_noise": "enabled"
}
}
del pipe
return {"ui": {"value": [seed]}, "result": (new_pipe,)}
# 预采样设置(高级)
class samplerSettingsAdvanced:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {"required":
{"pipe": ("PIPE_LINE",),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
"start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}),
"end_at_step": ("INT", {"default": 10000, "min": 0, "max": 10000}),
"add_noise": (["enable", "disable"],),
"seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
"return_with_leftover_noise": (["disable", "enable"], ),
},
"optional": {
"image_to_latent": ("IMAGE",),
"latent": ("LATENT",)
},
"hidden":
{"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
RETURN_TYPES = ("PIPE_LINE", )
RETURN_NAMES = ("pipe",)
OUTPUT_NODE = True
FUNCTION = "settings"
CATEGORY = "EasyUse/PreSampling"
def settings(self, pipe, steps, cfg, sampler_name, scheduler, start_at_step, end_at_step, add_noise, seed, return_with_leftover_noise, image_to_latent=None, latent=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
# 图生图转换
vae = pipe["vae"]
batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1
if image_to_latent is not None:
samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])}
samples = RepeatLatentBatch().repeat(samples, batch_size)[0]
images = image_to_latent
elif latent is not None:
samples = latent
images = pipe["images"]
else:
samples = pipe["samples"]
images = pipe["images"]
force_full_denoise = True
if return_with_leftover_noise == "enable":
force_full_denoise = False
new_pipe = {
"model": pipe['model'],
"positive": pipe['positive'],
"negative": pipe['negative'],
"vae": pipe['vae'],
"clip": pipe['clip'],
"samples": samples,
"images": images,
"seed": seed,
"loader_settings": {
**pipe["loader_settings"],
"steps": steps,
"cfg": cfg,
"sampler_name": sampler_name,
"scheduler": scheduler,
"start_step": start_at_step,
"last_step": end_at_step,
"denoise": 1.0,
"add_noise": add_noise,
"force_full_denoise": force_full_denoise
}
}
del pipe
return {"ui": {"value": [seed]}, "result": (new_pipe,)}
# 预采样设置(噪声注入)
class samplerSettingsNoiseIn:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {"required":
{"pipe": ("PIPE_LINE",),
"factor": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 1.0, "step":0.01, "round": 0.01}),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"optional": {
"optional_noise_seed": ("INT",{"forceInput": True}),
"optional_latent": ("LATENT",),
},
"hidden":
{"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
RETURN_TYPES = ("PIPE_LINE", )
RETURN_NAMES = ("pipe",)
OUTPUT_NODE = True
FUNCTION = "settings"
CATEGORY = "EasyUse/PreSampling"
def slerp(self, val, low, high):
dims = low.shape
low = low.reshape(dims[0], -1)
high = high.reshape(dims[0], -1)
low_norm = low / torch.norm(low, dim=1, keepdim=True)
high_norm = high / torch.norm(high, dim=1, keepdim=True)
low_norm[low_norm != low_norm] = 0.0
high_norm[high_norm != high_norm] = 0.0
omega = torch.acos((low_norm * high_norm).sum(1))
so = torch.sin(omega)
res = (torch.sin((1.0 - val) * omega) / so).unsqueeze(1) * low + (torch.sin(val * omega) / so).unsqueeze(
1) * high
return res.reshape(dims)
def prepare_mask(self, mask, shape):
mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])),
size=(shape[2], shape[3]), mode="bilinear")
mask = mask.expand((-1, shape[1], -1, -1))
if mask.shape[0] < shape[0]:
mask = mask.repeat((shape[0] - 1) // mask.shape[0] + 1, 1, 1, 1)[:shape[0]]
return mask
def expand_mask(self, mask, expand, tapered_corners):
try:
import numpy as np
import scipy
c = 0 if tapered_corners else 1
kernel = np.array([[c, 1, c],
[1, 1, 1],
[c, 1, c]])
mask = mask.reshape((-1, mask.shape[-2], mask.shape[-1]))
out = []
for m in mask:
output = m.numpy()
for _ in range(abs(expand)):
if expand < 0:
output = scipy.ndimage.grey_erosion(output, footprint=kernel)
else:
output = scipy.ndimage.grey_dilation(output, footprint=kernel)
output = torch.from_numpy(output)
out.append(output)
return torch.stack(out, dim=0)
except:
return None
def settings(self, pipe, factor, steps, cfg, sampler_name, scheduler, denoise, seed, optional_noise_seed=None, optional_latent=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
latent = optional_latent if optional_latent is not None else pipe["samples"]
model = pipe["model"]
# generate base noise
batch_size, _, height, width = latent["samples"].shape
generator = torch.manual_seed(seed)
base_noise = torch.randn((1, 4, height, width), dtype=torch.float32, device="cpu", generator=generator).repeat(batch_size, 1, 1, 1).cpu()
# generate variation noise
if optional_noise_seed is None or optional_noise_seed == seed:
optional_noise_seed = seed+1
generator = torch.manual_seed(optional_noise_seed)
variation_noise = torch.randn((batch_size, 4, height, width), dtype=torch.float32, device="cpu",
generator=generator).cpu()
slerp_noise = self.slerp(factor, base_noise, variation_noise)
end_at_step = steps # min(steps, end_at_step)
start_at_step = round(end_at_step - end_at_step * denoise)
device = comfy.model_management.get_torch_device()
comfy.model_management.load_model_gpu(model)
model_patcher = comfy.model_patcher.ModelPatcher(model.model, load_device=device, offload_device=comfy.model_management.unet_offload_device())
sampler = comfy.samplers.KSampler(model_patcher, steps=steps, device=device, sampler=sampler_name,
scheduler=scheduler, denoise=1.0, model_options=model.model_options)
sigmas = sampler.sigmas
sigma = sigmas[start_at_step] - sigmas[end_at_step]
sigma /= model.model.latent_format.scale_factor
sigma = sigma.cpu().numpy()
work_latent = latent.copy()
work_latent["samples"] = latent["samples"].clone() + slerp_noise * sigma
if "noise_mask" in latent:
noise_mask = self.prepare_mask(latent["noise_mask"], latent['samples'].shape)
work_latent["samples"] = noise_mask * work_latent["samples"] + (1-noise_mask) * latent["samples"]
work_latent['noise_mask'] = self.expand_mask(latent["noise_mask"].clone(), 5, True)
if pipe is None:
pipe = {}
new_pipe = {
"model": pipe['model'],
"positive": pipe['positive'],
"negative": pipe['negative'],
"vae": pipe['vae'],
"clip": pipe['clip'],
"samples": work_latent,
"images": pipe['images'],
"seed": seed,
"loader_settings": {
**pipe["loader_settings"],
"steps": steps,
"cfg": cfg,
"sampler_name": sampler_name,
"scheduler": scheduler,
"denoise": denoise,
"add_noise": "disable"
}
}
return (new_pipe,)
# 预采样设置(自定义)
from comfy_extras.nodes_custom_sampler import BasicGuider, DualCFGGuider, CFGGuider, KSamplerSelect, DisableNoise, RandomNoise, BasicScheduler, KarrasScheduler, ExponentialScheduler, PolyexponentialScheduler, SDTurboScheduler, VPScheduler
from tqdm import trange
class samplerCustomSettings:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {"required":
{"pipe": ("PIPE_LINE",),
"guider": (['CFG','DualCFG','IP2P+DualCFG','Basic'],{"default":"Basic"}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
"cfg_negative": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS + ['inversed_euler'],),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS + ['karrasADV','exponentialADV','polyExponential','sdturbo','vp'],),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"sigma_max": ("FLOAT", {"default": 14.614642, "min": 0.0, "max": 1000.0, "step": 0.01, "round": False}),
"sigma_min": ("FLOAT", {"default": 0.0291675, "min": 0.0, "max": 1000.0, "step": 0.01, "round": False}),
"rho": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "step": 0.01, "round": False}),
"beta_d": ("FLOAT", {"default": 19.9, "min": 0.0, "max": 1000.0, "step": 0.01, "round": False}),
"beta_min": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 1000.0, "step": 0.01, "round": False}),
"eps_s": ("FLOAT", {"default": 0.001, "min": 0.0, "max": 1.0, "step": 0.0001, "round": False}),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"add_noise": (["enable", "disable"], {"default": "enable"}),
"seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"optional": {
"image_to_latent": ("IMAGE",),
"latent": ("LATENT",),
"optional_sampler":("SAMPLER",),
"optional_sigmas":("SIGMAS",),
},
"hidden":
{"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
RETURN_TYPES = ("PIPE_LINE", )
RETURN_NAMES = ("pipe",)
OUTPUT_NODE = True
FUNCTION = "settings"
CATEGORY = "EasyUse/PreSampling"
def ip2p(self, positive, negative, vae=None, pixels=None, latent=None):
if latent is not None:
concat_latent = latent
else:
x = (pixels.shape[1] // 8) * 8
y = (pixels.shape[2] // 8) * 8
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, :]
concat_latent = vae.encode(pixels)
out_latent = {}
out_latent["samples"] = torch.zeros_like(concat_latent)
out = []
for conditioning in [positive, negative]:
c = []
for t in conditioning:
d = t[1].copy()
d["concat_latent_image"] = concat_latent
n = [t[0], d]
c.append(n)
out.append(c)
return (out[0], out[1], out_latent)
def get_inversed_euler_sampler(self):
@torch.no_grad()
def sample_inversed_euler(model, x, sigmas, extra_args=None, callback=None, disable=None, s_churn=0., s_tmin=0.,
s_tmax=float('inf'), s_noise=1.):
"""Implements Algorithm 2 (Euler steps) from Karras et al. (2022)."""
extra_args = {} if extra_args is None else extra_args
s_in = x.new_ones([x.shape[0]])
for i in trange(1, len(sigmas), disable=disable):
sigma_in = sigmas[i - 1]
if i == 1:
sigma_t = sigmas[i]
else:
sigma_t = sigma_in
denoised = model(x, sigma_t * s_in, **extra_args)
if i == 1:
d = (x - denoised) / (2 * sigmas[i])
else:
d = (x - denoised) / sigmas[i - 1]
dt = sigmas[i] - sigmas[i - 1]
x = x + d * dt
if callback is not None:
callback(
{'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised})
return x / sigmas[-1]
ksampler = comfy.samplers.KSAMPLER(sample_inversed_euler)
return (ksampler,)
def settings(self, pipe, guider, cfg, cfg_negative, sampler_name, scheduler, steps, sigma_max, sigma_min, rho, beta_d, beta_min, eps_s, denoise, add_noise, seed, image_to_latent=None, latent=None, optional_sampler=None, optional_sigmas=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
# 图生图转换
vae = pipe["vae"]
model = pipe["model"]
positive = pipe['positive']
negative = pipe['negative']
batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1
_guider, sigmas = None, None
if image_to_latent is not None:
if guider == "IP2P+DualCFG":
positive, negative, latent = self.ip2p(pipe['positive'], pipe['negative'], vae, image_to_latent)
samples = latent
else:
samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])}
samples = RepeatLatentBatch().repeat(samples, batch_size)[0]
images = image_to_latent
elif latent is not None:
if guider == "IP2P+DualCFG":
positive, negative, latent = self.ip2p(pipe['positive'], pipe['negative'], latent=latent)
samples = latent
else:
samples = latent
images = pipe["images"]
else:
samples = pipe["samples"]
images = pipe["images"]
# guider
if guider == 'CFG':
_guider, = CFGGuider().get_guider(model, positive, negative, cfg)
elif guider in ['DualCFG', 'IP2P+DualCFG']:
_guider, = DualCFGGuider().get_guider(model, positive, negative, pipe['negative'], cfg, cfg_negative)
else:
_guider, = BasicGuider().get_guider(model, positive)
# sampler
if optional_sampler:
sampler = optional_sampler
else:
if sampler_name == 'inversed_euler':
sampler, = self.get_inversed_euler_sampler()
else:
sampler, = KSamplerSelect().get_sampler(sampler_name)
# sigmas
if optional_sigmas:
sigmas = optional_sigmas
else:
if scheduler == 'vp':
sigmas, = VPScheduler().get_sigmas(steps, beta_d, beta_min, eps_s)
elif scheduler == 'karrasADV':
sigmas, = KarrasScheduler().get_sigmas(steps, sigma_max, sigma_min, rho)
elif scheduler == 'exponentialADV':
sigmas, = ExponentialScheduler().get_sigmas(steps, sigma_max, sigma_min)
elif scheduler == 'polyExponential':
sigmas, = PolyexponentialScheduler().get_sigmas(steps, sigma_max, sigma_min, rho)
elif scheduler == 'sdturbo':
sigmas, = SDTurboScheduler().get_sigmas(model, steps, denoise)
else:
sigmas, = BasicScheduler().get_sigmas(model, scheduler, steps, denoise)
# noise
if add_noise == 'disabled':
noise, = DisableNoise().get_noise()
else:
noise, = RandomNoise().get_noise(seed)
new_pipe = {
"model": pipe['model'],
"positive": pipe['positive'],
"negative": pipe['negative'],
"vae": pipe['vae'],
"clip": pipe['clip'],
"samples": samples,
"images": images,
"seed": seed,
"loader_settings": {
**pipe["loader_settings"],
"steps": steps,
"cfg": cfg,
"sampler_name": sampler_name,
"scheduler": scheduler,
"denoise": denoise,
"custom": {
"noise": noise,
"guider": _guider,
"sampler": sampler,
"sigmas": sigmas,
}
}
}
del pipe
return {"ui": {"value": [seed]}, "result": (new_pipe,)}
# 预采样设置(SDTurbo)
from .gradual_latent_hires_fix import sample_dpmpp_2s_ancestral, sample_dpmpp_2m_sde, sample_lcm, sample_euler_ancestral
class sdTurboSettings:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {"required": {
"pipe": ("PIPE_LINE",),
"steps": ("INT", {"default": 1, "min": 1, "max": 10}),
"cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0}),
"sampler_name": (comfy.samplers.SAMPLER_NAMES,),
"eta": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01, "round": False}),
"s_noise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01, "round": False}),
"upscale_ratio": ("FLOAT", {"default": 2.0, "min": 0.0, "max": 16.0, "step": 0.01, "round": False}),
"start_step": ("INT", {"default": 5, "min": 0, "max": 1000, "step": 1}),
"end_step": ("INT", {"default": 15, "min": 0, "max": 1000, "step": 1}),
"upscale_n_step": ("INT", {"default": 3, "min": 0, "max": 1000, "step": 1}),
"unsharp_kernel_size": ("INT", {"default": 3, "min": 1, "max": 21, "step": 1}),
"unsharp_sigma": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 10.0, "step": 0.01, "round": False}),
"unsharp_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.01, "round": False}),
"seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
RETURN_TYPES = ("PIPE_LINE",)
RETURN_NAMES = ("pipe",)
OUTPUT_NODE = True
FUNCTION = "settings"
CATEGORY = "EasyUse/PreSampling"
def settings(self, pipe, steps, cfg, sampler_name, eta, s_noise, upscale_ratio, start_step, end_step, upscale_n_step, unsharp_kernel_size, unsharp_sigma, unsharp_strength, seed, prompt=None, extra_pnginfo=None, my_unique_id=None):
model = pipe['model']
# sigma
timesteps = torch.flip(torch.arange(1, 11) * 100 - 1, (0,))[:steps]
sigmas = model.model.model_sampling.sigma(timesteps)
sigmas = torch.cat([sigmas, sigmas.new_zeros([1])])
#sampler
sample_function = None
extra_options = {
"eta": eta,
"s_noise": s_noise,
"upscale_ratio": upscale_ratio,
"start_step": start_step,
"end_step": end_step,
"upscale_n_step": upscale_n_step,
"unsharp_kernel_size": unsharp_kernel_size,
"unsharp_sigma": unsharp_sigma,
"unsharp_strength": unsharp_strength,
}
if sampler_name == "euler_ancestral":
sample_function = sample_euler_ancestral
elif sampler_name == "dpmpp_2s_ancestral":
sample_function = sample_dpmpp_2s_ancestral
elif sampler_name == "dpmpp_2m_sde":
sample_function = sample_dpmpp_2m_sde
elif sampler_name == "lcm":
sample_function = sample_lcm
if sample_function is not None:
unsharp_kernel_size = unsharp_kernel_size if unsharp_kernel_size % 2 == 1 else unsharp_kernel_size + 1
extra_options["unsharp_kernel_size"] = unsharp_kernel_size
_sampler = comfy.samplers.KSAMPLER(sample_function, extra_options)
else:
_sampler = comfy.samplers.sampler_object(sampler_name)
extra_options = None
new_pipe = {
"model": pipe['model'],
"positive": pipe['positive'],
"negative": pipe['negative'],
"vae": pipe['vae'],
"clip": pipe['clip'],
"samples": pipe["samples"],
"images": pipe["images"],
"seed": seed,
"loader_settings": {
**pipe["loader_settings"],
"extra_options": extra_options,
"sampler": _sampler,
"sigmas": sigmas,
"steps": steps,
"cfg": cfg,
"add_noise": "enabled"
}
}
del pipe
return {"ui": {"value": [seed]}, "result": (new_pipe,)}
# cascade预采样参数
class cascadeSettings:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {"required":
{"pipe": ("PIPE_LINE",),
"encode_vae_name": (["None"] + folder_paths.get_filename_list("vae"),),
"decode_vae_name": (["None"] + folder_paths.get_filename_list("vae"),),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 4.0, "min": 0.0, "max": 100.0}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default":"euler_ancestral"}),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"default":"simple"}),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"optional": {
"image_to_latent_c": ("IMAGE",),
"latent_c": ("LATENT",),
},
"hidden":{"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
RETURN_TYPES = ("PIPE_LINE",)
RETURN_NAMES = ("pipe",)
OUTPUT_NODE = True
FUNCTION = "settings"
CATEGORY = "EasyUse/PreSampling"
def settings(self, pipe, encode_vae_name, decode_vae_name, steps, cfg, sampler_name, scheduler, denoise, seed, model=None, image_to_latent_c=None, latent_c=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
images, samples_c = None, None
samples = pipe['samples']
batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1
encode_vae_name = encode_vae_name if encode_vae_name is not None else pipe['loader_settings']['encode_vae_name']
decode_vae_name = decode_vae_name if decode_vae_name is not None else pipe['loader_settings']['decode_vae_name']
if image_to_latent_c is not None:
if encode_vae_name != 'None':
encode_vae = easyCache.load_vae(encode_vae_name)
else:
encode_vae = pipe['vae'][0]
if "compression" not in pipe["loader_settings"]:
raise Exception("compression is not found")
compression = pipe["loader_settings"]['compression']
width = image_to_latent_c.shape[-2]
height = image_to_latent_c.shape[-3]
out_width = (width // compression) * encode_vae.downscale_ratio
out_height = (height // compression) * encode_vae.downscale_ratio
s = comfy.utils.common_upscale(image_to_latent_c.movedim(-1, 1), out_width, out_height, "bicubic",
"center").movedim(1,
-1)
c_latent = encode_vae.encode(s[:, :, :, :3])
b_latent = torch.zeros([c_latent.shape[0], 4, height // 4, width // 4])
samples_c = {"samples": c_latent}
samples_c = RepeatLatentBatch().repeat(samples_c, batch_size)[0]
samples_b = {"samples": b_latent}
samples_b = RepeatLatentBatch().repeat(samples_b, batch_size)[0]
samples = (samples_c, samples_b)
images = image_to_latent_c
elif latent_c is not None:
samples_c = latent_c
samples = (samples_c, samples[1])
images = pipe["images"]
if samples_c is not None:
samples = (samples_c, samples[1])
new_pipe = {
"model": pipe['model'],
"positive": pipe['positive'],
"negative": pipe['negative'],
"vae": pipe['vae'],
"clip": pipe['clip'],
"samples": samples,
"images": images,
"seed": seed,
"loader_settings": {
**pipe["loader_settings"],
"encode_vae_name": encode_vae_name,
"decode_vae_name": decode_vae_name,
"steps": steps,
"cfg": cfg,
"sampler_name": sampler_name,
"scheduler": scheduler,
"denoise": denoise,
"add_noise": "enabled"
}
}
sampler.update_value_by_id("pipe_line", my_unique_id, new_pipe)
del pipe
return {"ui": {"value": [seed]}, "result": (new_pipe,)}
# layerDiffusion预采样参数
class layerDiffusionSettings:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {"required":
{
"pipe": ("PIPE_LINE",),
"method": ([LayerMethod.FG_ONLY_ATTN.value, LayerMethod.FG_ONLY_CONV.value, LayerMethod.EVERYTHING.value, LayerMethod.FG_TO_BLEND.value, LayerMethod.BG_TO_BLEND.value],),
"weight": ("FLOAT",{"default": 1.0, "min": -1, "max": 3, "step": 0.05},),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default": "euler"}),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"default": "normal"}),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"optional": {
"image": ("IMAGE",),
"blended_image": ("IMAGE",),
"mask": ("MASK",),
# "latent": ("LATENT",),
# "blended_latent": ("LATENT",),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
RETURN_TYPES = ("PIPE_LINE",)
RETURN_NAMES = ("pipe",)
OUTPUT_NODE = True
FUNCTION = "settings"
CATEGORY = "EasyUse/PreSampling"
def get_layer_diffusion_method(self, method, has_blend_latent):
method = LayerMethod(method)
if has_blend_latent:
if method == LayerMethod.BG_TO_BLEND:
method = LayerMethod.BG_BLEND_TO_FG
elif method == LayerMethod.FG_TO_BLEND:
method = LayerMethod.FG_BLEND_TO_BG
return method
def settings(self, pipe, method, weight, steps, cfg, sampler_name, scheduler, denoise, seed, image=None, blended_image=None, mask=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
blend_samples = pipe['blend_samples'] if "blend_samples" in pipe else None
vae = pipe["vae"]
batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1
method = self.get_layer_diffusion_method(method, blend_samples is not None or blended_image is not None)
if image is not None or "image" in pipe:
image = image if image is not None else pipe['image']
if mask is not None:
print('inpaint')
samples, = VAEEncodeForInpaint().encode(vae, image, mask)
else:
samples = {"samples": vae.encode(image[:,:,:,:3])}
samples = RepeatLatentBatch().repeat(samples, batch_size)[0]
images = image
elif "samp_images" in pipe:
samples = {"samples": vae.encode(pipe["samp_images"][:,:,:,:3])}
samples = RepeatLatentBatch().repeat(samples, batch_size)[0]
images = pipe["samp_images"]
else:
if method not in [LayerMethod.FG_ONLY_ATTN, LayerMethod.FG_ONLY_CONV, LayerMethod.EVERYTHING]:
raise Exception("image is missing")
samples = pipe["samples"]
images = pipe["images"]
if method in [LayerMethod.BG_BLEND_TO_FG, LayerMethod.FG_BLEND_TO_BG]:
if blended_image is None and blend_samples is None:
raise Exception("blended_image is missing")
elif blended_image is not None:
blend_samples = {"samples": vae.encode(blended_image[:,:,:,:3])}
blend_samples = RepeatLatentBatch().repeat(blend_samples, batch_size)[0]
new_pipe = {
"model": pipe['model'],
"positive": pipe['positive'],
"negative": pipe['negative'],
"vae": pipe['vae'],
"clip": pipe['clip'],
"samples": samples,
"blend_samples": blend_samples,
"images": images,
"seed": seed,
"loader_settings": {
**pipe["loader_settings"],
"steps": steps,
"cfg": cfg,
"sampler_name": sampler_name,
"scheduler": scheduler,
"denoise": denoise,
"add_noise": "enabled",
"layer_diffusion_method": method,
"layer_diffusion_weight": weight,
}
}
del pipe
return {"ui": {"value": [seed]}, "result": (new_pipe,)}
# 预采样设置(layerDiffuse附加)
class layerDiffusionSettingsADDTL:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {"required":
{
"pipe": ("PIPE_LINE",),
"foreground_prompt": ("STRING", {"default": "", "placeholder": "Foreground Additional Prompt", "multiline": True}),
"background_prompt": ("STRING", {"default": "", "placeholder": "Background Additional Prompt", "multiline": True}),
"blended_prompt": ("STRING", {"default": "", "placeholder": "Blended Additional Prompt", "multiline": True}),
},
"optional": {
"optional_fg_cond": ("CONDITIONING",),
"optional_bg_cond": ("CONDITIONING",),
"optional_blended_cond": ("CONDITIONING",),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
RETURN_TYPES = ("PIPE_LINE",)
RETURN_NAMES = ("pipe",)
OUTPUT_NODE = True
FUNCTION = "settings"
CATEGORY = "EasyUse/PreSampling"
def settings(self, pipe, foreground_prompt, background_prompt, blended_prompt, optional_fg_cond=None, optional_bg_cond=None, optional_blended_cond=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
fg_cond, bg_cond, blended_cond = None, None, None
clip = pipe['clip']
if optional_fg_cond is not None:
fg_cond = optional_fg_cond
elif foreground_prompt != "":
fg_cond, = CLIPTextEncode().encode(clip, foreground_prompt)
if optional_bg_cond is not None:
bg_cond = optional_bg_cond
elif background_prompt != "":
bg_cond, = CLIPTextEncode().encode(clip, background_prompt)
if optional_blended_cond is not None:
blended_cond = optional_blended_cond
elif blended_prompt != "":
blended_cond, = CLIPTextEncode().encode(clip, blended_prompt)
new_pipe = {
**pipe,
"loader_settings": {
**pipe["loader_settings"],
"layer_diffusion_cond": (fg_cond, bg_cond, blended_cond)
}
}
del pipe
return (new_pipe,)
# 预采样设置(动态CFG)
from .dynthres_core import DynThresh
class dynamicCFGSettings:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {"required":
{"pipe": ("PIPE_LINE",),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
"cfg_mode": (DynThresh.Modes,),
"cfg_scale_min": ("FLOAT", {"default": 3.5, "min": 0.0, "max": 100.0, "step": 0.5}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"optional":{
"image_to_latent": ("IMAGE",),
"latent": ("LATENT",)
},
"hidden":
{"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
RETURN_TYPES = ("PIPE_LINE",)
RETURN_NAMES = ("pipe",)
OUTPUT_NODE = True
FUNCTION = "settings"
CATEGORY = "EasyUse/PreSampling"
def settings(self, pipe, steps, cfg, cfg_mode, cfg_scale_min,sampler_name, scheduler, denoise, seed, image_to_latent=None, latent=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
dynamic_thresh = DynThresh(7.0, 1.0,"CONSTANT", 0, cfg_mode, cfg_scale_min, 0, 0, 999, False,
"MEAN", "AD", 1)
def sampler_dyn_thresh(args):
input = args["input"]
cond = input - args["cond"]
uncond = input - args["uncond"]
cond_scale = args["cond_scale"]
time_step = args["timestep"]
dynamic_thresh.step = 999 - time_step[0]
return input - dynamic_thresh.dynthresh(cond, uncond, cond_scale, None)
model = pipe['model']
m = model.clone()
m.set_model_sampler_cfg_function(sampler_dyn_thresh)
# 图生图转换
vae = pipe["vae"]
batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1
if image_to_latent is not None:
samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])}
samples = RepeatLatentBatch().repeat(samples, batch_size)[0]
images = image_to_latent
elif latent is not None:
samples = RepeatLatentBatch().repeat(latent, batch_size)[0]
images = pipe["images"]
else:
samples = pipe["samples"]
images = pipe["images"]
new_pipe = {
"model": m,
"positive": pipe['positive'],
"negative": pipe['negative'],
"vae": pipe['vae'],
"clip": pipe['clip'],
"samples": samples,
"images": images,
"seed": seed,
"loader_settings": {
**pipe["loader_settings"],
"steps": steps,
"cfg": cfg,
"sampler_name": sampler_name,
"scheduler": scheduler,
"denoise": denoise
},
}
del pipe
return {"ui": {"value": [seed]}, "result": (new_pipe,)}
# 动态CFG
class dynamicThresholdingFull:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model": ("MODEL",),
"mimic_scale": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "step": 0.5}),
"threshold_percentile": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"mimic_mode": (DynThresh.Modes,),
"mimic_scale_min": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.5}),
"cfg_mode": (DynThresh.Modes,),
"cfg_scale_min": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.5}),
"sched_val": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}),
"separate_feature_channels": (["enable", "disable"],),
"scaling_startpoint": (DynThresh.Startpoints,),
"variability_measure": (DynThresh.Variabilities,),
"interpolate_phi": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
}
}
RETURN_TYPES = ("MODEL",)
FUNCTION = "patch"
CATEGORY = "EasyUse/PreSampling"
def patch(self, model, mimic_scale, threshold_percentile, mimic_mode, mimic_scale_min, cfg_mode, cfg_scale_min,
sched_val, separate_feature_channels, scaling_startpoint, variability_measure, interpolate_phi):
dynamic_thresh = DynThresh(mimic_scale, threshold_percentile, mimic_mode, mimic_scale_min, cfg_mode,
cfg_scale_min, sched_val, 0, 999, separate_feature_channels == "enable",
scaling_startpoint, variability_measure, interpolate_phi)
def sampler_dyn_thresh(args):
input = args["input"]
cond = input - args["cond"]
uncond = input - args["uncond"]
cond_scale = args["cond_scale"]
time_step = args["timestep"]
dynamic_thresh.step = 999 - time_step[0]
return input - dynamic_thresh.dynthresh(cond, uncond, cond_scale, None)
m = model.clone()
m.set_model_sampler_cfg_function(sampler_dyn_thresh)
return (m,)
#---------------------------------------------------------------采样器 开始----------------------------------------------------------------------
# 完整采样器
class samplerFull(LayerDiffuse):
def __init__(self):
super().__init__()
@classmethod
def INPUT_TYPES(cls):
return {"required":
{"pipe": ("PIPE_LINE",),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"image_output": (["Hide", "Preview", "Save", "Hide/Save", "Sender", "Sender/Save"],),
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
"save_prefix": ("STRING", {"default": "ComfyUI"}),
},
"optional": {
"seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
"model": ("MODEL",),
"positive": ("CONDITIONING",),
"negative": ("CONDITIONING",),
"latent": ("LATENT",),
"vae": ("VAE",),
"clip": ("CLIP",),
"xyPlot": ("XYPLOT",),
"image": ("IMAGE",),
},
"hidden":
{"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID",
"embeddingsList": (folder_paths.get_filename_list("embeddings"),)
}
}
RETURN_TYPES = ("PIPE_LINE", "IMAGE", "MODEL", "CONDITIONING", "CONDITIONING", "LATENT", "VAE", "CLIP", "INT",)
RETURN_NAMES = ("pipe", "image", "model", "positive", "negative", "latent", "vae", "clip", "seed",)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "EasyUse/Sampler"
def run(self, pipe, steps, cfg, sampler_name, scheduler, denoise, image_output, link_id, save_prefix, seed=None, model=None, positive=None, negative=None, latent=None, vae=None, clip=None, xyPlot=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False, downscale_options=None):
# Clean loaded_objects
easyCache.update_loaded_objects(prompt)
samp_model = model if model is not None else pipe["model"]
samp_positive = positive if positive is not None else pipe["positive"]
samp_negative = negative if negative is not None else pipe["negative"]
samp_samples = latent if latent is not None else pipe["samples"]
samp_vae = vae if vae is not None else pipe["vae"]
samp_clip = clip if clip is not None else pipe["clip"]
samp_seed = seed if seed is not None else pipe['seed']
samp_custom = pipe["loader_settings"]["custom"] if "custom" in pipe["loader_settings"] else None
steps = steps if steps is not None else pipe['loader_settings']['steps']
start_step = pipe['loader_settings']['start_step'] if 'start_step' in pipe['loader_settings'] else 0
last_step = pipe['loader_settings']['last_step'] if 'last_step' in pipe['loader_settings'] else 10000
cfg = cfg if cfg is not None else pipe['loader_settings']['cfg']
sampler_name = sampler_name if sampler_name is not None else pipe['loader_settings']['sampler_name']
scheduler = scheduler if scheduler is not None else pipe['loader_settings']['scheduler']
denoise = denoise if denoise is not None else pipe['loader_settings']['denoise']
add_noise = pipe['loader_settings']['add_noise'] if 'add_noise' in pipe['loader_settings'] else 'enabled'
force_full_denoise = pipe['loader_settings']['force_full_denoise'] if 'force_full_denoise' in pipe['loader_settings'] else True
disable_noise = False
if add_noise == "disable":
disable_noise = True
def downscale_model_unet(samp_model):
if downscale_options is None:
return samp_model
# 获取Unet参数
elif "PatchModelAddDownscale" in ALL_NODE_CLASS_MAPPINGS:
cls = ALL_NODE_CLASS_MAPPINGS['PatchModelAddDownscale']
# 自动收缩Unet
if downscale_options['downscale_factor'] is None:
unet_config = samp_model.model.model_config.unet_config
if unet_config is not None and "samples" in samp_samples:
height = samp_samples['samples'].shape[2] * 8
width = samp_samples['samples'].shape[3] * 8
context_dim = unet_config.get('context_dim')
longer_side = width if width > height else height
if context_dim is not None and longer_side > context_dim:
width_downscale_factor = float(width / context_dim)
height_downscale_factor = float(height / context_dim)
if width_downscale_factor > 1.75:
log_node_warn("正在收缩模型Unet...")
log_node_warn("收缩系数:" + str(width_downscale_factor))
(samp_model,) = cls().patch(samp_model, downscale_options['block_number'], width_downscale_factor, 0, 0.35, True, "bicubic",
"bicubic")
elif height_downscale_factor > 1.25:
log_node_warn("正在收缩模型Unet...")
log_node_warn("收缩系数:" + str(height_downscale_factor))
(samp_model,) = cls().patch(samp_model, downscale_options['block_number'], height_downscale_factor, 0, 0.35, True, "bicubic",
"bicubic")
else:
cls = ALL_NODE_CLASS_MAPPINGS['PatchModelAddDownscale']
log_node_warn("正在收缩模型Unet...")
log_node_warn("收缩系数:" + str(downscale_options['downscale_factor']))
(samp_model,) = cls().patch(samp_model, downscale_options['block_number'], downscale_options['downscale_factor'], downscale_options['start_percent'], downscale_options['end_percent'], downscale_options['downscale_after_skip'], downscale_options['downscale_method'], downscale_options['upscale_method'])
return samp_model
def process_sample_state(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive,
samp_negative,
steps, start_step, last_step, cfg, sampler_name, scheduler, denoise,
image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id,
preview_latent, force_full_denoise=force_full_denoise, disable_noise=disable_noise, samp_custom=None):
# LayerDiffusion
if "layer_diffusion_method" in pipe['loader_settings']:
samp_blend_samples = pipe["blend_samples"] if "blend_samples" in pipe else None
additional_cond = pipe["loader_settings"]['layer_diffusion_cond'] if "layer_diffusion_cond" in pipe[
'loader_settings'] else (None, None, None)
method = self.get_layer_diffusion_method(pipe['loader_settings']['layer_diffusion_method'],
samp_blend_samples is not None)
images = pipe["images"].movedim(-1, 1) if "images" in pipe else None
weight = pipe['loader_settings']['layer_diffusion_weight'] if 'layer_diffusion_weight' in pipe[
'loader_settings'] else 1.0
samp_model, samp_positive, samp_negative = self.apply_layer_diffusion(samp_model, method, weight,
samp_samples, samp_blend_samples,
samp_positive, samp_negative,
images, additional_cond)
blend_samples = pipe['blend_samples'] if "blend_samples" in pipe else None
layer_diffusion_method = pipe['loader_settings']['layer_diffusion_method'] if 'layer_diffusion_method' in pipe['loader_settings'] else None
empty_samples = pipe["loader_settings"]["empty_samples"] if "empty_samples" in pipe["loader_settings"] else None
samples = empty_samples if layer_diffusion_method is not None and empty_samples is not None else samp_samples
# Downscale Model Unet
if samp_model is not None:
samp_model = downscale_model_unet(samp_model)
# 推理初始时间
start_time = int(time.time() * 1000)
# 开始推理
samp_samples = sampler.common_ksampler(samp_model, samp_seed, steps, cfg, sampler_name, scheduler, samp_positive, samp_negative, samples, denoise=denoise, preview_latent=preview_latent, start_step=start_step, last_step=last_step, force_full_denoise=force_full_denoise, disable_noise=disable_noise, custom=samp_custom)
# 推理结束时间
end_time = int(time.time() * 1000)
latent = samp_samples["samples"]
# 解码图片
if tile_size is not None:
samp_images = samp_vae.decode_tiled(latent, tile_x=tile_size // 8, tile_y=tile_size // 8, )
else:
samp_images = samp_vae.decode(latent).cpu()
# LayerDiffusion Decode
new_images, samp_images, alpha = self.layer_diffusion_decode(layer_diffusion_method, latent, blend_samples, samp_images, samp_model)
# 推理总耗时(包含解码)
end_decode_time = int(time.time() * 1000)
spent_time = '扩散:' + str((end_time-start_time)/1000)+'秒, 解码:' + str((end_decode_time-end_time)/1000)+'秒'
results = easySave(new_images, save_prefix, image_output, prompt, extra_pnginfo)
sampler.update_value_by_id("results", my_unique_id, results)
# Clean loaded_objects
easyCache.update_loaded_objects(prompt)
new_pipe = {
"model": samp_model,
"positive": samp_positive,
"negative": samp_negative,
"vae": samp_vae,
"clip": samp_clip,
"samples": samp_samples,
"blend_samples": blend_samples,
"images": new_images,
"samp_images": samp_images,
"alpha": alpha,
"seed": samp_seed,
"loader_settings": {
**pipe["loader_settings"],
"spent_time": spent_time
}
}
sampler.update_value_by_id("pipe_line", my_unique_id, new_pipe)
del pipe
if image_output in ("Hide", "Hide/Save"):
return {"ui": {},
"result": sampler.get_output(new_pipe,)}
if image_output in ("Sender", "Sender/Save"):
PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results})
ModelPatcher.calculate_weight = default_calculate_weight
return {"ui": {"images": results},
"result": sampler.get_output(new_pipe,)}
def process_xyPlot(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, samp_negative,
steps, cfg, sampler_name, scheduler, denoise,
image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, preview_latent, xyPlot, force_full_denoise, disable_noise, samp_custom):
sampleXYplot = easyXYPlot(xyPlot, save_prefix, image_output, prompt, extra_pnginfo, my_unique_id, sampler, easyCache)
if not sampleXYplot.validate_xy_plot():
return process_sample_state(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive,
samp_negative, steps, 0, 10000, cfg,
sampler_name, scheduler, denoise, image_output, link_id, save_prefix, tile_size, prompt,
extra_pnginfo, my_unique_id, preview_latent, samp_custom=samp_custom)
# Downscale Model Unet
if samp_model is not None:
samp_model = downscale_model_unet(samp_model)
alpha = None
blend_samples = pipe['blend_samples'] if "blend_samples" in pipe else None
layer_diffusion_method = pipe['loader_settings']['layer_diffusion_method'] if 'layer_diffusion_method' in pipe['loader_settings'] else None
plot_image_vars = {
"x_node_type": sampleXYplot.x_node_type, "y_node_type": sampleXYplot.y_node_type,
"lora_name": pipe["loader_settings"]["lora_name"] if "lora_name" in pipe["loader_settings"] else None,
"lora_model_strength": pipe["loader_settings"]["lora_model_strength"] if "model_strength" in pipe["loader_settings"] else None,
"lora_clip_strength": pipe["loader_settings"]["lora_clip_strength"] if "clip_strength" in pipe["loader_settings"] else None,
"lora_stack": pipe["loader_settings"]["lora_stack"] if "lora_stack" in pipe["loader_settings"] else None,
"steps": steps,
"cfg": cfg,
"sampler_name": sampler_name,
"scheduler": scheduler,
"denoise": denoise,
"seed": samp_seed,
"images": pipe['images'],
"model": samp_model, "vae": samp_vae, "clip": samp_clip, "positive_cond": samp_positive,
"negative_cond": samp_negative,
"ckpt_name": pipe['loader_settings']['ckpt_name'] if "ckpt_name" in pipe["loader_settings"] else None,
"vae_name": pipe['loader_settings']['vae_name'] if "vae_name" in pipe["loader_settings"] else None,
"clip_skip": pipe['loader_settings']['clip_skip'] if "clip_skip" in pipe["loader_settings"] else None,
"positive": pipe['loader_settings']['positive'] if "positive" in pipe["loader_settings"] else None,
"positive_token_normalization": pipe['loader_settings']['positive_token_normalization'] if "positive_token_normalization" in pipe["loader_settings"] else None,
"positive_weight_interpretation": pipe['loader_settings']['positive_weight_interpretation'] if "positive_weight_interpretation" in pipe["loader_settings"] else None,
"negative": pipe['loader_settings']['negative'] if "negative" in pipe["loader_settings"] else None,
"negative_token_normalization": pipe['loader_settings']['negative_token_normalization'] if "negative_token_normalization" in pipe["loader_settings"] else None,
"negative_weight_interpretation": pipe['loader_settings']['negative_weight_interpretation'] if "negative_weight_interpretation" in pipe["loader_settings"] else None,
}
if "models" in pipe["loader_settings"]:
plot_image_vars["models"] = pipe["loader_settings"]["models"]
if "vae_use" in pipe["loader_settings"]:
plot_image_vars["vae_use"] = pipe["loader_settings"]["vae_use"]
if "a1111_prompt_style" in pipe["loader_settings"]:
plot_image_vars["a1111_prompt_style"] = pipe["loader_settings"]["a1111_prompt_style"]
if "cnet_stack" in pipe["loader_settings"]:
plot_image_vars["cnet"] = pipe["loader_settings"]["cnet_stack"]
if "positive_cond_stack" in pipe["loader_settings"]:
plot_image_vars["positive_cond_stack"] = pipe["loader_settings"]["positive_cond_stack"]
if "negative_cond_stack" in pipe["loader_settings"]:
plot_image_vars["negative_cond_stack"] = pipe["loader_settings"]["negative_cond_stack"]
if layer_diffusion_method:
plot_image_vars["layer_diffusion_method"] = layer_diffusion_method
if "layer_diffusion_weight" in pipe["loader_settings"]:
plot_image_vars["layer_diffusion_weight"] = pipe['loader_settings']['layer_diffusion_weight']
if "layer_diffusion_cond" in pipe["loader_settings"]:
plot_image_vars["layer_diffusion_cond"] = pipe['loader_settings']['layer_diffusion_cond']
if "empty_samples" in pipe["loader_settings"]:
plot_image_vars["empty_samples"] = pipe["loader_settings"]['empty_samples']
latent_image = sampleXYplot.get_latent(pipe["samples"])
latents_plot = sampleXYplot.get_labels_and_sample(plot_image_vars, latent_image, preview_latent, start_step,
last_step, force_full_denoise, disable_noise)
samp_samples = {"samples": latents_plot}
images, image_list = sampleXYplot.plot_images_and_labels()
# Generate output_images
output_images = torch.stack([tensor.squeeze() for tensor in image_list])
new_images, samp_images, alpha = self.layer_diffusion_decode(layer_diffusion_method, latents_plot, blend_samples,
output_images, samp_model)
results = easySave(images, save_prefix, image_output, prompt, extra_pnginfo)
sampler.update_value_by_id("results", my_unique_id, results)
# Clean loaded_objects
easyCache.update_loaded_objects(prompt)
new_pipe = {
"model": samp_model,
"positive": samp_positive,
"negative": samp_negative,
"vae": samp_vae,
"clip": samp_clip,
"samples": samp_samples,
"blend_samples": blend_samples,
"samp_images": samp_images,
"images": new_images,
"seed": samp_seed,
"alpha": alpha,
"loader_settings": pipe["loader_settings"],
}
sampler.update_value_by_id("pipe_line", my_unique_id, new_pipe)
del pipe
if image_output in ("Hide", "Hide/Save"):
return sampler.get_output(new_pipe)
ModelPatcher.calculate_weight = default_calculate_weight
return {"ui": {"images": results}, "result": (sampler.get_output(new_pipe))}
preview_latent = True
if image_output in ("Hide", "Hide/Save"):
preview_latent = False
xyplot_id = next((x for x in prompt if "XYPlot" in str(prompt[x]["class_type"])), None)
if xyplot_id is None:
xyPlot = None
else:
xyPlot = pipe["loader_settings"]["xyplot"] if "xyplot" in pipe["loader_settings"] else xyPlot
if xyPlot is not None:
return process_xyPlot(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, samp_negative, steps, cfg, sampler_name, scheduler, denoise, image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, preview_latent, xyPlot, force_full_denoise, disable_noise, samp_custom)
else:
return process_sample_state(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, samp_negative, steps, start_step, last_step, cfg, sampler_name, scheduler, denoise, image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, preview_latent, force_full_denoise, disable_noise, samp_custom)
# 简易采样器
class samplerSimple:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {"required":
{"pipe": ("PIPE_LINE",),
"image_output": (["Hide", "Preview", "Save", "Hide/Save", "Sender", "Sender/Save"],{"default": "Preview"}),
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
"save_prefix": ("STRING", {"default": "ComfyUI"}),
},
"optional": {
"model": ("MODEL",),
},
"hidden":
{"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID",
"embeddingsList": (folder_paths.get_filename_list("embeddings"),)
}
}
RETURN_TYPES = ("PIPE_LINE", "IMAGE",)
RETURN_NAMES = ("pipe", "image",)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "EasyUse/Sampler"
def run(self, pipe, image_output, link_id, save_prefix, model=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False):
return samplerFull().run(pipe, None, None, None, None, None, image_output, link_id, save_prefix,
None, model, None, None, None, None, None, None,
None, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise)
# 简易采样器 (Tiled)
class samplerSimpleTiled:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {"required":
{"pipe": ("PIPE_LINE",),
"tile_size": ("INT", {"default": 512, "min": 320, "max": 4096, "step": 64}),
"image_output": (["Hide", "Preview", "Save", "Hide/Save", "Sender", "Sender/Save"],{"default": "Preview"}),
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
"save_prefix": ("STRING", {"default": "ComfyUI"})
},
"optional": {
"model": ("MODEL",),
},
"hidden": {
"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID",
"embeddingsList": (folder_paths.get_filename_list("embeddings"),)
}
}
RETURN_TYPES = ("PIPE_LINE", "IMAGE",)
RETURN_NAMES = ("pipe", "image",)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "EasyUse/Sampler"
def run(self, pipe, tile_size=512, image_output='preview', link_id=0, save_prefix='ComfyUI', model=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False):
return samplerFull().run(pipe, None, None,None,None,None, image_output, link_id, save_prefix,
None, model, None, None, None, None, None, None,
tile_size, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise)
# 简易采样器 (LayerDiffusion)
class samplerSimpleLayerDiffusion:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {"required":
{"pipe": ("PIPE_LINE",),
"image_output": (["Hide", "Preview", "Save", "Hide/Save", "Sender", "Sender/Save"],{"default": "Preview"}),
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
"save_prefix": ("STRING", {"default": "ComfyUI"})
},
"optional": {
"model": ("MODEL",),
},
"hidden": {
"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID",
"embeddingsList": (folder_paths.get_filename_list("embeddings"),)
}
}
RETURN_TYPES = ("PIPE_LINE", "IMAGE", "IMAGE", "MASK")
RETURN_NAMES = ("pipe", "final_image", "original_image", "alpha")
OUTPUT_NODE = True
OUTPUT_IS_LIST = (False, False, False, True)
FUNCTION = "run"
CATEGORY = "EasyUse/Sampler"
def run(self, pipe, image_output='preview', link_id=0, save_prefix='ComfyUI', model=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False):
result = samplerFull().run(pipe, None, None,None,None,None, image_output, link_id, save_prefix,
None, model, None, None, None, None, None, None,
None, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise)
pipe = result["result"][0] if "result" in result else None
return ({"ui":result['ui'], "result":(pipe, pipe["images"], pipe["samp_images"], pipe["alpha"])})
# 简易采样器(收缩Unet)
class samplerSimpleDownscaleUnet:
def __init__(self):
pass
upscale_methods = ["bicubic", "nearest-exact", "bilinear", "area", "bislerp"]
@classmethod
def INPUT_TYPES(s):
return {"required":
{"pipe": ("PIPE_LINE",),
"downscale_mode": (["None", "Auto", "Custom"],{"default": "Auto"}),
"block_number": ("INT", {"default": 3, "min": 1, "max": 32, "step": 1}),
"downscale_factor": ("FLOAT", {"default": 2.0, "min": 0.1, "max": 9.0, "step": 0.001}),
"start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"end_percent": ("FLOAT", {"default": 0.35, "min": 0.0, "max": 1.0, "step": 0.001}),
"downscale_after_skip": ("BOOLEAN", {"default": True}),
"downscale_method": (s.upscale_methods,),
"upscale_method": (s.upscale_methods,),
"image_output": (["Hide", "Preview", "Save", "Hide/Save", "Sender", "Sender/Save"],{"default": "Preview"}),
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
"save_prefix": ("STRING", {"default": "ComfyUI"}),
},
"optional": {
"model": ("MODEL",),
},
"hidden":
{"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID",
"embeddingsList": (folder_paths.get_filename_list("embeddings"),)
}
}
RETURN_TYPES = ("PIPE_LINE", "IMAGE",)
RETURN_NAMES = ("pipe", "image",)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "EasyUse/Sampler"
def run(self, pipe, downscale_mode, block_number, downscale_factor, start_percent, end_percent, downscale_after_skip, downscale_method, upscale_method, image_output, link_id, save_prefix, model=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False):
downscale_options = None
if downscale_mode == 'Auto':
downscale_options = {
"block_number": block_number,
"downscale_factor": None,
"start_percent": 0,
"end_percent":0.35,
"downscale_after_skip": True,
"downscale_method": "bicubic",
"upscale_method": "bicubic"
}
elif downscale_mode == 'Custom':
downscale_options = {
"block_number": block_number,
"downscale_factor": downscale_factor,
"start_percent": start_percent,
"end_percent": end_percent,
"downscale_after_skip": downscale_after_skip,
"downscale_method": downscale_method,
"upscale_method": upscale_method
}
return samplerFull().run(pipe, None, None,None,None,None, image_output, link_id, save_prefix,
None, model, None, None, None, None, None, None,
tile_size, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise, downscale_options)
# 简易采样器 (内补)
class samplerSimpleInpainting:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {"required":
{"pipe": ("PIPE_LINE",),
"grow_mask_by": ("INT", {"default": 6, "min": 0, "max": 64, "step": 1}),
"image_output": (["Hide", "Preview", "Save", "Hide/Save", "Sender", "Sender/Save"],{"default": "Preview"}),
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
"save_prefix": ("STRING", {"default": "ComfyUI"}),
"additional": (["None", "Differential Diffusion", "Only InpaintModelConditioning"],{"default": "None"})
},
"optional": {
"model": ("MODEL",),
"mask": ("MASK",),
"patch": ("INPAINT_PATCH",),
},
"hidden":
{"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID",
"embeddingsList": (folder_paths.get_filename_list("embeddings"),)
}
}
RETURN_TYPES = ("PIPE_LINE", "IMAGE", "VAE")
RETURN_NAMES = ("pipe", "image", "vae")
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "EasyUse/Sampler"
def run(self, pipe, grow_mask_by, image_output, link_id, save_prefix, additional, model=None, mask=None, patch=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False):
fooocus_model = None
model = model if model is not None else pipe['model']
latent = pipe['samples'] if 'samples' in pipe else None
positive = pipe['positive']
negative = pipe['negative']
pixels = pipe["images"] if pipe and "images" in pipe else None
vae = pipe["vae"] if pipe and "vae" in pipe else None
if 'noise_mask' in latent and mask is None:
mask = latent['noise_mask']
else:
if pixels is None:
raise Exception("No Images found")
if vae is None:
raise Exception("No VAE found")
latent, = VAEEncodeForInpaint().encode(vae, pixels, mask, grow_mask_by)
mask = latent['noise_mask']
if mask is not None:
if additional != "None":
positive, negative, latent = InpaintModelConditioning().encode(positive, negative, pixels, vae, mask)
if additional == "Differential Diffusion":
cls = ALL_NODE_CLASS_MAPPINGS['DifferentialDiffusion']
if cls is not None:
model, = cls().apply(model)
else:
raise Exception("Differential Diffusion not found,please update comfyui")
# when patch was linked
fooocus_model = None
if patch is not None:
worker = InpaintWorker(node_name="easy kSamplerInpainting")
fooocus_model, = worker.patch(model, latent, patch)
new_pipe = {
**pipe,
"model": fooocus_model if fooocus_model else model,
"positive": positive,
"negative": negative,
"vae": vae,
"samples": latent,
"loader_settings": pipe["loader_settings"],
}
else:
new_pipe = pipe
del pipe
return samplerFull().run(new_pipe, None, None,None,None,None, image_output, link_id, save_prefix,
None, None, None, None, None, None, None, None,
tile_size, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise)
# SDTurbo采样器
class samplerSDTurbo:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {"required":
{"pipe": ("PIPE_LINE",),
"image_output": (["Hide", "Preview", "Save", "Hide/Save", "Sender", "Sender/Save"],{"default": "Preview"}),
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
"save_prefix": ("STRING", {"default": "ComfyUI"}),
},
"optional": {
"model": ("MODEL",),
},
"hidden":
{"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO",
"my_unique_id": "UNIQUE_ID",
"embeddingsList": (folder_paths.get_filename_list("embeddings"),)
}
}
RETURN_TYPES = ("PIPE_LINE", "IMAGE",)
RETURN_NAMES = ("pipe", "image",)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "EasyUse/Sampler"
def run(self, pipe, image_output, link_id, save_prefix, model=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None,):
# Clean loaded_objects
easyCache.update_loaded_objects(prompt)
my_unique_id = int(my_unique_id)
samp_model = pipe["model"] if model is None else model
samp_positive = pipe["positive"]
samp_negative = pipe["negative"]
samp_samples = pipe["samples"]
samp_vae = pipe["vae"]
samp_clip = pipe["clip"]
samp_seed = pipe['seed']
samp_sampler = pipe['loader_settings']['sampler']
sigmas = pipe['loader_settings']['sigmas']
cfg = pipe['loader_settings']['cfg']
steps = pipe['loader_settings']['steps']
disable_noise = False
preview_latent = True
if image_output in ("Hide", "Hide/Save"):
preview_latent = False
# 推理初始时间
start_time = int(time.time() * 1000)
# 开始推理
samp_samples = sampler.custom_ksampler(samp_model, samp_seed, steps, cfg, samp_sampler, sigmas, samp_positive, samp_negative, samp_samples,
disable_noise, preview_latent)
# 推理结束时间
end_time = int(time.time() * 1000)
latent = samp_samples['samples']
# 解码图片
if tile_size is not None:
samp_images = samp_vae.decode_tiled(latent, tile_x=tile_size // 8, tile_y=tile_size // 8, )
else:
samp_images = samp_vae.decode(latent).cpu()
# 推理总耗时(包含解码)
end_decode_time = int(time.time() * 1000)
spent_time = '扩散:' + str((end_time - start_time) / 1000) + '秒, 解码:' + str(
(end_decode_time - end_time) / 1000) + '秒'
# Clean loaded_objects
easyCache.update_loaded_objects(prompt)
results = easySave(samp_images, save_prefix, image_output, prompt, extra_pnginfo)
sampler.update_value_by_id("results", my_unique_id, results)
new_pipe = {
"model": samp_model,
"positive": samp_positive,
"negative": samp_negative,
"vae": samp_vae,
"clip": samp_clip,
"samples": samp_samples,
"images": samp_images,
"seed": samp_seed,
"loader_settings": {
**pipe["loader_settings"],
"spent_time": spent_time
}
}
sampler.update_value_by_id("pipe_line", my_unique_id, new_pipe)
del pipe
if image_output in ("Hide", "Hide/Save"):
return {"ui": {},
"result": sampler.get_output(new_pipe, )}
if image_output in ("Sender", "Sender/Save"):
PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results})
return {"ui": {"images": results},
"result": sampler.get_output(new_pipe, )}
# Cascade完整采样器
class samplerCascadeFull:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {"required":
{"pipe": ("PIPE_LINE",),
"encode_vae_name": (["None"] + folder_paths.get_filename_list("vae"),),
"decode_vae_name": (["None"] + folder_paths.get_filename_list("vae"),),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 4.0, "min": 0.0, "max": 100.0}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default":"euler_ancestral"}),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"default":"simple"}),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"image_output": (["Hide", "Preview", "Save", "Hide/Save", "Sender", "Sender/Save"],),
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
"save_prefix": ("STRING", {"default": "ComfyUI"}),
"seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"optional": {
"image_to_latent_c": ("IMAGE",),
"latent_c": ("LATENT",),
"model_c": ("MODEL",),
},
"hidden":{"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID",
"embeddingsList": (folder_paths.get_filename_list("embeddings"),)
}
}
RETURN_TYPES = ("PIPE_LINE", "MODEL", "LATENT")
RETURN_NAMES = ("pipe", "model_b", "latent_b")
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "EasyUse/Sampler"
def run(self, pipe, encode_vae_name, decode_vae_name, steps, cfg, sampler_name, scheduler, denoise, image_output, link_id, save_prefix, seed, image_to_latent_c=None, latent_c=None, model_c=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False):
encode_vae_name = encode_vae_name if encode_vae_name is not None else pipe['loader_settings']['encode_vae_name']
decode_vae_name = decode_vae_name if decode_vae_name is not None else pipe['loader_settings']['decode_vae_name']
batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1
if image_to_latent_c is not None:
if encode_vae_name != 'None':
encode_vae = easyCache.load_vae(encode_vae_name)
else:
encode_vae = pipe['vae'][0]
if "compression" not in pipe["loader_settings"]:
raise Exception("compression is not found")
compression = pipe["loader_settings"]['compression']
width = image_to_latent_c.shape[-2]
height = image_to_latent_c.shape[-3]
out_width = (width // compression) * encode_vae.downscale_ratio
out_height = (height // compression) * encode_vae.downscale_ratio
s = comfy.utils.common_upscale(image_to_latent_c.movedim(-1, 1), out_width, out_height, "bicubic",
"center").movedim(1, -1)
latent_c = encode_vae.encode(s[:, :, :, :3])
latent_b = torch.zeros([latent_c.shape[0], 4, height // 4, width // 4])
samples_c = {"samples": latent_c}
samples_c = RepeatLatentBatch().repeat(samples_c, batch_size)[0]
samples_b = {"samples": latent_b}
samples_b = RepeatLatentBatch().repeat(samples_b, batch_size)[0]
images = image_to_latent_c
elif latent_c is not None:
samples_c = latent_c
samples_b = pipe["samples"][1]
images = pipe["images"]
else:
samples_c = pipe["samples"][0]
samples_b = pipe["samples"][1]
images = pipe["images"]
# Clean loaded_objects
easyCache.update_loaded_objects(prompt)
samp_model = model_c if model_c else pipe["model"][0]
samp_positive = pipe["positive"]
samp_negative = pipe["negative"]
samp_samples = samples_c
samp_seed = seed if seed is not None else pipe['seed']
steps = steps if steps is not None else pipe['loader_settings']['steps']
start_step = pipe['loader_settings']['start_step'] if 'start_step' in pipe['loader_settings'] else 0
last_step = pipe['loader_settings']['last_step'] if 'last_step' in pipe['loader_settings'] else 10000
cfg = cfg if cfg is not None else pipe['loader_settings']['cfg']
sampler_name = sampler_name if sampler_name is not None else pipe['loader_settings']['sampler_name']
scheduler = scheduler if scheduler is not None else pipe['loader_settings']['scheduler']
denoise = denoise if denoise is not None else pipe['loader_settings']['denoise']
# 推理初始时间
start_time = int(time.time() * 1000)
# 开始推理
samp_samples = sampler.common_ksampler(samp_model, samp_seed, steps, cfg, sampler_name, scheduler,
samp_positive, samp_negative, samp_samples, denoise=denoise,
preview_latent=False, start_step=start_step,
last_step=last_step, force_full_denoise=False,
disable_noise=False)
# 推理结束时间
end_time = int(time.time() * 1000)
stage_c = samp_samples["samples"]
results = None
if image_output not in ['Hide', 'Hide/Save']:
if decode_vae_name != 'None':
decode_vae = easyCache.load_vae(decode_vae_name)
else:
decode_vae = pipe['vae'][0]
samp_images = decode_vae.decode(stage_c).cpu()
results = easySave(samp_images, save_prefix, image_output, prompt, extra_pnginfo)
sampler.update_value_by_id("results", my_unique_id, results)
# 推理总耗时(包含解码)
end_decode_time = int(time.time() * 1000)
spent_time = '扩散:' + str((end_time - start_time) / 1000) + '秒, 解码:' + str(
(end_decode_time - end_time) / 1000) + '秒'
# Clean loaded_objects
easyCache.update_loaded_objects(prompt)
# zero_out
c1 = []
for t in samp_positive:
d = t[1].copy()
if "pooled_output" in d:
d["pooled_output"] = torch.zeros_like(d["pooled_output"])
n = [torch.zeros_like(t[0]), d]
c1.append(n)
# stage_b_conditioning
c2 = []
for t in c1:
d = t[1].copy()
d['stable_cascade_prior'] = stage_c
n = [t[0], d]
c2.append(n)
new_pipe = {
"model": pipe['model'][1],
"positive": c2,
"negative": c1,
"vae": pipe['vae'][1],
"clip": pipe['clip'],
"samples": samples_b,
"images": images,
"seed": seed,
"loader_settings": {
**pipe["loader_settings"],
"spent_time": spent_time
}
}
sampler.update_value_by_id("pipe_line", my_unique_id, new_pipe)
del pipe
if image_output in ("Hide", "Hide/Save"):
return {"ui": {},
"result": sampler.get_output(new_pipe, )}
if image_output in ("Sender", "Sender/Save") and results is not None:
PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results})
return {"ui": {"images": results}, "result": (new_pipe, new_pipe['model'], new_pipe['samples'])}
# 简易采样器Cascade
class samplerCascadeSimple:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {"required":
{"pipe": ("PIPE_LINE",),
"image_output": (["Hide", "Preview", "Save", "Hide/Save", "Sender", "Sender/Save"], {"default": "Preview"}),
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
"save_prefix": ("STRING", {"default": "ComfyUI"}),
},
"optional": {
"model_c": ("MODEL",),
},
"hidden":
{"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID",
"embeddingsList": (folder_paths.get_filename_list("embeddings"),)
}
}
RETURN_TYPES = ("PIPE_LINE", "IMAGE",)
RETURN_NAMES = ("pipe", "image",)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "EasyUse/Sampler"
def run(self, pipe, image_output, link_id, save_prefix, model_c=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False):
return samplerCascadeFull().run(pipe, None, None,None, None,None,None,None, image_output, link_id, save_prefix,
None, None, None, model_c, tile_size, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise)
class unsampler:
@classmethod
def INPUT_TYPES(s):
return {"required":{
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"end_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}),
"cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
"normalize": (["disable", "enable"],),
},
"optional": {
"pipe": ("PIPE_LINE",),
"optional_model": ("MODEL",),
"optional_positive": ("CONDITIONING",),
"optional_negative": ("CONDITIONING",),
"optional_latent": ("LATENT",),
}
}
RETURN_TYPES = ("PIPE_LINE", "LATENT",)
RETURN_NAMES = ("pipe", "latent",)
FUNCTION = "unsampler"
CATEGORY = "EasyUse/Sampler"
def unsampler(self, cfg, sampler_name, steps, end_at_step, scheduler, normalize, pipe=None, optional_model=None, optional_positive=None, optional_negative=None,
optional_latent=None):
model = optional_model if optional_model is not None else pipe["model"]
positive = optional_positive if optional_positive is not None else pipe["positive"]
negative = optional_negative if optional_negative is not None else pipe["negative"]
latent_image = optional_latent if optional_latent is not None else pipe["samples"]
normalize = normalize == "enable"
device = comfy.model_management.get_torch_device()
latent = latent_image
latent_image = latent["samples"]
end_at_step = min(end_at_step, steps - 1)
end_at_step = steps - end_at_step
noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, device="cpu")
noise_mask = None
if "noise_mask" in latent:
noise_mask = comfy.sample.prepare_mask(latent["noise_mask"], noise.shape, device)
noise = noise.to(device)
latent_image = latent_image.to(device)
_positive = comfy.sampler_helpers.convert_cond(positive)
_negative = comfy.sampler_helpers.convert_cond(negative)
models, inference_memory = comfy.sampler_helpers.get_additional_models({"positive": _positive, "negative": _negative}, model.model_dtype())
comfy.model_management.load_models_gpu([model] + models, model.memory_required(noise.shape) + inference_memory)
model_patcher = comfy.model_patcher.ModelPatcher(model.model, load_device=device, offload_device=comfy.model_management.unet_offload_device())
sampler = comfy.samplers.KSampler(model_patcher, steps=steps, device=device, sampler=sampler_name,
scheduler=scheduler, denoise=1.0, model_options=model.model_options)
sigmas = sampler.sigmas.flip(0) + 0.0001
pbar = comfy.utils.ProgressBar(steps)
def callback(step, x0, x, total_steps):
pbar.update_absolute(step + 1, total_steps)
samples = sampler.sample(noise, positive, negative, cfg=cfg, latent_image=latent_image,
force_full_denoise=False, denoise_mask=noise_mask, sigmas=sigmas, start_step=0,
last_step=end_at_step, callback=callback)
if normalize:
# technically doesn't normalize because unsampling is not guaranteed to end at a std given by the schedule
samples -= samples.mean()
samples /= samples.std()
samples = samples.cpu()
comfy.sample.cleanup_additional_models(models)
out = latent.copy()
out["samples"] = samples
if pipe is None:
pipe = {}
new_pipe = {
**pipe,
"samples": out
}
return (new_pipe, out,)
#---------------------------------------------------------------修复 开始----------------------------------------------------------------------#
# 高清修复
class hiresFix:
upscale_methods = ["nearest-exact", "bilinear", "area", "bicubic", "lanczos", "bislerp"]
crop_methods = ["disabled", "center"]
@classmethod
def INPUT_TYPES(s):
return {"required": {
"model_name": (folder_paths.get_filename_list("upscale_models"),),
"rescale_after_model": ([False, True], {"default": True}),
"rescale_method": (s.upscale_methods,),
"rescale": (["by percentage", "to Width/Height", 'to longer side - maintain aspect'],),
"percent": ("INT", {"default": 50, "min": 0, "max": 1000, "step": 1}),
"width": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}),
"height": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}),
"longer_side": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}),
"crop": (s.crop_methods,),
"image_output": (["Hide", "Preview", "Save", "Hide/Save", "Sender", "Sender/Save"],{"default": "Preview"}),
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
"save_prefix": ("STRING", {"default": "ComfyUI"}),
},
"optional": {
"pipe": ("PIPE_LINE",),
"image": ("IMAGE",),
"vae": ("VAE",),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID",
},
}
RETURN_TYPES = ("PIPE_LINE", "IMAGE", "LATENT", )
RETURN_NAMES = ('pipe', 'image', "latent", )
FUNCTION = "upscale"
CATEGORY = "EasyUse/Fix"
OUTPUT_NODE = True
def vae_encode_crop_pixels(self, pixels):
x = (pixels.shape[1] // 8) * 8
y = (pixels.shape[2] // 8) * 8
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, :]
return pixels
def upscale(self, model_name, rescale_after_model, rescale_method, rescale, percent, width, height,
longer_side, crop, image_output, link_id, save_prefix, pipe=None, image=None, vae=None, prompt=None,
extra_pnginfo=None, my_unique_id=None):
new_pipe = {}
if pipe is not None:
image = image if image is not None else pipe["images"]
vae = vae if vae is not None else pipe.get("vae")
elif image is None or vae is None:
raise ValueError("pipe or image or vae missing.")
# Load Model
model_path = folder_paths.get_full_path("upscale_models", model_name)
sd = comfy.utils.load_torch_file(model_path, safe_load=True)
upscale_model = model_loading.load_state_dict(sd).eval()
# Model upscale
device = comfy.model_management.get_torch_device()
upscale_model.to(device)
in_img = image.movedim(-1, -3).to(device)
tile = 128 + 64
overlap = 8
steps = in_img.shape[0] * comfy.utils.get_tiled_scale_steps(in_img.shape[3], in_img.shape[2], tile_x=tile,
tile_y=tile, overlap=overlap)
pbar = comfy.utils.ProgressBar(steps)
s = comfy.utils.tiled_scale(in_img, lambda a: upscale_model(a), tile_x=tile, tile_y=tile, overlap=overlap,
upscale_amount=upscale_model.scale, pbar=pbar)
upscale_model.cpu()
s = torch.clamp(s.movedim(-3, -1), min=0, max=1.0)
# Post Model Rescale
if rescale_after_model == True:
samples = s.movedim(-1, 1)
orig_height = samples.shape[2]
orig_width = samples.shape[3]
if rescale == "by percentage" and percent != 0:
height = percent / 100 * orig_height
width = percent / 100 * orig_width
if (width > MAX_RESOLUTION):
width = MAX_RESOLUTION
if (height > MAX_RESOLUTION):
height = MAX_RESOLUTION
width = easySampler.enforce_mul_of_64(width)
height = easySampler.enforce_mul_of_64(height)
elif rescale == "to longer side - maintain aspect":
longer_side = easySampler.enforce_mul_of_64(longer_side)
if orig_width > orig_height:
width, height = longer_side, easySampler.enforce_mul_of_64(longer_side * orig_height / orig_width)
else:
width, height = easySampler.enforce_mul_of_64(longer_side * orig_width / orig_height), longer_side
s = comfy.utils.common_upscale(samples, width, height, rescale_method, crop)
s = s.movedim(1, -1)
# vae encode
pixels = self.vae_encode_crop_pixels(s)
t = vae.encode(pixels[:, :, :, :3])
if pipe is not None:
new_pipe = {
"model": pipe['model'],
"positive": pipe['positive'],
"negative": pipe['negative'],
"vae": vae,
"clip": pipe['clip'],
"samples": {"samples": t},
"images": s,
"seed": pipe['seed'],
"loader_settings": {
**pipe["loader_settings"],
}
}
del pipe
else:
new_pipe = {}
results = easySave(s, save_prefix, image_output, prompt, extra_pnginfo)
if image_output in ("Sender", "Sender/Save"):
PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results})
if image_output in ("Hide", "Hide/Save"):
return (new_pipe, s, {"samples": t},)
return {"ui": {"images": results},
"result": (new_pipe, s, {"samples": t},)}
# 预细节修复
class preDetailerFix:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"pipe": ("PIPE_LINE",),
"guide_size": ("FLOAT", {"default": 256, "min": 64, "max": MAX_RESOLUTION, "step": 8}),
"guide_size_for": ("BOOLEAN", {"default": True, "label_on": "bbox", "label_off": "crop_region"}),
"max_size": ("FLOAT", {"default": 768, "min": 64, "max": MAX_RESOLUTION, "step": 8}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}),
"feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}),
"noise_mask": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
"force_inpaint": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
"drop_size": ("INT", {"min": 1, "max": MAX_RESOLUTION, "step": 1, "default": 10}),
"wildcard": ("STRING", {"multiline": True, "dynamicPrompts": False}),
"cycle": ("INT", {"default": 1, "min": 1, "max": 10, "step": 1}),
},
"optional": {
"bbox_segm_pipe": ("PIPE_LINE",),
"sam_pipe": ("PIPE_LINE",),
"optional_image": ("IMAGE",),
},
}
RETURN_TYPES = ("PIPE_LINE",)
RETURN_NAMES = ("pipe",)
OUTPUT_IS_LIST = (False,)
FUNCTION = "doit"
CATEGORY = "EasyUse/Fix"
def doit(self, pipe, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler, denoise, feather, noise_mask, force_inpaint, drop_size, wildcard, cycle, bbox_segm_pipe=None, sam_pipe=None, optional_image=None):
model = pipe["model"] if "model" in pipe else None
if model is None:
raise Exception(f"[ERROR] pipe['model'] is missing")
clip = pipe["clip"] if"clip" in pipe else None
if clip is None:
raise Exception(f"[ERROR] pipe['clip'] is missing")
vae = pipe["vae"] if "vae" in pipe else None
if vae is None:
raise Exception(f"[ERROR] pipe['vae'] is missing")
if optional_image is not None:
images = optional_image
else:
images = pipe["images"] if "images" in pipe else None
if images is None:
raise Exception(f"[ERROR] pipe['image'] is missing")
positive = pipe["positive"] if "positive" in pipe else None
if positive is None:
raise Exception(f"[ERROR] pipe['positive'] is missing")
negative = pipe["negative"] if "negative" in pipe else None
if negative is None:
raise Exception(f"[ERROR] pipe['negative'] is missing")
bbox_segm_pipe = bbox_segm_pipe or (pipe["bbox_segm_pipe"] if pipe and "bbox_segm_pipe" in pipe else None)
if bbox_segm_pipe is None:
raise Exception(f"[ERROR] bbox_segm_pipe or pipe['bbox_segm_pipe'] is missing")
sam_pipe = sam_pipe or (pipe["sam_pipe"] if pipe and "sam_pipe" in pipe else None)
if sam_pipe is None:
raise Exception(f"[ERROR] sam_pipe or pipe['sam_pipe'] is missing")
loader_settings = pipe["loader_settings"] if "loader_settings" in pipe else {}
new_pipe = {
"images": images,
"model": model,
"clip": clip,
"vae": vae,
"positive": positive,
"negative": negative,
"seed": seed,
"bbox_segm_pipe": bbox_segm_pipe,
"sam_pipe": sam_pipe,
"loader_settings": loader_settings,
"detail_fix_settings": {
"guide_size": guide_size,
"guide_size_for": guide_size_for,
"max_size": max_size,
"seed": seed,
"steps": steps,
"cfg": cfg,
"sampler_name": sampler_name,
"scheduler": scheduler,
"denoise": denoise,
"feather": feather,
"noise_mask": noise_mask,
"force_inpaint": force_inpaint,
"drop_size": drop_size,
"wildcard": wildcard,
"cycle": cycle
}
}
del bbox_segm_pipe
del sam_pipe
return (new_pipe,)
# 预遮罩细节修复
class preMaskDetailerFix:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"pipe": ("PIPE_LINE",),
"mask": ("MASK",),
"guide_size": ("FLOAT", {"default": 384, "min": 64, "max": MAX_RESOLUTION, "step": 8}),
"guide_size_for": ("BOOLEAN", {"default": True, "label_on": "bbox", "label_off": "crop_region"}),
"max_size": ("FLOAT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}),
"mask_mode": ("BOOLEAN", {"default": True, "label_on": "masked only", "label_off": "whole"}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}),
"feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}),
"crop_factor": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 10, "step": 0.1}),
"drop_size": ("INT", {"min": 1, "max": MAX_RESOLUTION, "step": 1, "default": 10}),
"refiner_ratio": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0}),
"batch_size": ("INT", {"default": 1, "min": 1, "max": 100}),
"cycle": ("INT", {"default": 1, "min": 1, "max": 10, "step": 1}),
},
"optional": {
# "patch": ("INPAINT_PATCH",),
"optional_image": ("IMAGE",),
"inpaint_model": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
"noise_mask_feather": ("INT", {"default": 20, "min": 0, "max": 100, "step": 1}),
},
}
RETURN_TYPES = ("PIPE_LINE",)
RETURN_NAMES = ("pipe",)
OUTPUT_IS_LIST = (False,)
FUNCTION = "doit"
CATEGORY = "EasyUse/Fix"
def doit(self, pipe, mask, guide_size, guide_size_for, max_size, mask_mode, seed, steps, cfg, sampler_name, scheduler, denoise, feather, crop_factor, drop_size,refiner_ratio, batch_size, cycle, optional_image=None, inpaint_model=False, noise_mask_feather=20):
model = pipe["model"] if "model" in pipe else None
if model is None:
raise Exception(f"[ERROR] pipe['model'] is missing")
clip = pipe["clip"] if"clip" in pipe else None
if clip is None:
raise Exception(f"[ERROR] pipe['clip'] is missing")
vae = pipe["vae"] if "vae" in pipe else None
if vae is None:
raise Exception(f"[ERROR] pipe['vae'] is missing")
if optional_image is not None:
images = optional_image
else:
images = pipe["images"] if "images" in pipe else None
if images is None:
raise Exception(f"[ERROR] pipe['image'] is missing")
positive = pipe["positive"] if "positive" in pipe else None
if positive is None:
raise Exception(f"[ERROR] pipe['positive'] is missing")
negative = pipe["negative"] if "negative" in pipe else None
if negative is None:
raise Exception(f"[ERROR] pipe['negative'] is missing")
latent = pipe["samples"] if "samples" in pipe else None
if latent is None:
raise Exception(f"[ERROR] pipe['samples'] is missing")
if 'noise_mask' not in latent:
if images is None:
raise Exception("No Images found")
if vae is None:
raise Exception("No VAE found")
x = (images.shape[1] // 8) * 8
y = (images.shape[2] // 8) * 8
mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])),
size=(images.shape[1], images.shape[2]), mode="bilinear")
pixels = images.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]
mask_erosion = mask
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)
latent = {"samples": t, "noise_mask": (mask_erosion[:, :, :x, :y].round())}
# when patch was linked
# if patch is not None:
# worker = InpaintWorker(node_name="easy kSamplerInpainting")
# model, = worker.patch(model, latent, patch)
loader_settings = pipe["loader_settings"] if "loader_settings" in pipe else {}
new_pipe = {
"images": images,
"model": model,
"clip": clip,
"vae": vae,
"positive": positive,
"negative": negative,
"seed": seed,
"mask": mask,
"loader_settings": loader_settings,
"detail_fix_settings": {
"guide_size": guide_size,
"guide_size_for": guide_size_for,
"max_size": max_size,
"seed": seed,
"steps": steps,
"cfg": cfg,
"sampler_name": sampler_name,
"scheduler": scheduler,
"denoise": denoise,
"feather": feather,
"crop_factor": crop_factor,
"drop_size": drop_size,
"refiner_ratio": refiner_ratio,
"batch_size": batch_size,
"cycle": cycle
},
"mask_settings": {
"mask_mode": mask_mode,
"inpaint_model": inpaint_model,
"noise_mask_feather": noise_mask_feather
}
}
del pipe
return (new_pipe,)
# 细节修复
class detailerFix:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"pipe": ("PIPE_LINE",),
"image_output": (["Hide", "Preview", "Save", "Hide/Save", "Sender", "Sender/Save"],{"default": "Preview"}),
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
"save_prefix": ("STRING", {"default": "ComfyUI"}),
},
"optional": {
"model": ("MODEL",),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", }
}
RETURN_TYPES = ("PIPE_LINE", "IMAGE", "IMAGE", "IMAGE")
RETURN_NAMES = ("pipe", "image", "cropped_refined", "cropped_enhanced_alpha")
OUTPUT_NODE = True
OUTPUT_IS_LIST = (False, False, True, True)
FUNCTION = "doit"
CATEGORY = "EasyUse/Fix"
def doit(self, pipe, image_output, link_id, save_prefix, model=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
# Clean loaded_objects
easyCache.update_loaded_objects(prompt)
my_unique_id = int(my_unique_id)
model = model or (pipe["model"] if "model" in pipe else None)
if model is None:
raise Exception(f"[ERROR] model or pipe['model'] is missing")
detail_fix_settings = pipe["detail_fix_settings"] if "detail_fix_settings" in pipe else None
if detail_fix_settings is None:
raise Exception(f"[ERROR] detail_fix_settings or pipe['detail_fix_settings'] is missing")
mask = pipe["mask"] if "mask" in pipe else None
image = pipe["images"]
clip = pipe["clip"]
vae = pipe["vae"]
seed = pipe["seed"]
positive = pipe["positive"]
negative = pipe["negative"]
loader_settings = pipe["loader_settings"] if "loader_settings" in pipe else {}
guide_size = pipe["detail_fix_settings"]["guide_size"] if "guide_size" in pipe["detail_fix_settings"] else 256
guide_size_for = pipe["detail_fix_settings"]["guide_size_for"] if "guide_size_for" in pipe[
"detail_fix_settings"] else True
max_size = pipe["detail_fix_settings"]["max_size"] if "max_size" in pipe["detail_fix_settings"] else 768
steps = pipe["detail_fix_settings"]["steps"] if "steps" in pipe["detail_fix_settings"] else 20
cfg = pipe["detail_fix_settings"]["cfg"] if "cfg" in pipe["detail_fix_settings"] else 1.0
sampler_name = pipe["detail_fix_settings"]["sampler_name"] if "sampler_name" in pipe[
"detail_fix_settings"] else None
scheduler = pipe["detail_fix_settings"]["scheduler"] if "scheduler" in pipe["detail_fix_settings"] else None
denoise = pipe["detail_fix_settings"]["denoise"] if "denoise" in pipe["detail_fix_settings"] else 0.5
feather = pipe["detail_fix_settings"]["feather"] if "feather" in pipe["detail_fix_settings"] else 5
crop_factor = pipe["detail_fix_settings"]["crop_factor"] if "crop_factor" in pipe["detail_fix_settings"] else 3.0
drop_size = pipe["detail_fix_settings"]["drop_size"] if "drop_size" in pipe["detail_fix_settings"] else 10
refiner_ratio = pipe["detail_fix_settings"]["refiner_ratio"] if "refiner_ratio" in pipe else 0.2
batch_size = pipe["detail_fix_settings"]["batch_size"] if "batch_size" in pipe["detail_fix_settings"] else 1
noise_mask = pipe["detail_fix_settings"]["noise_mask"] if "noise_mask" in pipe["detail_fix_settings"] else None
force_inpaint = pipe["detail_fix_settings"]["force_inpaint"] if "force_inpaint" in pipe["detail_fix_settings"] else False
wildcard = pipe["detail_fix_settings"]["wildcard"] if "wildcard" in pipe["detail_fix_settings"] else ""
cycle = pipe["detail_fix_settings"]["cycle"] if "cycle" in pipe["detail_fix_settings"] else 1
bbox_segm_pipe = pipe["bbox_segm_pipe"] if pipe and "bbox_segm_pipe" in pipe else None
sam_pipe = pipe["sam_pipe"] if "sam_pipe" in pipe else None
# 细节修复初始时间
start_time = int(time.time() * 1000)
if "mask_settings" in pipe:
mask_mode = pipe['mask_settings']["mask_mode"] if "inpaint_model" in pipe['mask_settings'] else True
inpaint_model = pipe['mask_settings']["inpaint_model"] if "inpaint_model" in pipe['mask_settings'] else False
noise_mask_feather = pipe['mask_settings']["noise_mask_feather"] if "noise_mask_feather" in pipe['mask_settings'] else 20
cls = ALL_NODE_CLASS_MAPPINGS["MaskDetailerPipe"]
if "MaskDetailerPipe" not in ALL_NODE_CLASS_MAPPINGS:
raise Exception(f"[ERROR] To use MaskDetailerPipe, you need to install 'Impact Pack'")
basic_pipe = (model, clip, vae, positive, negative)
result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, basic_pipe, refiner_basic_pipe_opt = cls().doit(image, mask, basic_pipe, guide_size, guide_size_for, max_size, mask_mode,
seed, steps, cfg, sampler_name, scheduler, denoise,
feather, crop_factor, drop_size, refiner_ratio, batch_size, cycle=1,
refiner_basic_pipe_opt=None, detailer_hook=None, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather)
result_mask = mask
result_cnet_images = ()
else:
if bbox_segm_pipe is None:
raise Exception(f"[ERROR] bbox_segm_pipe or pipe['bbox_segm_pipe'] is missing")
if sam_pipe is None:
raise Exception(f"[ERROR] sam_pipe or pipe['sam_pipe'] is missing")
bbox_detector_opt, bbox_threshold, bbox_dilation, bbox_crop_factor, segm_detector_opt = bbox_segm_pipe
sam_model_opt, sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion, sam_mask_hint_threshold, sam_mask_hint_use_negative = sam_pipe
if "FaceDetailer" not in ALL_NODE_CLASS_MAPPINGS:
raise Exception(f"[ERROR] To use FaceDetailer, you need to install 'Impact Pack'")
cls = ALL_NODE_CLASS_MAPPINGS["FaceDetailer"]
result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, result_mask, pipe, result_cnet_images = cls().doit(
image, model, clip, vae, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name,
scheduler,
positive, negative, denoise, feather, noise_mask, force_inpaint,
bbox_threshold, bbox_dilation, bbox_crop_factor,
sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion, sam_mask_hint_threshold,
sam_mask_hint_use_negative, drop_size, bbox_detector_opt, wildcard, cycle, sam_model_opt,
segm_detector_opt,
detailer_hook=None)
# 细节修复结束时间
end_time = int(time.time() * 1000)
spent_time = '细节修复:' + str((end_time - start_time) / 1000) + '秒'
results = easySave(result_img, save_prefix, image_output, prompt, extra_pnginfo)
sampler.update_value_by_id("results", my_unique_id, results)
# Clean loaded_objects
easyCache.update_loaded_objects(prompt)
new_pipe = {
"samples": None,
"images": result_img,
"model": model,
"clip": clip,
"vae": vae,
"seed": seed,
"positive": positive,
"negative": negative,
"wildcard": wildcard,
"bbox_segm_pipe": bbox_segm_pipe,
"sam_pipe": sam_pipe,
"loader_settings": {
**loader_settings,
"spent_time": spent_time
},
"detail_fix_settings": detail_fix_settings
}
if "mask_settings" in pipe:
new_pipe["mask_settings"] = pipe["mask_settings"]
sampler.update_value_by_id("pipe_line", my_unique_id, new_pipe)
del bbox_segm_pipe
del sam_pipe
del pipe
if image_output in ("Hide", "Hide/Save"):
return {"ui": {},
"result": (new_pipe, result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, result_mask, result_cnet_images )}
if image_output in ("Sender", "Sender/Save"):
PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results})
return {"ui": {"images": results}, "result": (new_pipe, result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, result_mask, result_cnet_images )}
class ultralyticsDetectorForDetailerFix:
@classmethod
def INPUT_TYPES(s):
bboxs = ["bbox/" + x for x in folder_paths.get_filename_list("ultralytics_bbox")]
segms = ["segm/" + x for x in folder_paths.get_filename_list("ultralytics_segm")]
return {"required":
{"model_name": (bboxs + segms,),
"bbox_threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
"bbox_dilation": ("INT", {"default": 10, "min": -512, "max": 512, "step": 1}),
"bbox_crop_factor": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 10, "step": 0.1}),
}
}
RETURN_TYPES = ("PIPE_LINE",)
RETURN_NAMES = ("bbox_segm_pipe",)
FUNCTION = "doit"
CATEGORY = "EasyUse/Fix"
def doit(self, model_name, bbox_threshold, bbox_dilation, bbox_crop_factor):
if 'UltralyticsDetectorProvider' not in ALL_NODE_CLASS_MAPPINGS:
raise Exception(f"[ERROR] To use UltralyticsDetectorProvider, you need to install 'Impact Pack'")
cls = ALL_NODE_CLASS_MAPPINGS['UltralyticsDetectorProvider']
bbox_detector, segm_detector = cls().doit(model_name)
pipe = (bbox_detector, bbox_threshold, bbox_dilation, bbox_crop_factor, segm_detector)
return (pipe,)
class samLoaderForDetailerFix:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model_name": (folder_paths.get_filename_list("sams"),),
"device_mode": (["AUTO", "Prefer GPU", "CPU"],{"default": "AUTO"}),
"sam_detection_hint": (
["center-1", "horizontal-2", "vertical-2", "rect-4", "diamond-4", "mask-area", "mask-points",
"mask-point-bbox", "none"],),
"sam_dilation": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}),
"sam_threshold": ("FLOAT", {"default": 0.93, "min": 0.0, "max": 1.0, "step": 0.01}),
"sam_bbox_expansion": ("INT", {"default": 0, "min": 0, "max": 1000, "step": 1}),
"sam_mask_hint_threshold": ("FLOAT", {"default": 0.7, "min": 0.0, "max": 1.0, "step": 0.01}),
"sam_mask_hint_use_negative": (["False", "Small", "Outter"],),
}
}
RETURN_TYPES = ("PIPE_LINE",)
RETURN_NAMES = ("sam_pipe",)
FUNCTION = "doit"
CATEGORY = "EasyUse/Fix"
def doit(self, model_name, device_mode, sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion, sam_mask_hint_threshold, sam_mask_hint_use_negative):
if 'SAMLoader' not in ALL_NODE_CLASS_MAPPINGS:
raise Exception(f"[ERROR] To use SAMLoader, you need to install 'Impact Pack'")
cls = ALL_NODE_CLASS_MAPPINGS['SAMLoader']
(sam_model,) = cls().load_model(model_name, device_mode)
pipe = (sam_model, sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion, sam_mask_hint_threshold, sam_mask_hint_use_negative)
return (pipe,)
#---------------------------------------------------------------Pipe 开始----------------------------------------------------------------------#
# pipeIn
class pipeIn:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {},
"optional": {
"pipe": ("PIPE_LINE",),
"model": ("MODEL",),
"pos": ("CONDITIONING",),
"neg": ("CONDITIONING",),
"latent": ("LATENT",),
"vae": ("VAE",),
"clip": ("CLIP",),
"image": ("IMAGE",),
"xyPlot": ("XYPLOT",),
},
"hidden": {"my_unique_id": "UNIQUE_ID"},
}
RETURN_TYPES = ("PIPE_LINE",)
RETURN_NAMES = ("pipe",)
FUNCTION = "flush"
CATEGORY = "EasyUse/Pipe"
def flush(self, pipe=None, model=None, pos=None, neg=None, latent=None, vae=None, clip=None, image=None, xyplot=None, my_unique_id=None):
model = model if model is not None else pipe.get("model")
if model is None:
log_node_warn(f'pipeIn[{my_unique_id}]', "Model missing from pipeLine")
pos = pos if pos is not None else pipe.get("positive")
if pos is None:
log_node_warn(f'pipeIn[{my_unique_id}]', "Pos Conditioning missing from pipeLine")
neg = neg if neg is not None else pipe.get("negative")
if neg is None:
log_node_warn(f'pipeIn[{my_unique_id}]', "Neg Conditioning missing from pipeLine")
vae = vae if vae is not None else pipe.get("vae")
if vae is None:
log_node_warn(f'pipeIn[{my_unique_id}]', "VAE missing from pipeLine")
clip = clip if clip is not None else pipe.get("clip")
if clip is None:
log_node_warn(f'pipeIn[{my_unique_id}]', "Clip missing from pipeLine")
if latent is not None:
samples = latent
elif image is None:
samples = pipe.get("samples") if pipe is not None else None
image = pipe.get("images") if pipe is not None else None
elif image is not None:
if pipe is None:
batch_size = 1
else:
batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1
samples = {"samples": vae.encode(image[:, :, :, :3])}
samples = RepeatLatentBatch().repeat(samples, batch_size)[0]
if pipe is None:
pipe = {"loader_settings": {"positive": "", "negative": "", "xyplot": None}}
xyplot = xyplot if xyplot is not None else pipe['loader_settings']['xyplot'] if xyplot in pipe['loader_settings'] else None
new_pipe = {
**pipe,
"model": model,
"positive": pos,
"negative": neg,
"vae": vae,
"clip": clip,
"samples": samples,
"images": image,
"seed": pipe.get('seed') if pipe is not None and "seed" in pipe else None,
"loader_settings": {
**pipe["loader_settings"],
"xyplot": xyplot
}
}
del pipe
return (new_pipe,)
# pipeOut
class pipeOut:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"pipe": ("PIPE_LINE",),
},
"hidden": {"my_unique_id": "UNIQUE_ID"},
}
RETURN_TYPES = ("PIPE_LINE", "MODEL", "CONDITIONING", "CONDITIONING", "LATENT", "VAE", "CLIP", "IMAGE", "INT",)
RETURN_NAMES = ("pipe", "model", "pos", "neg", "latent", "vae", "clip", "image", "seed",)
FUNCTION = "flush"
CATEGORY = "EasyUse/Pipe"
def flush(self, pipe, my_unique_id=None):
model = pipe.get("model")
pos = pipe.get("positive")
neg = pipe.get("negative")
latent = pipe.get("samples")
vae = pipe.get("vae")
clip = pipe.get("clip")
image = pipe.get("images")
seed = pipe.get("seed")
return pipe, model, pos, neg, latent, vae, clip, image, seed
# pipeEdit
class pipeEdit:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"clip_skip": ("INT", {"default": -1, "min": -24, "max": 0, "step": 1}),
"optional_positive": ("STRING", {"default": "", "multiline": True}),
"positive_token_normalization": (["none", "mean", "length", "length+mean"],),
"positive_weight_interpretation": (["comfy", "A1111", "comfy++", "compel", "fixed attention"],),
"optional_negative": ("STRING", {"default": "", "multiline": True}),
"negative_token_normalization": (["none", "mean", "length", "length+mean"],),
"negative_weight_interpretation": (["comfy", "A1111", "comfy++", "compel", "fixed attention"],),
"a1111_prompt_style": ("BOOLEAN", {"default": False}),
"conditioning_mode": (['replace', 'concat', 'combine', 'average', 'timestep'], {"default": "replace"}),
"average_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"old_cond_start": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"old_cond_end": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"new_cond_start": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"new_cond_end": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
},
"optional": {
"pipe": ("PIPE_LINE",),
"model": ("MODEL",),
"pos": ("CONDITIONING",),
"neg": ("CONDITIONING",),
"latent": ("LATENT",),
"vae": ("VAE",),
"clip": ("CLIP",),
"image": ("IMAGE",),
},
"hidden": {"my_unique_id": "UNIQUE_ID", "prompt":"PROMPT"},
}
RETURN_TYPES = ("PIPE_LINE", "MODEL", "CONDITIONING", "CONDITIONING", "LATENT", "VAE", "CLIP", "IMAGE")
RETURN_NAMES = ("pipe", "model", "pos", "neg", "latent", "vae", "clip", "image")
FUNCTION = "flush"
CATEGORY = "EasyUse/Pipe"
def flush(self, clip_skip, optional_positive, positive_token_normalization, positive_weight_interpretation, optional_negative, negative_token_normalization, negative_weight_interpretation, a1111_prompt_style, conditioning_mode, average_strength, old_cond_start, old_cond_end, new_cond_start, new_cond_end, pipe=None, model=None, pos=None, neg=None, latent=None, vae=None, clip=None, image=None, my_unique_id=None, prompt=None):
model = model if model is not None else pipe.get("model")
if model is None:
log_node_warn(f'pipeIn[{my_unique_id}]', "Model missing from pipeLine")
vae = vae if vae is not None else pipe.get("vae")
if vae is None:
log_node_warn(f'pipeIn[{my_unique_id}]', "VAE missing from pipeLine")
clip = clip if clip is not None else pipe.get("clip")
if clip is None:
log_node_warn(f'pipeIn[{my_unique_id}]', "Clip missing from pipeLine")
if image is None:
image = pipe.get("images") if pipe is not None else None
samples = latent if latent is not None else pipe.get("samples")
if samples is None:
log_node_warn(f'pipeIn[{my_unique_id}]', "Latent missing from pipeLine")
else:
batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1
samples = {"samples": vae.encode(image[:, :, :, :3])}
samples = RepeatLatentBatch().repeat(samples, batch_size)[0]
pipe_lora_stack = pipe.get("lora_stack") if pipe is not None and "lora_stack" in pipe else []
steps = pipe["loader_settings"]["steps"] if "steps" in pipe["loader_settings"] else 1
if pos is None and optional_positive != '':
pos, positive_wildcard_prompt, model, clip = prompt_to_cond('positive', model, clip, clip_skip,
pipe_lora_stack, optional_positive, positive_token_normalization,positive_weight_interpretation,
a1111_prompt_style, my_unique_id, prompt, easyCache, True, steps)
pos = set_cond(pipe['positive'], pos, conditioning_mode, average_strength, old_cond_start, old_cond_end, new_cond_start, new_cond_end)
pipe['loader_settings']['positive'] = positive_wildcard_prompt
pipe['loader_settings']['positive_token_normalization'] = positive_token_normalization
pipe['loader_settings']['positive_weight_interpretation'] = positive_weight_interpretation
if a1111_prompt_style:
pipe['loader_settings']['a1111_prompt_style'] = True
else:
pos = pipe.get("positive")
if pos is None:
log_node_warn(f'pipeIn[{my_unique_id}]', "Pos Conditioning missing from pipeLine")
if neg is None and optional_negative != '':
neg, negative_wildcard_prompt, model, clip = prompt_to_cond("negative", model, clip, clip_skip, pipe_lora_stack, optional_negative,
negative_token_normalization, negative_weight_interpretation,
a1111_prompt_style, my_unique_id, prompt, easyCache, True, steps)
neg = set_cond(pipe['negative'], neg, conditioning_mode, average_strength, old_cond_start, old_cond_end, new_cond_start, new_cond_end)
pipe['loader_settings']['negative'] = negative_wildcard_prompt
pipe['loader_settings']['negative_token_normalization'] = negative_token_normalization
pipe['loader_settings']['negative_weight_interpretation'] = negative_weight_interpretation
if a1111_prompt_style:
pipe['loader_settings']['a1111_prompt_style'] = True
else:
neg = pipe.get("negative")
if neg is None:
log_node_warn(f'pipeIn[{my_unique_id}]', "Neg Conditioning missing from pipeLine")
if pipe is None:
pipe = {"loader_settings": {"positive": "", "negative": "", "xyplot": None}}
new_pipe = {
**pipe,
"model": model,
"positive": pos,
"negative": neg,
"vae": vae,
"clip": clip,
"samples": samples,
"images": image,
"seed": pipe.get('seed') if pipe is not None and "seed" in pipe else None,
"loader_settings":{
**pipe["loader_settings"]
}
}
del pipe
return (new_pipe, model,pos, neg, latent, vae, clip, image)
# pipeToBasicPipe
class pipeToBasicPipe:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"pipe": ("PIPE_LINE",),
},
"hidden": {"my_unique_id": "UNIQUE_ID"},
}
RETURN_TYPES = ("BASIC_PIPE",)
RETURN_NAMES = ("basic_pipe",)
FUNCTION = "doit"
CATEGORY = "EasyUse/Pipe"
def doit(self, pipe, my_unique_id=None):
new_pipe = (pipe.get('model'), pipe.get('clip'), pipe.get('vae'), pipe.get('positive'), pipe.get('negative'))
del pipe
return (new_pipe,)
# pipeBatchIndex
class pipeBatchIndex:
@classmethod
def INPUT_TYPES(s):
return {"required": {"pipe": ("PIPE_LINE",),
"batch_index": ("INT", {"default": 0, "min": 0, "max": 63}),
"length": ("INT", {"default": 1, "min": 1, "max": 64}),
},
"hidden": {"my_unique_id": "UNIQUE_ID"},}
RETURN_TYPES = ("PIPE_LINE",)
RETURN_NAMES = ("pipe",)
FUNCTION = "doit"
CATEGORY = "EasyUse/Pipe"
def doit(self, pipe, batch_index, length, my_unique_id=None):
samples = pipe["samples"]
new_samples, = LatentFromBatch().frombatch(samples, batch_index, length)
new_pipe = {
**pipe,
"samples": new_samples
}
del pipe
return (new_pipe,)
# pipeXYPlot
class pipeXYPlot:
lora_list = ["None"] + folder_paths.get_filename_list("loras")
lora_strengths = {"min": -4.0, "max": 4.0, "step": 0.01}
token_normalization = ["none", "mean", "length", "length+mean"]
weight_interpretation = ["comfy", "A1111", "compel", "comfy++"]
loader_dict = {
"ckpt_name": folder_paths.get_filename_list("checkpoints"),
"vae_name": ["Baked-VAE"] + folder_paths.get_filename_list("vae"),
"clip_skip": {"min": -24, "max": -1, "step": 1},
"lora_name": lora_list,
"lora_model_strength": lora_strengths,
"lora_clip_strength": lora_strengths,
"positive": [],
"negative": [],
}
sampler_dict = {
"steps": {"min": 1, "max": 100, "step": 1},
"cfg": {"min": 0.0, "max": 100.0, "step": 1.0},
"sampler_name": comfy.samplers.KSampler.SAMPLERS,
"scheduler": comfy.samplers.KSampler.SCHEDULERS,
"denoise": {"min": 0.0, "max": 1.0, "step": 0.01},
"seed": {"min": 0, "max": MAX_SEED_NUM},
}
plot_dict = {**sampler_dict, **loader_dict}
plot_values = ["None", ]
plot_values.append("---------------------")
for k in sampler_dict:
plot_values.append(f'preSampling: {k}')
plot_values.append("---------------------")
for k in loader_dict:
plot_values.append(f'loader: {k}')
def __init__(self):
pass
rejected = ["None", "---------------------", "Nothing"]
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"grid_spacing": ("INT", {"min": 0, "max": 500, "step": 5, "default": 0, }),
"output_individuals": (["False", "True"], {"default": "False"}),
"flip_xy": (["False", "True"], {"default": "False"}),
"x_axis": (pipeXYPlot.plot_values, {"default": 'None'}),
"x_values": (
"STRING", {"default": '', "multiline": True, "placeholder": 'insert values seperated by "; "'}),
"y_axis": (pipeXYPlot.plot_values, {"default": 'None'}),
"y_values": (
"STRING", {"default": '', "multiline": True, "placeholder": 'insert values seperated by "; "'}),
},
"optional": {
"pipe": ("PIPE_LINE",)
},
"hidden": {
"plot_dict": (pipeXYPlot.plot_dict,),
},
}
RETURN_TYPES = ("PIPE_LINE",)
RETURN_NAMES = ("pipe",)
FUNCTION = "plot"
CATEGORY = "EasyUse/Pipe"
def plot(self, grid_spacing, output_individuals, flip_xy, x_axis, x_values, y_axis, y_values, pipe=None):
def clean_values(values):
original_values = values.split("; ")
cleaned_values = []
for value in original_values:
# Strip the semi-colon
cleaned_value = value.strip(';').strip()
if cleaned_value == "":
continue
# Try to convert the cleaned_value back to int or float if possible
try:
cleaned_value = int(cleaned_value)
except ValueError:
try:
cleaned_value = float(cleaned_value)
except ValueError:
pass
# Append the cleaned_value to the list
cleaned_values.append(cleaned_value)
return cleaned_values
if x_axis in self.rejected:
x_axis = "None"
x_values = []
else:
x_values = clean_values(x_values)
if y_axis in self.rejected:
y_axis = "None"
y_values = []
else:
y_values = clean_values(y_values)
if flip_xy == "True":
x_axis, y_axis = y_axis, x_axis
x_values, y_values = y_values, x_values
xy_plot = {"x_axis": x_axis,
"x_vals": x_values,
"y_axis": y_axis,
"y_vals": y_values,
"grid_spacing": grid_spacing,
"output_individuals": output_individuals}
if pipe is not None:
new_pipe = pipe
new_pipe['loader_settings'] = {
**pipe['loader_settings'],
"xyplot": xy_plot
}
del pipe
return (new_pipe, xy_plot,)
# pipeXYPlotAdvanced
class pipeXYPlotAdvanced:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"pipe": ("PIPE_LINE",),
"grid_spacing": ("INT", {"min": 0, "max": 500, "step": 5, "default": 0, }),
"output_individuals": (["False", "True"], {"default": "False"}),
"flip_xy": (["False", "True"], {"default": "False"}),
},
"optional": {
"X": ("X_Y",),
"Y": ("X_Y",),
},
"hidden": {"my_unique_id": "UNIQUE_ID"}
}
RETURN_TYPES = ("PIPE_LINE",)
RETURN_NAMES = ("pipe",)
FUNCTION = "plot"
CATEGORY = "EasyUse/Pipe"
def plot(self, pipe, grid_spacing, output_individuals, flip_xy, X=None, Y=None, my_unique_id=None):
if X != None:
x_axis = X.get('axis')
x_values = X.get('values')
else:
x_axis = "Nothing"
x_values = [""]
if Y != None:
y_axis = Y.get('axis')
y_values = Y.get('values')
else:
y_axis = "Nothing"
y_values = [""]
if pipe is not None:
new_pipe = pipe
positive = pipe["loader_settings"]["positive"] if "positive" in pipe["loader_settings"] else ""
negative = pipe["loader_settings"]["negative"] if "negative" in pipe["loader_settings"] else ""
if x_axis == 'advanced: ModelMergeBlocks':
models = X.get('models')
vae_use = X.get('vae_use')
if models is None:
raise Exception("models is not found")
new_pipe['loader_settings'] = {
**pipe['loader_settings'],
"models": models,
"vae_use": vae_use
}
if y_axis == 'advanced: ModelMergeBlocks':
models = Y.get('models')
vae_use = Y.get('vae_use')
if models is None:
raise Exception("models is not found")
new_pipe['loader_settings'] = {
**pipe['loader_settings'],
"models": models,
"vae_use": vae_use
}
if x_axis in ['advanced: Lora', 'advanced: Checkpoint']:
lora_stack = X.get('lora_stack')
_lora_stack = []
if lora_stack is not None:
for lora in lora_stack:
_lora_stack.append(
{"lora_name": lora[0], "model": pipe['model'], "clip": pipe['clip'], "model_strength": lora[1],
"clip_strength": lora[2]})
del lora_stack
x_values = "; ".join(x_values)
lora_stack = pipe['lora_stack'] + _lora_stack if 'lora_stack' in pipe else _lora_stack
new_pipe['loader_settings'] = {
**pipe['loader_settings'],
"lora_stack": lora_stack,
}
if y_axis in ['advanced: Lora', 'advanced: Checkpoint']:
lora_stack = Y.get('lora_stack')
_lora_stack = []
if lora_stack is not None:
for lora in lora_stack:
_lora_stack.append(
{"lora_name": lora[0], "model": pipe['model'], "clip": pipe['clip'], "model_strength": lora[1],
"clip_strength": lora[2]})
del lora_stack
y_values = "; ".join(y_values)
lora_stack = pipe['lora_stack'] + _lora_stack if 'lora_stack' in pipe else _lora_stack
new_pipe['loader_settings'] = {
**pipe['loader_settings'],
"lora_stack": lora_stack,
}
if x_axis == 'advanced: Seeds++ Batch':
if new_pipe['seed']:
value = x_values
x_values = []
for index in range(value):
x_values.append(str(new_pipe['seed'] + index))
x_values = "; ".join(x_values)
if y_axis == 'advanced: Seeds++ Batch':
if new_pipe['seed']:
value = y_values
y_values = []
for index in range(value):
y_values.append(str(new_pipe['seed'] + index))
y_values = "; ".join(y_values)
if x_axis == 'advanced: Positive Prompt S/R':
if positive:
x_value = x_values
x_values = []
for index, value in enumerate(x_value):
search_txt, replace_txt, replace_all = value
if replace_all:
txt = replace_txt if replace_txt is not None else positive
x_values.append(txt)
else:
txt = positive.replace(search_txt, replace_txt, 1) if replace_txt is not None else positive
x_values.append(txt)
x_values = "; ".join(x_values)
if y_axis == 'advanced: Positive Prompt S/R':
if positive:
y_value = y_values
y_values = []
for index, value in enumerate(y_value):
search_txt, replace_txt, replace_all = value
if replace_all:
txt = replace_txt if replace_txt is not None else positive
y_values.append(txt)
else:
txt = positive.replace(search_txt, replace_txt, 1) if replace_txt is not None else positive
y_values.append(txt)
y_values = "; ".join(y_values)
if x_axis == 'advanced: Negative Prompt S/R':
if negative:
x_value = x_values
x_values = []
for index, value in enumerate(x_value):
search_txt, replace_txt, replace_all = value
if replace_all:
txt = replace_txt if replace_txt is not None else negative
x_values.append(txt)
else:
txt = negative.replace(search_txt, replace_txt, 1) if replace_txt is not None else negative
x_values.append(txt)
x_values = "; ".join(x_values)
if y_axis == 'advanced: Negative Prompt S/R':
if negative:
y_value = y_values
y_values = []
for index, value in enumerate(y_value):
search_txt, replace_txt, replace_all = value
if replace_all:
txt = replace_txt if replace_txt is not None else negative
y_values.append(txt)
else:
txt = negative.replace(search_txt, replace_txt, 1) if replace_txt is not None else negative
y_values.append(txt)
y_values = "; ".join(y_values)
if "advanced: ControlNet" in x_axis:
x_value = x_values
x_values = []
cnet = []
for index, value in enumerate(x_value):
cnet.append(value)
x_values.append(str(index))
x_values = "; ".join(x_values)
new_pipe['loader_settings'] = {
**pipe['loader_settings'],
"cnet_stack": cnet,
}
if "advanced: ControlNet" in y_axis:
y_value = y_values
y_values = []
cnet = []
for index, value in enumerate(y_value):
cnet.append(value)
y_values.append(str(index))
y_values = "; ".join(y_values)
new_pipe['loader_settings'] = {
**pipe['loader_settings'],
"cnet_stack": cnet,
}
if "advanced: Pos Condition" in x_axis:
x_values = "; ".join(x_values)
cond = X.get('cond')
new_pipe['loader_settings'] = {
**pipe['loader_settings'],
"positive_cond_stack": cond,
}
if "advanced: Pos Condition" in y_axis:
y_values = "; ".join(y_values)
cond = Y.get('cond')
new_pipe['loader_settings'] = {
**pipe['loader_settings'],
"positive_cond_stack": cond,
}
if "advanced: Neg Condition" in x_axis:
x_values = "; ".join(x_values)
cond = X.get('cond')
new_pipe['loader_settings'] = {
**pipe['loader_settings'],
"negative_cond_stack": cond,
}
if "advanced: Neg Condition" in y_axis:
y_values = "; ".join(y_values)
cond = Y.get('cond')
new_pipe['loader_settings'] = {
**pipe['loader_settings'],
"negative_cond_stack": cond,
}
del pipe
return pipeXYPlot().plot(grid_spacing, output_individuals, flip_xy, x_axis, x_values, y_axis, y_values, new_pipe)
#---------------------------------------------------------------XY Inputs 开始----------------------------------------------------------------------#
def load_preset(filename):
path = os.path.join(RESOURCES_DIR, filename)
path = os.path.abspath(path)
preset_list = []
if os.path.exists(path):
with open(path, 'r') as file:
for line in file:
preset_list.append(line.strip())
return preset_list
else:
return []
def generate_floats(batch_count, first_float, last_float):
if batch_count > 1:
interval = (last_float - first_float) / (batch_count - 1)
values = [str(round(first_float + i * interval, 3)) for i in range(batch_count)]
else:
values = [str(first_float)] if batch_count == 1 else []
return "; ".join(values)
def generate_ints(batch_count, first_int, last_int):
if batch_count > 1:
interval = (last_int - first_int) / (batch_count - 1)
values = [str(int(first_int + i * interval)) for i in range(batch_count)]
else:
values = [str(first_int)] if batch_count == 1 else []
# values = list(set(values)) # Remove duplicates
# values.sort() # Sort in ascending order
return "; ".join(values)
# Seed++ Batch
class XYplot_SeedsBatch:
@classmethod
def INPUT_TYPES(cls):
return {"required": {
"batch_count": ("INT", {"default": 3, "min": 1, "max": 50}), },
}
RETURN_TYPES = ("X_Y",)
RETURN_NAMES = ("X or Y",)
FUNCTION = "xy_value"
CATEGORY = "EasyUse/XY Inputs"
def xy_value(self, batch_count):
axis = "advanced: Seeds++ Batch"
xy_values = {"axis": axis, "values": batch_count}
return (xy_values,)
# Step Values
class XYplot_Steps:
parameters = ["steps", "start_at_step", "end_at_step",]
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"target_parameter": (cls.parameters,),
"batch_count": ("INT", {"default": 3, "min": 0, "max": 50}),
"first_step": ("INT", {"default": 10, "min": 1, "max": 10000}),
"last_step": ("INT", {"default": 20, "min": 1, "max": 10000}),
"first_start_step": ("INT", {"default": 0, "min": 0, "max": 10000}),
"last_start_step": ("INT", {"default": 10, "min": 0, "max": 10000}),
"first_end_step": ("INT", {"default": 10, "min": 0, "max": 10000}),
"last_end_step": ("INT", {"default": 20, "min": 0, "max": 10000}),
}
}
RETURN_TYPES = ("X_Y",)
RETURN_NAMES = ("X or Y",)
FUNCTION = "xy_value"
CATEGORY = "EasyUse/XY Inputs"
def xy_value(self, target_parameter, batch_count, first_step, last_step, first_start_step, last_start_step,
first_end_step, last_end_step,):
axis, xy_first, xy_last = None, None, None
if target_parameter == "steps":
axis = "advanced: Steps"
xy_first = first_step
xy_last = last_step
elif target_parameter == "start_at_step":
axis = "advanced: StartStep"
xy_first = first_start_step
xy_last = last_start_step
elif target_parameter == "end_at_step":
axis = "advanced: EndStep"
xy_first = first_end_step
xy_last = last_end_step
values = generate_ints(batch_count, xy_first, xy_last)
return ({"axis": axis, "values": values},) if values is not None else (None,)
class XYplot_CFG:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"batch_count": ("INT", {"default": 3, "min": 0, "max": 50}),
"first_cfg": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0}),
"last_cfg": ("FLOAT", {"default": 9.0, "min": 0.0, "max": 100.0}),
}
}
RETURN_TYPES = ("X_Y",)
RETURN_NAMES = ("X or Y",)
FUNCTION = "xy_value"
CATEGORY = "EasyUse/XY Inputs"
def xy_value(self, batch_count, first_cfg, last_cfg):
axis = "advanced: CFG Scale"
values = generate_floats(batch_count, first_cfg, last_cfg)
return ({"axis": axis, "values": values},) if values else (None,)
# Step Values
class XYplot_Sampler_Scheduler:
parameters = ["sampler", "scheduler", "sampler & scheduler"]
@classmethod
def INPUT_TYPES(cls):
samplers = ["None"] + comfy.samplers.KSampler.SAMPLERS
schedulers = ["None"] + comfy.samplers.KSampler.SCHEDULERS
inputs = {
"required": {
"target_parameter": (cls.parameters,),
"input_count": ("INT", {"default": 1, "min": 1, "max": 30, "step": 1})
}
}
for i in range(1, 30 + 1):
inputs["required"][f"sampler_{i}"] = (samplers,)
inputs["required"][f"scheduler_{i}"] = (schedulers,)
return inputs
RETURN_TYPES = ("X_Y",)
RETURN_NAMES = ("X or Y",)
FUNCTION = "xy_value"
CATEGORY = "EasyUse/XY Inputs"
def xy_value(self, target_parameter, input_count, **kwargs):
axis, values, = None, None,
if target_parameter == "scheduler":
axis = "advanced: Scheduler"
schedulers = [kwargs.get(f"scheduler_{i}") for i in range(1, input_count + 1)]
values = [scheduler for scheduler in schedulers if scheduler != "None"]
elif target_parameter == "sampler":
axis = "advanced: Sampler"
samplers = [kwargs.get(f"sampler_{i}") for i in range(1, input_count + 1)]
values = [sampler for sampler in samplers if sampler != "None"]
else:
axis = "advanced: Sampler&Scheduler"
samplers = [kwargs.get(f"sampler_{i}") for i in range(1, input_count + 1)]
schedulers = [kwargs.get(f"scheduler_{i}") for i in range(1, input_count + 1)]
values = []
for sampler, scheduler in zip(samplers, schedulers):
sampler = sampler if sampler else 'None'
scheduler = scheduler if scheduler else 'None'
values.append(sampler +', '+ scheduler)
values = "; ".join(values)
return ({"axis": axis, "values": values},) if values else (None,)
class XYplot_Denoise:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"batch_count": ("INT", {"default": 3, "min": 0, "max": 50}),
"first_denoise": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.1}),
"last_denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.1}),
}
}
RETURN_TYPES = ("X_Y",)
RETURN_NAMES = ("X or Y",)
FUNCTION = "xy_value"
CATEGORY = "EasyUse/XY Inputs"
def xy_value(self, batch_count, first_denoise, last_denoise):
axis = "advanced: Denoise"
values = generate_floats(batch_count, first_denoise, last_denoise)
return ({"axis": axis, "values": values},) if values else (None,)
# PromptSR
class XYplot_PromptSR:
@classmethod
def INPUT_TYPES(cls):
inputs = {
"required": {
"target_prompt": (["positive", "negative"],),
"search_txt": ("STRING", {"default": "", "multiline": False}),
"replace_all_text": ("BOOLEAN", {"default": False}),
"replace_count": ("INT", {"default": 3, "min": 1, "max": 30 - 1}),
}
}
# Dynamically add replace_X inputs
for i in range(1, 30):
replace_key = f"replace_{i}"
inputs["required"][replace_key] = ("STRING", {"default": "", "multiline": False, "placeholder": replace_key})
return inputs
RETURN_TYPES = ("X_Y",)
RETURN_NAMES = ("X or Y",)
FUNCTION = "xy_value"
CATEGORY = "EasyUse/XY Inputs"
def xy_value(self, target_prompt, search_txt, replace_all_text, replace_count, **kwargs):
axis = None
if target_prompt == "positive":
axis = "advanced: Positive Prompt S/R"
elif target_prompt == "negative":
axis = "advanced: Negative Prompt S/R"
# Create base entry
values = [(search_txt, None, replace_all_text)]
if replace_count > 0:
# Append additional entries based on replace_count
values.extend([(search_txt, kwargs.get(f"replace_{i+1}"), replace_all_text) for i in range(replace_count)])
return ({"axis": axis, "values": values},) if values is not None else (None,)
# XYPlot Pos Condition
class XYplot_Positive_Cond:
@classmethod
def INPUT_TYPES(cls):
inputs = {
"optional": {
"positive_1": ("CONDITIONING",),
"positive_2": ("CONDITIONING",),
"positive_3": ("CONDITIONING",),
"positive_4": ("CONDITIONING",),
}
}
return inputs
RETURN_TYPES = ("X_Y",)
RETURN_NAMES = ("X or Y",)
FUNCTION = "xy_value"
CATEGORY = "EasyUse/XY Inputs"
def xy_value(self, positive_1=None, positive_2=None, positive_3=None, positive_4=None):
axis = "advanced: Pos Condition"
values = []
cond = []
# Create base entry
if positive_1 is not None:
values.append("0")
cond.append(positive_1)
if positive_2 is not None:
values.append("1")
cond.append(positive_2)
if positive_3 is not None:
values.append("2")
cond.append(positive_3)
if positive_4 is not None:
values.append("3")
cond.append(positive_4)
return ({"axis": axis, "values": values, "cond": cond},) if values is not None else (None,)
# XYPlot Neg Condition
class XYplot_Negative_Cond:
@classmethod
def INPUT_TYPES(cls):
inputs = {
"optional": {
"negative_1": ("CONDITIONING"),
"negative_2": ("CONDITIONING"),
"negative_3": ("CONDITIONING"),
"negative_4": ("CONDITIONING"),
}
}
return inputs
RETURN_TYPES = ("X_Y",)
RETURN_NAMES = ("X or Y",)
FUNCTION = "xy_value"
CATEGORY = "EasyUse/XY Inputs"
def xy_value(self, negative_1=None, negative_2=None, negative_3=None, negative_4=None):
axis = "advanced: Neg Condition"
values = []
cond = []
# Create base entry
if negative_1 is not None:
values.append(0)
cond.append(negative_1)
if negative_2 is not None:
values.append(1)
cond.append(negative_2)
if negative_3 is not None:
values.append(2)
cond.append(negative_3)
if negative_4 is not None:
values.append(3)
cond.append(negative_4)
return ({"axis": axis, "values": values, "cond": cond},) if values is not None else (None,)
# XYPlot Pos Condition List
class XYplot_Positive_Cond_List:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"positive": ("CONDITIONING",),
}
}
INPUT_IS_LIST = True
RETURN_TYPES = ("X_Y",)
RETURN_NAMES = ("X or Y",)
FUNCTION = "xy_value"
CATEGORY = "EasyUse/XY Inputs"
def xy_value(self, positive):
axis = "advanced: Pos Condition"
values = []
cond = []
for index, c in enumerate(positive):
values.append(str(index))
cond.append(c)
return ({"axis": axis, "values": values, "cond": cond},) if values is not None else (None,)
# XYPlot Neg Condition List
class XYplot_Negative_Cond_List:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"negative": ("CONDITIONING",),
}
}
INPUT_IS_LIST = True
RETURN_TYPES = ("X_Y",)
RETURN_NAMES = ("X or Y",)
FUNCTION = "xy_value"
CATEGORY = "EasyUse/XY Inputs"
def xy_value(self, negative):
axis = "advanced: Neg Condition"
values = []
cond = []
for index, c in enumerate(negative):
values.append(index)
cond.append(c)
return ({"axis": axis, "values": values, "cond": cond},) if values is not None else (None,)
# XY Plot: ControlNet
class XYplot_Control_Net:
parameters = ["strength", "start_percent", "end_percent"]
@classmethod
def INPUT_TYPES(cls):
def get_file_list(filenames):
return [file for file in filenames if file != "put_models_here.txt" and "lllite" not in file]
return {
"required": {
"control_net_name": (get_file_list(folder_paths.get_filename_list("controlnet")),),
"image": ("IMAGE",),
"target_parameter": (cls.parameters,),
"batch_count": ("INT", {"default": 3, "min": 1, "max": 30}),
"first_strength": ("FLOAT", {"default": 0.0, "min": 0.00, "max": 10.0, "step": 0.01}),
"last_strength": ("FLOAT", {"default": 1.0, "min": 0.00, "max": 10.0, "step": 0.01}),
"first_start_percent": ("FLOAT", {"default": 0.0, "min": 0.00, "max": 1.0, "step": 0.01}),
"last_start_percent": ("FLOAT", {"default": 1.0, "min": 0.00, "max": 1.0, "step": 0.01}),
"first_end_percent": ("FLOAT", {"default": 0.0, "min": 0.00, "max": 1.0, "step": 0.01}),
"last_end_percent": ("FLOAT", {"default": 1.0, "min": 0.00, "max": 1.0, "step": 0.01}),
"strength": ("FLOAT", {"default": 1.0, "min": 0.00, "max": 10.0, "step": 0.01}),
"start_percent": ("FLOAT", {"default": 0.0, "min": 0.00, "max": 1.0, "step": 0.01}),
"end_percent": ("FLOAT", {"default": 1.0, "min": 0.00, "max": 1.0, "step": 0.01}),
},
}
RETURN_TYPES = ("X_Y",)
RETURN_NAMES = ("X or Y",)
FUNCTION = "xy_value"
CATEGORY = "EasyUse/XY Inputs"
def xy_value(self, control_net_name, image, target_parameter, batch_count, first_strength, last_strength, first_start_percent,
last_start_percent, first_end_percent, last_end_percent, strength, start_percent, end_percent):
axis, = None,
values = []
if target_parameter == "strength":
axis = "advanced: ControlNetStrength"
values.append([(control_net_name, image, first_strength, start_percent, end_percent)])
strength_increment = (last_strength - first_strength) / (batch_count - 1) if batch_count > 1 else 0
for i in range(1, batch_count - 1):
values.append([(control_net_name, image, first_strength + i * strength_increment, start_percent,
end_percent)])
if batch_count > 1:
values.append([(control_net_name, image, last_strength, start_percent, end_percent)])
elif target_parameter == "start_percent":
axis = "advanced: ControlNetStart%"
percent_increment = (last_start_percent - first_start_percent) / (batch_count - 1) if batch_count > 1 else 0
values.append([(control_net_name, image, strength, first_start_percent, end_percent)])
for i in range(1, batch_count - 1):
values.append([(control_net_name, image, strength, first_start_percent + i * percent_increment,
end_percent)])
# Always add the last start_percent if batch_count is more than 1.
if batch_count > 1:
values.append((control_net_name, image, strength, last_start_percent, end_percent))
elif target_parameter == "end_percent":
axis = "advanced: ControlNetEnd%"
percent_increment = (last_end_percent - first_end_percent) / (batch_count - 1) if batch_count > 1 else 0
values.append([(control_net_name, image, image, strength, start_percent, first_end_percent)])
for i in range(1, batch_count - 1):
values.append([(control_net_name, image, strength, start_percent,
first_end_percent + i * percent_increment)])
if batch_count > 1:
values.append([(control_net_name, image, strength, start_percent, last_end_percent)])
return ({"axis": axis, "values": values},)
#Checkpoints
class XYplot_Checkpoint:
modes = ["Ckpt Names", "Ckpt Names+ClipSkip", "Ckpt Names+ClipSkip+VAE"]
@classmethod
def INPUT_TYPES(cls):
checkpoints = ["None"] + folder_paths.get_filename_list("checkpoints")
vaes = ["Baked VAE"] + folder_paths.get_filename_list("vae")
inputs = {
"required": {
"input_mode": (cls.modes,),
"ckpt_count": ("INT", {"default": 3, "min": 0, "max": 10, "step": 1}),
}
}
for i in range(1, 10 + 1):
inputs["required"][f"ckpt_name_{i}"] = (checkpoints,)
inputs["required"][f"clip_skip_{i}"] = ("INT", {"default": -1, "min": -24, "max": -1, "step": 1})
inputs["required"][f"vae_name_{i}"] = (vaes,)
inputs["optional"] = {
"optional_lora_stack": ("LORA_STACK",)
}
return inputs
RETURN_TYPES = ("X_Y",)
RETURN_NAMES = ("X or Y",)
FUNCTION = "xy_value"
CATEGORY = "EasyUse/XY Inputs"
def xy_value(self, input_mode, ckpt_count, **kwargs):
axis = "advanced: Checkpoint"
checkpoints = [kwargs.get(f"ckpt_name_{i}") for i in range(1, ckpt_count + 1)]
clip_skips = [kwargs.get(f"clip_skip_{i}") for i in range(1, ckpt_count + 1)]
vaes = [kwargs.get(f"vae_name_{i}") for i in range(1, ckpt_count + 1)]
# Set None for Clip Skip and/or VAE if not correct modes
for i in range(ckpt_count):
if "ClipSkip" not in input_mode:
clip_skips[i] = 'None'
if "VAE" not in input_mode:
vaes[i] = 'None'
# Extend each sub-array with lora_stack if it's not None
values = [checkpoint.replace(',', '*')+','+str(clip_skip)+','+vae.replace(',', '*') for checkpoint, clip_skip, vae in zip(checkpoints, clip_skips, vaes) if
checkpoint != "None"]
optional_lora_stack = kwargs.get("optional_lora_stack") if "optional_lora_stack" in kwargs else []
xy_values = {"axis": axis, "values": values, "lora_stack": optional_lora_stack}
return (xy_values,)
#Loras
class XYplot_Lora:
modes = ["Lora Names", "Lora Names+Weights"]
@classmethod
def INPUT_TYPES(cls):
loras = ["None"] + folder_paths.get_filename_list("loras")
inputs = {
"required": {
"input_mode": (cls.modes,),
"lora_count": ("INT", {"default": 3, "min": 0, "max": 10, "step": 1}),
"model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
}
}
for i in range(1, 10 + 1):
inputs["required"][f"lora_name_{i}"] = (loras,)
inputs["required"][f"model_str_{i}"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
inputs["required"][f"clip_str_{i}"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
inputs["optional"] = {
"optional_lora_stack": ("LORA_STACK",)
}
return inputs
RETURN_TYPES = ("X_Y",)
RETURN_NAMES = ("X or Y",)
FUNCTION = "xy_value"
CATEGORY = "EasyUse/XY Inputs"
def xy_value(self, input_mode, lora_count, model_strength, clip_strength, **kwargs):
axis = "advanced: Lora"
# Extract values from kwargs
loras = [kwargs.get(f"lora_name_{i}") for i in range(1, lora_count + 1)]
model_strs = [kwargs.get(f"model_str_{i}", model_strength) for i in range(1, lora_count + 1)]
clip_strs = [kwargs.get(f"clip_str_{i}", clip_strength) for i in range(1, lora_count + 1)]
# Use model_strength and clip_strength for the loras where values are not provided
if "Weights" not in input_mode:
for i in range(lora_count):
model_strs[i] = model_strength
clip_strs[i] = clip_strength
# Extend each sub-array with lora_stack if it's not None
values = [lora.replace(',', '*')+','+str(model_str)+','+str(clip_str) for lora, model_str, clip_str
in zip(loras, model_strs, clip_strs) if lora != "None"]
optional_lora_stack = kwargs.get("optional_lora_stack") if "optional_lora_stack" in kwargs else []
print(values)
xy_values = {"axis": axis, "values": values, "lora_stack": optional_lora_stack}
return (xy_values,)
# 模型叠加
class XYplot_ModelMergeBlocks:
@classmethod
def INPUT_TYPES(s):
checkpoints = folder_paths.get_filename_list("checkpoints")
vae = ["Use Model 1", "Use Model 2"] + folder_paths.get_filename_list("vae")
preset = ["Preset"] # 20
preset += load_preset("mmb-preset.txt")
preset += load_preset("mmb-preset.custom.txt")
default_vectors = "1,0,0; \n0,1,0; \n0,0,1; \n1,1,0; \n1,0,1; \n0,1,1; "
return {
"required": {
"ckpt_name_1": (checkpoints,),
"ckpt_name_2": (checkpoints,),
"vae_use": (vae, {"default": "Use Model 1"}),
"preset": (preset, {"default": "preset"}),
"values": ("STRING", {"default": default_vectors, "multiline": True, "placeholder": 'Support 2 methods:\n\n1.input, middle, out in same line and insert values seperated by "; "\n\n2.model merge block number seperated by ", " in same line and insert values seperated by "; "'}),
},
"hidden": {"my_unique_id": "UNIQUE_ID"}
}
RETURN_TYPES = ("X_Y",)
RETURN_NAMES = ("X or Y",)
FUNCTION = "xy_value"
CATEGORY = "EasyUse/XY Inputs"
def xy_value(self, ckpt_name_1, ckpt_name_2, vae_use, preset, values, my_unique_id=None):
axis = "advanced: ModelMergeBlocks"
if ckpt_name_1 is None:
raise Exception("ckpt_name_1 is not found")
if ckpt_name_2 is None:
raise Exception("ckpt_name_2 is not found")
models = (ckpt_name_1, ckpt_name_2)
xy_values = {"axis":axis, "values":values, "models":models, "vae_use": vae_use}
return (xy_values,)
# 显示推理时间
class showSpentTime:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"pipe": ("PIPE_LINE",),
"spent_time": ("INFO", {"default": '推理完成后将显示推理时间', "forceInput": False}),
},
"hidden": {
"unique_id": "UNIQUE_ID",
"extra_pnginfo": "EXTRA_PNGINFO",
},
}
FUNCTION = "notify"
OUTPUT_NODE = True
RETURN_TYPES = ()
RETURN_NAMES = ()
CATEGORY = "EasyUse/Util"
def notify(self, pipe, spent_time=None, unique_id=None, extra_pnginfo=None):
if unique_id and extra_pnginfo and "workflow" in extra_pnginfo:
workflow = extra_pnginfo["workflow"]
node = next((x for x in workflow["nodes"] if str(x["id"]) == unique_id), None)
if node:
spent_time = pipe['loader_settings']['spent_time'] if 'spent_time' in pipe['loader_settings'] else ''
node["widgets_values"] = [spent_time]
return {"ui": {"text": spent_time}, "result": {}}
# 显示加载器参数中的各种名称
class showLoaderSettingsNames:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"pipe": ("PIPE_LINE",),
"names": ("INFO", {"default": '', "forceInput": False}),
},
"hidden": {
"unique_id": "UNIQUE_ID",
"extra_pnginfo": "EXTRA_PNGINFO",
},
}
RETURN_TYPES = ("STRING", "STRING", "STRING",)
RETURN_NAMES = ("ckpt_name", "vae_name", "lora_name")
FUNCTION = "notify"
OUTPUT_NODE = True
CATEGORY = "EasyUse/Util"
def notify(self, pipe, names=None, unique_id=None, extra_pnginfo=None):
if unique_id and extra_pnginfo and "workflow" in extra_pnginfo:
workflow = extra_pnginfo["workflow"]
node = next((x for x in workflow["nodes"] if str(x["id"]) == unique_id), None)
if node:
ckpt_name = pipe['loader_settings']['ckpt_name'] if 'ckpt_name' in pipe['loader_settings'] else ''
vae_name = pipe['loader_settings']['vae_name'] if 'vae_name' in pipe['loader_settings'] else ''
lora_name = pipe['loader_settings']['lora_name'] if 'lora_name' in pipe['loader_settings'] else ''
if ckpt_name:
ckpt_name = os.path.basename(os.path.splitext(ckpt_name)[0])
if vae_name:
vae_name = os.path.basename(os.path.splitext(vae_name)[0])
if lora_name:
lora_name = os.path.basename(os.path.splitext(lora_name)[0])
names = "ckpt_name: " + ckpt_name + '\n' + "vae_name: " + vae_name + '\n' + "lora_name: " + lora_name
node["widgets_values"] = names
return {"ui": {"text": names}, "result": (ckpt_name, vae_name, lora_name)}
NODE_CLASS_MAPPINGS = {
# seed 随机种
"easy seed": easySeed,
"easy globalSeed": globalSeed,
# prompt 提示词
"easy positive": positivePrompt,
"easy negative": negativePrompt,
"easy wildcards": wildcardsPrompt,
"easy promptList": promptList,
"easy promptLine": promptLine,
"easy promptConcat": promptConcat,
"easy promptReplace": promptReplace,
"easy stylesSelector": stylesPromptSelector,
"easy portraitMaster": portraitMaster,
# loaders 加载器
"easy fullLoader": fullLoader,
"easy a1111Loader": a1111Loader,
"easy comfyLoader": comfyLoader,
"easy svdLoader": svdLoader,
"easy sv3dLoader": sv3DLoader,
"easy zero123Loader": zero123Loader,
"easy dynamiCrafterLoader": dynamiCrafterLoader,
"easy cascadeLoader": cascadeLoader,
"easy loraStack": loraStackLoader,
"easy controlnetLoader": controlnetSimple,
"easy controlnetLoaderADV": controlnetAdvanced,
"easy LLLiteLoader": LLLiteLoader,
# Adapter 适配器
"easy ipadapterApply": ipadapterApply,
"easy ipadapterApplyADV": ipadapterApplyAdvanced,
"easy ipadapterApplyEncoder": ipadapterApplyEncoder,
"easy ipadapterApplyEmbeds": ipadapterApplyEmbeds,
"easy ipadapterStyleComposition": ipadapterStyleComposition,
"easy instantIDApply": instantIDApply,
"easy instantIDApplyADV": instantIDApplyAdvanced,
# Inpaint 内补
"easy fooocusInpaintLoader": fooocusInpaintLoader,
# latent 潜空间
"easy latentNoisy": latentNoisy,
"easy latentCompositeMaskedWithCond": latentCompositeMaskedWithCond,
# preSampling 预采样处理
"easy preSampling": samplerSettings,
"easy preSamplingAdvanced": samplerSettingsAdvanced,
"easy preSamplingNoiseIn": samplerSettingsNoiseIn,
"easy preSamplingCustom": samplerCustomSettings,
"easy preSamplingSdTurbo": sdTurboSettings,
"easy preSamplingDynamicCFG": dynamicCFGSettings,
"easy preSamplingCascade": cascadeSettings,
"easy preSamplingLayerDiffusion": layerDiffusionSettings,
"easy preSamplingLayerDiffusionADDTL": layerDiffusionSettingsADDTL,
# kSampler k采样器
"easy fullkSampler": samplerFull,
"easy kSampler": samplerSimple,
"easy kSamplerTiled": samplerSimpleTiled,
"easy kSamplerLayerDiffusion": samplerSimpleLayerDiffusion,
"easy kSamplerInpainting": samplerSimpleInpainting,
"easy kSamplerDownscaleUnet": samplerSimpleDownscaleUnet,
"easy kSamplerSDTurbo": samplerSDTurbo,
"easy fullCascadeKSampler": samplerCascadeFull,
"easy cascadeKSampler": samplerCascadeSimple,
"easy unSampler": unsampler,
# fix 修复相关
"easy hiresFix": hiresFix,
"easy preDetailerFix": preDetailerFix,
"easy preMaskDetailerFix": preMaskDetailerFix,
"easy ultralyticsDetectorPipe": ultralyticsDetectorForDetailerFix,
"easy samLoaderPipe": samLoaderForDetailerFix,
"easy detailerFix": detailerFix,
# pipe 管道(节点束)
"easy pipeIn": pipeIn,
"easy pipeOut": pipeOut,
"easy pipeEdit": pipeEdit,
"easy pipeToBasicPipe": pipeToBasicPipe,
"easy pipeBatchIndex": pipeBatchIndex,
"easy XYPlot": pipeXYPlot,
"easy XYPlotAdvanced": pipeXYPlotAdvanced,
# XY Inputs
"easy XYInputs: Seeds++ Batch": XYplot_SeedsBatch,
"easy XYInputs: Steps": XYplot_Steps,
"easy XYInputs: CFG Scale": XYplot_CFG,
"easy XYInputs: Sampler/Scheduler": XYplot_Sampler_Scheduler,
"easy XYInputs: Denoise": XYplot_Denoise,
"easy XYInputs: Checkpoint": XYplot_Checkpoint,
"easy XYInputs: Lora": XYplot_Lora,
"easy XYInputs: ModelMergeBlocks": XYplot_ModelMergeBlocks,
"easy XYInputs: PromptSR": XYplot_PromptSR,
"easy XYInputs: ControlNet": XYplot_Control_Net,
"easy XYInputs: PositiveCond": XYplot_Positive_Cond,
"easy XYInputs: PositiveCondList": XYplot_Positive_Cond_List,
"easy XYInputs: NegativeCond": XYplot_Negative_Cond,
"easy XYInputs: NegativeCondList": XYplot_Negative_Cond_List,
# others 其他
"easy showSpentTime": showSpentTime,
"easy showLoaderSettingsNames": showLoaderSettingsNames,
# "easy imageRemoveBG": imageREMBG,
"dynamicThresholdingFull": dynamicThresholdingFull,
}
NODE_DISPLAY_NAME_MAPPINGS = {
# seed 随机种
"easy seed": "EasySeed",
"easy globalSeed": "EasyGlobalSeed",
# prompt 提示词
"easy positive": "Positive",
"easy negative": "Negative",
"easy wildcards": "Wildcards",
"easy promptList": "PromptList",
"easy promptLine": "PromptLine",
"easy promptConcat": "PromptConcat",
"easy promptReplace": "PromptReplace",
"easy stylesSelector": "Styles Selector",
"easy portraitMaster": "Portrait Master",
# loaders 加载器
"easy fullLoader": "EasyLoader (Full)",
"easy a1111Loader": "EasyLoader (A1111)",
"easy comfyLoader": "EasyLoader (Comfy)",
"easy svdLoader": "EasyLoader (SVD)",
"easy sv3dLoader": "EasyLoader (SV3D)",
"easy zero123Loader": "EasyLoader (Zero123)",
"easy dynamiCrafterLoader": "EasyLoader (DynamiCrafter)",
"easy cascadeLoader": "EasyCascadeLoader",
"easy loraStack": "EasyLoraStack",
"easy controlnetLoader": "EasyControlnet",
"easy controlnetLoaderADV": "EasyControlnet (Advanced)",
"easy LLLiteLoader": "EasyLLLite",
# Adapter 适配器
"easy ipadapterApply": "Easy Apply IPAdapter",
"easy ipadapterApplyADV": "Easy Apply IPAdapter (Advanced)",
"easy ipadapterStyleComposition": "Easy Apply IPAdapter (StyleComposition)",
"easy ipadapterApplyEncoder": "Easy Apply IPAdapter (Encoder)",
"easy ipadapterApplyEmbeds": "Easy Apply IPAdapter (Embeds)",
"easy instantIDApply": "Easy Apply InstantID",
"easy instantIDApplyADV": "Easy Apply InstantID (Advanced)",
# Inpaint 内补
"easy fooocusInpaintLoader": "Load Fooocus Inpaint",
# latent 潜空间
"easy latentNoisy": "LatentNoisy",
"easy latentCompositeMaskedWithCond": "LatentCompositeMaskedWithCond",
# preSampling 预采样处理
"easy preSampling": "PreSampling",
"easy preSamplingAdvanced": "PreSampling (Advanced)",
"easy preSamplingNoiseIn": "PreSampling (NoiseIn)",
"easy preSamplingCustom": "PreSampling (Custom)",
"easy preSamplingSdTurbo": "PreSampling (SDTurbo)",
"easy preSamplingDynamicCFG": "PreSampling (DynamicCFG)",
"easy preSamplingCascade": "PreSampling (Cascade)",
"easy preSamplingLayerDiffusion": "PreSampling (LayerDiffuse)",
"easy preSamplingLayerDiffusionADDTL": "PreSampling (LayerDiffuse ADDTL)",
# kSampler k采样器
"easy kSampler": "EasyKSampler",
"easy fullkSampler": "EasyKSampler (Full)",
"easy kSamplerTiled": "EasyKSampler (Tiled Decode)",
"easy kSamplerLayerDiffusion": "EasyKSampler (LayerDiffuse)",
"easy kSamplerInpainting": "EasyKSampler (Inpainting)",
"easy kSamplerDownscaleUnet": "EasyKsampler (Downscale Unet)",
"easy kSamplerSDTurbo": "EasyKSampler (SDTurbo)",
"easy cascadeKSampler": "EasyCascadeKsampler",
"easy fullCascadeKSampler": "EasyCascadeKsampler (Full)",
"easy unSampler": "EasyUnSampler",
# fix 修复相关
"easy hiresFix": "HiresFix",
"easy preDetailerFix": "PreDetailerFix",
"easy preMaskDetailerFix": "preMaskDetailerFix",
"easy ultralyticsDetectorPipe": "UltralyticsDetector (Pipe)",
"easy samLoaderPipe": "SAMLoader (Pipe)",
"easy detailerFix": "DetailerFix",
# pipe 管道(节点束)
"easy pipeIn": "Pipe In",
"easy pipeOut": "Pipe Out",
"easy pipeEdit": "Pipe Edit",
"easy pipeBatchIndex": "Pipe Batch Index",
"easy pipeToBasicPipe": "Pipe -> BasicPipe",
"easy XYPlot": "XY Plot",
"easy XYPlotAdvanced": "XY Plot Advanced",
# XY Inputs
"easy XYInputs: Seeds++ Batch": "XY Inputs: Seeds++ Batch //EasyUse",
"easy XYInputs: Steps": "XY Inputs: Steps //EasyUse",
"easy XYInputs: CFG Scale": "XY Inputs: CFG Scale //EasyUse",
"easy XYInputs: Sampler/Scheduler": "XY Inputs: Sampler/Scheduler //EasyUse",
"easy XYInputs: Denoise": "XY Inputs: Denoise //EasyUse",
"easy XYInputs: Checkpoint": "XY Inputs: Checkpoint //EasyUse",
"easy XYInputs: Lora": "XY Inputs: Lora //EasyUse",
"easy XYInputs: ModelMergeBlocks": "XY Inputs: ModelMergeBlocks //EasyUse",
"easy XYInputs: PromptSR": "XY Inputs: PromptSR //EasyUse",
"easy XYInputs: ControlNet": "XY Inputs: Controlnet //EasyUse",
"easy XYInputs: PositiveCond": "XY Inputs: PosCond //EasyUse",
"easy XYInputs: PositiveCondList": "XY Inputs: PosCondList //EasyUse",
"easy XYInputs: NegativeCond": "XY Inputs: NegCond //EasyUse",
"easy XYInputs: NegativeCondList": "XY Inputs: NegCondList //EasyUse",
# others 其他
"easy showSpentTime": "Show Spent Time",
"easy showLoaderSettingsNames": "Show Loader Settings Names",
"easy imageRemoveBG": "ImageRemoveBG",
"dynamicThresholdingFull": "DynamicThresholdingFull",
}