7227 lines
316 KiB
Python
7227 lines
316 KiB
Python
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, get_sd_version
|
||
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("mediapipe", [os.path.join(model_path, "mediapipe")], set(['.tflite','.pth']))
|
||
|
||
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, prompt, find1="", replace1="", find2="", replace2="", find3="", replace3=""):
|
||
|
||
prompt = prompt.replace(find1, replace1)
|
||
prompt = prompt.replace(find2, replace2)
|
||
prompt = prompt.replace(find3, replace3)
|
||
|
||
return (prompt,)
|
||
|
||
|
||
# 肖像大师
|
||
# 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 injectNoiseToLatent:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {"required": {
|
||
"strength": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 200.0, "step": 0.0001}),
|
||
"normalize": ("BOOLEAN", {"default": False}),
|
||
"average": ("BOOLEAN", {"default": False}),
|
||
},
|
||
"optional": {
|
||
"pipe_to_noise": ("PIPE_LINE",),
|
||
"image_to_latent": ("IMAGE",),
|
||
"latent": ("LATENT",),
|
||
"noise": ("LATENT",),
|
||
"mask": ("MASK",),
|
||
"mix_randn_amount": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1000.0, "step": 0.001}),
|
||
# "seed": ("INT", {"default": 123, "min": 0, "max": 0xffffffffffffffff, "step": 1}),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("LATENT",)
|
||
FUNCTION = "inject"
|
||
CATEGORY = "EasyUse/Latent"
|
||
|
||
def inject(self,strength, normalize, average, pipe_to_noise=None, noise=None, image_to_latent=None, latent=None, mix_randn_amount=0, mask=None):
|
||
|
||
vae = pipe_to_noise["vae"] if pipe_to_noise is not None else pipe_to_noise["vae"]
|
||
batch_size = pipe_to_noise["loader_settings"]["batch_size"] if pipe_to_noise is not None and "batch_size" in pipe_to_noise["loader_settings"] else 1
|
||
if noise is None and pipe_to_noise is not None:
|
||
noise = pipe_to_noise["samples"]
|
||
elif noise is None:
|
||
raise Exception("InjectNoiseToLatent: No noise provided")
|
||
|
||
if image_to_latent is not None and vae is not None:
|
||
samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])}
|
||
latents = RepeatLatentBatch().repeat(samples, batch_size)[0]
|
||
elif latent is not None:
|
||
latents = latent
|
||
else:
|
||
raise Exception("InjectNoiseToLatent: No input latent provided")
|
||
|
||
samples = latents.copy()
|
||
if latents["samples"].shape != noise["samples"].shape:
|
||
raise ValueError("InjectNoiseToLatent: Latent and noise must have the same shape")
|
||
if average:
|
||
noised = (samples["samples"].clone() + noise["samples"].clone()) / 2
|
||
else:
|
||
noised = samples["samples"].clone() + noise["samples"].clone() * strength
|
||
if normalize:
|
||
noised = noised / noised.std()
|
||
if mask is not None:
|
||
mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])),
|
||
size=(noised.shape[2], noised.shape[3]), mode="bilinear")
|
||
mask = mask.expand((-1, noised.shape[1], -1, -1))
|
||
if mask.shape[0] < noised.shape[0]:
|
||
mask = mask.repeat((noised.shape[0] - 1) // mask.shape[0] + 1, 1, 1, 1)[:noised.shape[0]]
|
||
noised = mask * noised + (1 - mask) * latents["samples"]
|
||
if mix_randn_amount > 0:
|
||
# if seed is not None:
|
||
# torch.manual_seed(seed)
|
||
rand_noise = torch.randn_like(noised)
|
||
noised = ((1 - mix_randn_amount) * noised + mix_randn_amount *
|
||
rand_noise) / ((mix_randn_amount ** 2 + (1 - mix_randn_amount) ** 2) ** 0.5)
|
||
samples["samples"] = noised
|
||
return (samples,)
|
||
|
||
|
||
# 随机种
|
||
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 setCkptName:
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
return {"required": {
|
||
"ckpt_name": (folder_paths.get_filename_list("checkpoints"),),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = (AlwaysEqualProxy('*'),)
|
||
RETURN_NAMES = ("ckpt_name",)
|
||
FUNCTION = "set_name"
|
||
CATEGORY = "EasyUse/Utils"
|
||
|
||
def set_name(self, ckpt_name):
|
||
return (ckpt_name,)
|
||
|
||
class setControlName:
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
return {"required": {
|
||
"controlnet_name": (folder_paths.get_filename_list("controlnet"),),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = (AlwaysEqualProxy('*'),)
|
||
RETURN_NAMES = ("controlnet_name",)
|
||
FUNCTION = "set_name"
|
||
CATEGORY = "EasyUse/Utils"
|
||
|
||
def set_name(self, controlnet_name):
|
||
return (controlnet_name,)
|
||
|
||
# 简易加载器完整
|
||
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),)
|
||
|
||
#---------------------------------------------------------------适配器 开始----------------------------------------------------------------------#
|
||
from .libs.styleAlign import styleAlignBatch, SHARE_NORM_OPTIONS, SHARE_ATTN_OPTIONS
|
||
class styleAlignedBatchAlign:
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
return {
|
||
"required": {
|
||
"model": ("MODEL",),
|
||
"share_norm": (SHARE_NORM_OPTIONS,),
|
||
"share_attn": (SHARE_ATTN_OPTIONS,),
|
||
"scale": ("FLOAT", {"default": 1, "min": 0, "max": 1.0, "step": 0.1}),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("MODEL",)
|
||
FUNCTION = "align"
|
||
CATEGORY = "EasyUse/Adapter"
|
||
|
||
def align(self, model, share_norm, share_attn, scale):
|
||
return (styleAlignBatch(model, share_norm, share_attn, scale),)
|
||
|
||
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", "images", "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):
|
||
images, 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, images, 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, images = 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, images = cls().apply_ipadapter(model, ipadapter, image, weight, start_at, end_at, weight_type='standard', attn_mask=attn_mask)
|
||
if images is None:
|
||
images = image
|
||
return (model, images, 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", "images", "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):
|
||
images, 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, images, 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, images = 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)
|
||
if images is None:
|
||
images = image
|
||
return (model, images, 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, image = 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, image = 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)
|
||
|
||
class ipadapterApplyRegional(ipadapter):
|
||
def __init__(self):
|
||
super().__init__()
|
||
pass
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
ipa_cls = cls()
|
||
weight_types = ipa_cls.weight_types
|
||
return {
|
||
"required": {
|
||
"pipe": ("PIPE_LINE",),
|
||
"image": ("IMAGE",),
|
||
"positive": ("STRING", {"default": "", "placeholder": "positive", "multiline": True}),
|
||
"negative": ("STRING", {"default": "", "placeholder": "negative", "multiline": True}),
|
||
"image_weight": ("FLOAT", {"default": 1.0, "min": -1.0, "max": 3.0, "step": 0.05}),
|
||
"prompt_weight": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "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}),
|
||
},
|
||
|
||
"optional": {
|
||
"mask": ("MASK",),
|
||
"optional_ipadapter_params": ("IPADAPTER_PARAMS",),
|
||
},
|
||
"hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"}
|
||
}
|
||
|
||
RETURN_TYPES = ("PIPE_LINE", "IPADAPTER_PARAMS", "CONDITIONING", "CONDITIONING")
|
||
RETURN_NAMES = ("pipe", "ipadapter_params", "positive", "negative")
|
||
CATEGORY = "EasyUse/Adapter"
|
||
FUNCTION = "apply"
|
||
|
||
def apply(self, pipe, image, positive, negative, image_weight, prompt_weight, weight_type, start_at, end_at, mask=None, optional_ipadapter_params=None, prompt=None, my_unique_id=None):
|
||
model = pipe['model']
|
||
clip = pipe['clip']
|
||
clip_skip = pipe['loader_settings']['clip_skip']
|
||
a1111_prompt_style = pipe['loader_settings']['a1111_prompt_style']
|
||
pipe_lora_stack = pipe['loader_settings']['lora_stack']
|
||
positive_token_normalization = pipe['loader_settings']['positive_token_normalization']
|
||
positive_weight_interpretation = pipe['loader_settings']['positive_weight_interpretation']
|
||
negative_token_normalization = pipe['loader_settings']['negative_token_normalization']
|
||
negative_weight_interpretation = pipe['loader_settings']['negative_weight_interpretation']
|
||
if positive == '':
|
||
positive = pipe['loader_settings']['positive']
|
||
if negative == '':
|
||
negative = pipe['loader_settings']['negative']
|
||
|
||
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)
|
||
|
||
#ipadapter regional
|
||
if "IPAdapterRegionalConditioning" not in ALL_NODE_CLASS_MAPPINGS:
|
||
self.error()
|
||
|
||
cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterRegionalConditioning"]
|
||
ipadapter_params, new_positive_embeds, new_negative_embeds = cls().conditioning(image, image_weight, prompt_weight, weight_type, start_at, end_at, mask=mask, positive=positive_embeddings_final, negative=negative_embeddings_final)
|
||
|
||
if optional_ipadapter_params is not None:
|
||
positive_embeds = pipe['positive'] + new_positive_embeds
|
||
negative_embeds = pipe['negative'] + new_negative_embeds
|
||
_ipadapter_params = {
|
||
"image": optional_ipadapter_params["image"] + ipadapter_params["image"],
|
||
"attn_mask": optional_ipadapter_params["attn_mask"] + ipadapter_params["attn_mask"],
|
||
"weight": optional_ipadapter_params["weight"] + ipadapter_params["weight"],
|
||
"weight_type": optional_ipadapter_params["weight_type"] + ipadapter_params["weight_type"],
|
||
"start_at": optional_ipadapter_params["start_at"] + ipadapter_params["start_at"],
|
||
"end_at": optional_ipadapter_params["end_at"] + ipadapter_params["end_at"],
|
||
}
|
||
ipadapter_params = _ipadapter_params
|
||
del _ipadapter_params
|
||
else:
|
||
positive_embeds = new_positive_embeds
|
||
negative_embeds = new_negative_embeds
|
||
|
||
new_pipe = {
|
||
**pipe,
|
||
"positive": positive_embeds,
|
||
"negative": negative_embeds,
|
||
}
|
||
|
||
del pipe
|
||
|
||
return (new_pipe, ipadapter_params, positive_embeds, negative_embeds)
|
||
|
||
class ipadapterApplyFromParams(ipadapter):
|
||
def __init__(self):
|
||
super().__init__()
|
||
pass
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
ipa_cls = cls()
|
||
normal_presets = ipa_cls.normal_presets
|
||
return {
|
||
"required": {
|
||
"model": ("MODEL",),
|
||
"preset": (normal_presets,),
|
||
"ipadapter_params": ("IPADAPTER_PARAMS",),
|
||
"combine_embeds": (["concat", "add", "subtract", "average", "norm average", "max", "min"],),
|
||
"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"},{"default":"none"}),
|
||
},
|
||
|
||
"optional": {
|
||
"optional_ipadapter": ("IPADAPTER",),
|
||
"image_negative": ("IMAGE",),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("MODEL", "IPADAPTER",)
|
||
RETURN_NAMES = ("model", "ipadapter", )
|
||
CATEGORY = "EasyUse/Adapter"
|
||
FUNCTION = "apply"
|
||
|
||
def apply(self, model, preset, ipadapter_params, combine_embeds, embeds_scaling, cache_mode, optional_ipadapter=None, image_negative=None,):
|
||
model, ipadapter = self.load_model(model, preset, 0, 'CPU', clip_vision=None, optional_ipadapter=optional_ipadapter, cache_mode=cache_mode)
|
||
if "IPAdapterFromParams" not in ALL_NODE_CLASS_MAPPINGS:
|
||
self.error()
|
||
cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterFromParams"]
|
||
model, image = cls().apply_ipadapter(model, ipadapter, clip_vision=None, combine_embeds=combine_embeds, embeds_scaling=embeds_scaling, image_negative=image_negative, ipadapter_params=ipadapter_params)
|
||
|
||
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 + ['align_your_steps'],),
|
||
"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 + ['align_your_steps'],),
|
||
"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+['align_your_steps'],),
|
||
"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,)
|
||
|
||
# 预采样设置(自定义)
|
||
import comfy_extras.nodes_custom_sampler as custom_samplers
|
||
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', 'alignYourSteps'],),
|
||
"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}),
|
||
"flip_sigmas": ("BOOLEAN", {"default": 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 get_custom_cls(self, sampler_name):
|
||
try:
|
||
cls = custom_samplers.__dict__[sampler_name]
|
||
print(cls)
|
||
return cls()
|
||
except:
|
||
raise Exception(f"Custom sampler {sampler_name} not found, Please updated your ComfyUI")
|
||
|
||
def settings(self, pipe, guider, cfg, cfg_negative, sampler_name, scheduler, steps, sigma_max, sigma_min, rho, beta_d, beta_min, eps_s, flip_sigmas, 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, = self.get_custom_cls('CFGGuider').get_guider(model, positive, negative, cfg)
|
||
elif guider in ['DualCFG', 'IP2P+DualCFG']:
|
||
_guider, = self.get_custom_cls('DualCFGGuider').get_guider(model, positive, negative, pipe['negative'], cfg, cfg_negative)
|
||
else:
|
||
_guider, = self.get_custom_cls('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, = self.get_custom_cls('KSamplerSelect').get_sampler(sampler_name)
|
||
|
||
# sigmas
|
||
if optional_sigmas:
|
||
sigmas = optional_sigmas
|
||
else:
|
||
if scheduler == 'vp':
|
||
sigmas, = self.get_custom_cls('VPScheduler').get_sigmas(steps, beta_d, beta_min, eps_s)
|
||
elif scheduler == 'karrasADV':
|
||
sigmas, = self.get_custom_cls('KarrasScheduler').get_sigmas(steps, sigma_max, sigma_min, rho)
|
||
elif scheduler == 'exponentialADV':
|
||
sigmas, = self.get_custom_cls('ExponentialScheduler').get_sigmas(steps, sigma_max, sigma_min)
|
||
elif scheduler == 'polyExponential':
|
||
sigmas, = self.get_custom_cls('PolyexponentialScheduler').get_sigmas(steps, sigma_max, sigma_min, rho)
|
||
elif scheduler == 'sdturbo':
|
||
sigmas, = self.get_custom_cls('SDTurboScheduler').get_sigmas(model, steps, denoise)
|
||
elif scheduler == 'alignYourSteps':
|
||
try:
|
||
from comfy_extras.nodes_align_your_steps import AlignYourStepsScheduler
|
||
model_type = get_sd_version(model)
|
||
if model_type == 'unknown':
|
||
raise Exception("This Model not supported")
|
||
sigmas, = AlignYourStepsScheduler().get_sigmas(model_type.upper(), steps)
|
||
except:
|
||
raise Exception("Please update your ComfyUI")
|
||
else:
|
||
sigmas, = self.get_custom_cls('BasicScheduler').get_sigmas(model, scheduler, steps, denoise)
|
||
|
||
# filp_sigmas
|
||
if flip_sigmas:
|
||
sigmas, = self.get_custom_cls('FlipSigmas').get_sigmas(sigmas)
|
||
# noise
|
||
if add_noise == 'disable':
|
||
noise, = self.get_custom_cls('DisableNoise').get_noise()
|
||
else:
|
||
noise, = self.get_custom_cls('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+ ['align_your_steps'], {"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,)
|
||
|
||
#---------------------------------------------------------------采样器 开始----------------------------------------------------------------------
|
||
|
||
# 完整采样器
|
||
from .libs.chooser import ChooserMessage, ChooserCancelled
|
||
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+['align_your_steps'],),
|
||
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||
"image_output": (["Hide", "Preview", "Preview&Choose", "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)
|
||
# 开始推理
|
||
if scheduler == 'align_your_steps' and samp_custom is None:
|
||
try:
|
||
from comfy_extras.nodes_align_your_steps import AlignYourStepsScheduler
|
||
model_type = get_sd_version(samp_model)
|
||
if model_type == 'unknown':
|
||
raise Exception("This Model not supported")
|
||
sigmas, = AlignYourStepsScheduler().get_sigmas(model_type.upper(), steps)
|
||
except:
|
||
raise Exception("Please update your ComfyUI")
|
||
_sampler = comfy.samplers.sampler_object(sampler_name)
|
||
samp_samples = sampler.custom_ksampler(samp_model, samp_seed, steps, cfg, _sampler, sigmas, samp_positive, samp_negative, samples, disable_noise=disable_noise, preview_latent=preview_latent)
|
||
else:
|
||
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 == 'Preview&Choose':
|
||
if my_unique_id not in ChooserMessage.stash:
|
||
ChooserMessage.stash[my_unique_id] = {}
|
||
my_stash = ChooserMessage.stash[my_unique_id]
|
||
|
||
PromptServer.instance.send_sync("easyuse-image-choose", {"id": my_unique_id, "urls": results})
|
||
# wait for selection
|
||
try:
|
||
selections = ChooserMessage.waitForMessage(my_unique_id, asList=True)
|
||
new_images = [new_images[x] for x in selections if x >= 0] if len(selections) > 1 else [0]
|
||
samp_images = [samp_images[x] for x in selections if x >= 0] if len(selections) > 1 else [0]
|
||
except ChooserCancelled:
|
||
raise comfy.model_management.InterruptProcessingException()
|
||
|
||
new_pipe['images'] = new_images
|
||
new_pipe['samp_images'] = samp_images
|
||
|
||
return {"ui": {"images": results},
|
||
"result": sampler.get_output(new_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", "Preview&Choose", "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)}
|
||
|
||
|
||
#---------------------------------------------------------------API 开始----------------------------------------------------------------------#
|
||
from .libs.stability import stableAPI
|
||
class stableDiffusion3API:
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {
|
||
"positive": ("STRING", {"default": "", "placeholder": "Positive", "multiline": True}),
|
||
"negative": ("STRING", {"default": "", "placeholder": "Negative", "multiline": True}),
|
||
"model": (["sd3", "sd3-turbo"],),
|
||
"aspect_ratio": (['16:9', '1:1', '21:9', '2:3', '3:2', '4:5', '5:4', '9:16', '9:21'],),
|
||
"seed": ("INT", {"default": 0, "min": 0, "max": 4294967294}),
|
||
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0}),
|
||
},
|
||
"optional": {
|
||
"optional_image": ("IMAGE",),
|
||
},
|
||
"hidden": {
|
||
"unique_id": "UNIQUE_ID",
|
||
"extra_pnginfo": "EXTRA_PNGINFO",
|
||
},
|
||
}
|
||
|
||
RETURN_TYPES = ("IMAGE",)
|
||
RETURN_NAMES = ("image",)
|
||
|
||
FUNCTION = "generate"
|
||
OUTPUT_NODE = False
|
||
|
||
CATEGORY = "EasyUse/API"
|
||
|
||
def generate(self, positive, negative, model, aspect_ratio, seed, denoise, optional_image=None, unique_id=None, extra_pnginfo=None):
|
||
mode = 'text-to-image'
|
||
if optional_image is not None:
|
||
mode = 'image-to-image'
|
||
output_image = stableAPI.generate_sd3_image(positive, negative, aspect_ratio, seed=seed, mode=mode, model=model, strength=denoise, image=optional_image)
|
||
return (output_image,)
|
||
|
||
|
||
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 ipadapterApplyRegional": ipadapterApplyRegional,
|
||
"easy ipadapterApplyFromParams": ipadapterApplyFromParams,
|
||
"easy ipadapterStyleComposition": ipadapterStyleComposition,
|
||
"easy instantIDApply": instantIDApply,
|
||
"easy instantIDApplyADV": instantIDApplyAdvanced,
|
||
"easy styleAlignedBatchAlign": styleAlignedBatchAlign,
|
||
# Inpaint 内补
|
||
"easy fooocusInpaintLoader": fooocusInpaintLoader,
|
||
# latent 潜空间
|
||
"easy latentNoisy": latentNoisy,
|
||
"easy latentCompositeMaskedWithCond": latentCompositeMaskedWithCond,
|
||
"easy injectNoiseToLatent": injectNoiseToLatent,
|
||
# 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,
|
||
"dynamicThresholdingFull": dynamicThresholdingFull,
|
||
# api 相关
|
||
"easy stableDiffusion3API": stableDiffusion3API,
|
||
# utils
|
||
"easy ckptNames": setCkptName,
|
||
"easy controlnetNames": setControlName,
|
||
}
|
||
|
||
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 ipadapterApplyRegional": "Easy Apply IPAdapter (Regional)",
|
||
"easy ipadapterApplyEmbeds": "Easy Apply IPAdapter (Embeds)",
|
||
"easy ipadapterApplyFromParams": "Easy Apply IPAdapter (From Params)",
|
||
"easy instantIDApply": "Easy Apply InstantID",
|
||
"easy instantIDApplyADV": "Easy Apply InstantID (Advanced)",
|
||
"easy styleAlignedBatchAlign": "Easy Apply StyleAlign",
|
||
# Inpaint 内补
|
||
"easy fooocusInpaintLoader": "Load Fooocus Inpaint",
|
||
# latent 潜空间
|
||
"easy latentNoisy": "LatentNoisy",
|
||
"easy latentCompositeMaskedWithCond": "LatentCompositeMaskedWithCond",
|
||
"easy injectNoiseToLatent": "InjectNoiseToLatent",
|
||
# 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",
|
||
"dynamicThresholdingFull": "DynamicThresholdingFull",
|
||
# api 相关
|
||
"easy stableDiffusion3API": "Stable Diffusion 3 (API)",
|
||
# utils
|
||
"easy ckptNames": "Ckpt Names",
|
||
"easy controlnetNames": "ControlNet Names",
|
||
} |