|
|
|
|
|
|
|
from comfy.sd import ModelPatcher, CLIP, VAE |
|
from nodes import common_ksampler, CLIPSetLastLayer |
|
|
|
from torch import Tensor |
|
from PIL import Image, ImageOps, ImageDraw, ImageFont |
|
from PIL.PngImagePlugin import PngInfo |
|
import numpy as np |
|
import torch |
|
|
|
import ast |
|
from pathlib import Path |
|
import os |
|
import sys |
|
import subprocess |
|
import json |
|
import folder_paths |
|
import psutil |
|
|
|
|
|
my_dir = os.path.dirname(os.path.abspath(__file__)) |
|
|
|
|
|
sys.path.append(my_dir) |
|
|
|
|
|
comfy_dir = os.path.abspath(os.path.join(my_dir, '..', '..')) |
|
|
|
|
|
sys.path.append(comfy_dir) |
|
|
|
|
|
font_path = os.path.join(my_dir, 'arial.ttf') |
|
|
|
|
|
import comfy.samplers |
|
import comfy.sd |
|
import comfy.utils |
|
|
|
|
|
from tsc_utils import * |
|
|
|
MAX_RESOLUTION=8192 |
|
|
|
|
|
|
|
class TSC_EfficientLoader: |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
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": -1, "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}), |
|
"positive": ("STRING", {"default": "Positive","multiline": True}), |
|
"negative": ("STRING", {"default": "Negative", "multiline": True}), |
|
"empty_latent_width": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 64}), |
|
"empty_latent_height": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 64}), |
|
"batch_size": ("INT", {"default": 1, "min": 1, "max": 64})}, |
|
"optional": {"lora_stack": ("LORA_STACK", )}, |
|
"hidden": { "prompt": "PROMPT", |
|
"my_unique_id": "UNIQUE_ID",}, |
|
} |
|
|
|
RETURN_TYPES = ("MODEL", "CONDITIONING", "CONDITIONING", "LATENT", "VAE", "CLIP", "DEPENDENCIES",) |
|
RETURN_NAMES = ("MODEL", "CONDITIONING+", "CONDITIONING-", "LATENT", "VAE", "CLIP", "DEPENDENCIES", ) |
|
FUNCTION = "efficientloader" |
|
CATEGORY = "Efficiency Nodes/Loaders" |
|
|
|
def efficientloader(self, ckpt_name, vae_name, clip_skip, lora_name, lora_model_strength, lora_clip_strength, |
|
positive, negative, empty_latent_width, empty_latent_height, batch_size, lora_stack=None, |
|
prompt=None, my_unique_id=None): |
|
|
|
model: ModelPatcher | None = None |
|
clip: CLIP | None = None |
|
vae: VAE | None = None |
|
|
|
|
|
latent = torch.zeros([batch_size, 4, empty_latent_height // 8, empty_latent_width // 8]).cpu() |
|
|
|
|
|
globals_cleanup(prompt) |
|
|
|
|
|
vae_cache, ckpt_cache, lora_cache = get_cache_numbers("Efficient Loader") |
|
|
|
if lora_name != "None": |
|
lora_params = [(lora_name, lora_model_strength, lora_clip_strength)] |
|
if lora_stack is not None: |
|
lora_params.extend(lora_stack) |
|
model, clip = load_lora(lora_params, ckpt_name, my_unique_id, cache=lora_cache, ckpt_cache=ckpt_cache, cache_overwrite=True) |
|
if vae_name == "Baked VAE": |
|
vae = get_bvae_by_ckpt_name(ckpt_name) |
|
else: |
|
model, clip, vae = load_checkpoint(ckpt_name, my_unique_id, cache=ckpt_cache, cache_overwrite=True) |
|
lora_params = None |
|
|
|
|
|
if vae_name != "Baked VAE": |
|
vae = load_vae(vae_name, my_unique_id, cache=vae_cache, cache_overwrite=True) |
|
|
|
|
|
|
|
|
|
|
|
if not clip: |
|
raise Exception("No CLIP found") |
|
clip = clip.clone() |
|
clip.clip_layer(clip_skip) |
|
|
|
|
|
dependencies = (vae_name, ckpt_name, clip, clip_skip, positive, negative, lora_params) |
|
|
|
return (model, [[clip.encode(positive), {}]], [[clip.encode(negative), {}]], {"samples":latent}, vae, clip, dependencies, ) |
|
|
|
|
|
|
|
class TSC_LoRA_Stacker: |
|
|
|
loras = ["None"] + folder_paths.get_filename_list("loras") |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"lora_name_1": (cls.loras,), |
|
"lora_wt_1": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), |
|
"lora_name_2": (cls.loras,), |
|
"lora_wt_2": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), |
|
"lora_name_3": (cls.loras,), |
|
"lora_wt_3": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})}, |
|
"optional": {"lora_stack": ("LORA_STACK",)}, |
|
} |
|
|
|
RETURN_TYPES = ("LORA_STACK",) |
|
RETURN_NAMES = ("LORA_STACK",) |
|
FUNCTION = "lora_stacker" |
|
CATEGORY = "Efficiency Nodes/Misc" |
|
|
|
def lora_stacker(self, lora_name_1, lora_wt_1, lora_name_2, lora_wt_2, lora_name_3, lora_wt_3, lora_stack=None): |
|
|
|
loras = [(lora_name, lora_wt, lora_wt) for lora_name, lora_wt, lora_wt in |
|
[(lora_name_1, lora_wt_1, lora_wt_1), |
|
(lora_name_2, lora_wt_2, lora_wt_2), |
|
(lora_name_3, lora_wt_3, lora_wt_3)] |
|
if lora_name != "None"] |
|
|
|
|
|
if lora_stack is not None: |
|
loras.extend([l for l in lora_stack if l[0] != "None"]) |
|
|
|
return (loras,) |
|
|
|
|
|
class TSC_LoRA_Stacker_Adv: |
|
|
|
loras = ["None"] + folder_paths.get_filename_list("loras") |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"lora_name_1": (cls.loras,), |
|
"model_str_1": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), |
|
"clip_str_1": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), |
|
"lora_name_2": (cls.loras,), |
|
"model_str_2": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), |
|
"clip_str_2": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), |
|
"lora_name_3": (cls.loras,), |
|
"model_str_3": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), |
|
"clip_str_3": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})}, |
|
"optional": {"lora_stack": ("LORA_STACK",)}, |
|
} |
|
|
|
RETURN_TYPES = ("LORA_STACK",) |
|
RETURN_NAMES = ("LORA_STACK",) |
|
FUNCTION = "lora_stacker" |
|
CATEGORY = "Efficiency Nodes/Misc" |
|
|
|
def lora_stacker(self, lora_name_1, model_str_1, clip_str_1, lora_name_2, model_str_2, clip_str_2, |
|
lora_name_3, model_str_3, clip_str_3, lora_stack=None): |
|
|
|
loras = [(lora_name, model_str, clip_str) for lora_name, model_str, clip_str in |
|
[(lora_name_1, model_str_1, clip_str_1), |
|
(lora_name_2, model_str_2, clip_str_2), |
|
(lora_name_3, model_str_3, clip_str_3)] |
|
if lora_name != "None"] |
|
|
|
|
|
if lora_stack is not None: |
|
loras.extend([l for l in lora_stack if l[0] != "None"]) |
|
|
|
return (loras,) |
|
|
|
|
|
|
|
class TSC_KSampler: |
|
|
|
empty_image = pil2tensor(Image.new('RGBA', (1, 1), (0, 0, 0, 0))) |
|
|
|
def __init__(self): |
|
self.output_dir = os.path.join(comfy_dir, 'temp') |
|
self.type = "temp" |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": |
|
{"sampler_state": (["Sample", "Hold", "Script"], ), |
|
"model": ("MODEL",), |
|
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), |
|
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}), |
|
"cfg": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0}), |
|
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,), |
|
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,), |
|
"positive": ("CONDITIONING",), |
|
"negative": ("CONDITIONING",), |
|
"latent_image": ("LATENT",), |
|
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), |
|
"preview_image": (["Disabled", "Enabled", "Output Only"],), |
|
}, |
|
"optional": { "optional_vae": ("VAE",), |
|
"script": ("SCRIPT",),}, |
|
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID",}, |
|
} |
|
|
|
RETURN_TYPES = ("MODEL", "CONDITIONING", "CONDITIONING", "LATENT", "VAE", "IMAGE", ) |
|
RETURN_NAMES = ("MODEL", "CONDITIONING+", "CONDITIONING-", "LATENT", "VAE", "IMAGE", ) |
|
OUTPUT_NODE = True |
|
FUNCTION = "sample" |
|
CATEGORY = "Efficiency Nodes/Sampling" |
|
|
|
def sample(self, sampler_state, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, |
|
latent_image, preview_image, denoise=1.0, prompt=None, extra_pnginfo=None, my_unique_id=None, |
|
optional_vae=(None,), script=None): |
|
|
|
|
|
def get_settings(): |
|
|
|
my_dir = os.path.dirname(os.path.abspath(__file__)) |
|
|
|
settings_file = os.path.join(my_dir, 'node_settings.json') |
|
|
|
with open(settings_file, 'r') as file: |
|
node_settings = json.load(file) |
|
|
|
kse_vae_tiled = node_settings.get("KSampler (Efficient)", {}).get('vae_tiled', False) |
|
xy_vae_tiled = node_settings.get("XY Plot", {}).get('vae_tiled', False) |
|
return kse_vae_tiled, xy_vae_tiled |
|
|
|
kse_vae_tiled, xy_vae_tiled = get_settings() |
|
|
|
|
|
def map_filename(filename): |
|
prefix_len = len(os.path.basename(filename_prefix)) |
|
prefix = filename[:prefix_len + 1] |
|
try: |
|
digits = int(filename[prefix_len + 1:].split('_')[0]) |
|
except: |
|
digits = 0 |
|
return (digits, prefix) |
|
|
|
def compute_vars(input): |
|
input = input.replace("%width%", str(images[0].shape[1])) |
|
input = input.replace("%height%", str(images[0].shape[0])) |
|
return input |
|
|
|
def preview_images(images, filename_prefix): |
|
filename_prefix = compute_vars(filename_prefix) |
|
|
|
subfolder = os.path.dirname(os.path.normpath(filename_prefix)) |
|
filename = os.path.basename(os.path.normpath(filename_prefix)) |
|
|
|
full_output_folder = os.path.join(self.output_dir, subfolder) |
|
|
|
try: |
|
counter = max(filter(lambda a: a[1][:-1] == filename and a[1][-1] == "_", |
|
map(map_filename, os.listdir(full_output_folder))))[0] + 1 |
|
except ValueError: |
|
counter = 1 |
|
except FileNotFoundError: |
|
os.makedirs(full_output_folder, exist_ok=True) |
|
counter = 1 |
|
|
|
if not os.path.exists(self.output_dir): |
|
os.makedirs(self.output_dir) |
|
|
|
results = list() |
|
for image in images: |
|
i = 255. * image.cpu().numpy() |
|
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) |
|
metadata = PngInfo() |
|
if prompt is not None: |
|
metadata.add_text("prompt", json.dumps(prompt)) |
|
if extra_pnginfo is not None: |
|
for x in extra_pnginfo: |
|
metadata.add_text(x, json.dumps(extra_pnginfo[x])) |
|
file = f"{filename}_{counter:05}_.png" |
|
img.save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=4) |
|
results.append({ |
|
"filename": file, |
|
"subfolder": subfolder, |
|
"type": self.type |
|
}); |
|
counter += 1 |
|
return results |
|
|
|
def get_value_by_id(key: str, my_unique_id): |
|
global last_helds |
|
for value, id_ in last_helds[key]: |
|
if id_ == my_unique_id: |
|
return value |
|
return None |
|
|
|
def update_value_by_id(key: str, my_unique_id, new_value): |
|
global last_helds |
|
|
|
for i, (value, id_) in enumerate(last_helds[key]): |
|
if id_ == my_unique_id: |
|
last_helds[key][i] = (new_value, id_) |
|
return True |
|
|
|
last_helds[key].append((new_value, my_unique_id)) |
|
return True |
|
|
|
|
|
globals_cleanup(prompt) |
|
|
|
|
|
my_unique_id = int(my_unique_id) |
|
|
|
|
|
vae = optional_vae |
|
if vae == (None,): |
|
print('\033[33mKSampler(Efficient) Warning:\033[0m No vae input detected, preview and output image disabled.\n') |
|
preview_image = "Disabled" |
|
|
|
|
|
if get_value_by_id("results", my_unique_id) is None: |
|
last_results = list() |
|
else: |
|
last_results = get_value_by_id("results", my_unique_id) |
|
|
|
|
|
if get_value_by_id("latent", my_unique_id) is None: |
|
last_latent = latent_image |
|
else: |
|
last_latent = {"samples": None} |
|
last_latent["samples"] = get_value_by_id("latent", my_unique_id) |
|
|
|
|
|
if get_value_by_id("images", my_unique_id) == None: |
|
last_images = TSC_KSampler.empty_image |
|
else: |
|
last_images = get_value_by_id("images", my_unique_id) |
|
|
|
|
|
latent: Tensor|None = None |
|
|
|
|
|
filename_prefix = "KSeff_{:02d}".format(my_unique_id) |
|
|
|
|
|
|
|
if sampler_state == "Sample": |
|
|
|
|
|
samples = common_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, |
|
latent_image, denoise=denoise) |
|
|
|
|
|
latent = samples[0]["samples"] |
|
|
|
|
|
update_value_by_id("latent", my_unique_id, latent) |
|
|
|
|
|
if preview_image == "Disabled": |
|
|
|
update_value_by_id("vae_decode", my_unique_id, True) |
|
return {"ui": {"images": list()}, |
|
"result": (model, positive, negative, {"samples": latent}, vae, TSC_KSampler.empty_image,)} |
|
else: |
|
|
|
if kse_vae_tiled == False: |
|
images = vae.decode(latent).cpu() |
|
else: |
|
images = vae.decode_tiled(latent).cpu() |
|
update_value_by_id("images", my_unique_id, images) |
|
|
|
|
|
update_value_by_id("vae_decode", my_unique_id, False) |
|
|
|
|
|
results = preview_images(images, filename_prefix) |
|
update_value_by_id("results", my_unique_id, results) |
|
|
|
|
|
images_value = list() if preview_image == "Output Only" else results |
|
|
|
|
|
return {"ui": {"images": images_value}, |
|
"result": (model, positive, negative, {"samples": latent}, vae, images,)} |
|
|
|
|
|
|
|
|
|
elif sampler_state == "Hold": |
|
|
|
|
|
if preview_image == "Disabled": |
|
return {"ui": {"images": list()}, |
|
"result": (model, positive, negative, last_latent, vae, TSC_KSampler.empty_image,)} |
|
|
|
else: |
|
latent = last_latent["samples"] |
|
|
|
if get_value_by_id("vae_decode", my_unique_id) == True: |
|
|
|
|
|
if kse_vae_tiled == False: |
|
images = vae.decode(latent).cpu() |
|
else: |
|
images = vae.decode_tiled(latent).cpu() |
|
update_value_by_id("images", my_unique_id, images) |
|
|
|
|
|
update_value_by_id("vae_decode", my_unique_id, False) |
|
|
|
|
|
results = preview_images(images, filename_prefix) |
|
update_value_by_id("results", my_unique_id, results) |
|
|
|
else: |
|
images = last_images |
|
results = last_results |
|
|
|
|
|
images_value = list() if preview_image == "Output Only" else results |
|
|
|
|
|
return {"ui": {"images": images_value}, |
|
"result": (model, positive, negative, {"samples": latent}, vae, images,)} |
|
|
|
|
|
elif sampler_state == "Script": |
|
|
|
|
|
script_node_name, script_node_id = extract_node_info(prompt, my_unique_id, 'script') |
|
|
|
|
|
if script == None or script == (None,) or script_node_name!="XY Plot": |
|
if script_node_name!="XY Plot": |
|
print('\033[31mKSampler(Efficient) Error:\033[0m No valid script input detected') |
|
return {"ui": {"images": list()}, |
|
"result": (model, positive, negative, last_latent, vae, last_images,)} |
|
|
|
|
|
if vae == (None,): |
|
print('\033[31mKSampler(Efficient) Error:\033[0m VAE must be connected to use Script mode.') |
|
return {"ui": {"images": list()}, |
|
"result": (model, positive, negative, last_latent, vae, last_images,)} |
|
|
|
|
|
if preview_image == "Disabled": |
|
print('\033[33mKSampler(Efficient) Warning:\033[0m The preview image cannot be disabled when running' |
|
' the XY Plot script, proceeding as if it was enabled.\n') |
|
|
|
|
|
image_tensors = torch.split(latent_image['samples'], 1, dim=0) |
|
|
|
|
|
shape = image_tensors[0].shape |
|
|
|
|
|
latent_height, latent_width = shape[2] * 8, shape[3] * 8 |
|
|
|
|
|
latent_image = {'samples': image_tensors[0]} |
|
|
|
|
|
|
|
if script_node_name == "XY Plot": |
|
|
|
|
|
vae_name = None |
|
ckpt_name = None |
|
clip = None |
|
lora_params = None |
|
positive_prompt = None |
|
negative_prompt = None |
|
clip_skip = None |
|
|
|
|
|
X_type, X_value, Y_type, Y_value, grid_spacing, Y_label_orientation, cache_models, xyplot_as_output_image,\ |
|
flip_xy, dependencies = script |
|
|
|
|
|
if dependencies is not None: |
|
vae_name, ckpt_name, clip, clip_skip, positive_prompt, negative_prompt, lora_params = dependencies |
|
|
|
|
|
def process_xy_for_print(value, replacement, type_): |
|
if isinstance(value, tuple) and type_ == "Scheduler": |
|
return value[0] |
|
elif isinstance(value, tuple): |
|
return tuple(replacement if v is None else v for v in value) |
|
else: |
|
return replacement if value is None else value |
|
|
|
|
|
replacement_X = scheduler if X_type == 'Sampler' else clip_skip if X_type == 'Checkpoint' else None |
|
replacement_Y = scheduler if Y_type == 'Sampler' else clip_skip if Y_type == 'Checkpoint' else None |
|
|
|
|
|
X_value_processed = [process_xy_for_print(v, replacement_X, X_type) for v in X_value] |
|
Y_value_processed = [process_xy_for_print(v, replacement_Y, Y_type) for v in Y_value] |
|
|
|
|
|
print("-" * 40) |
|
print("XY Plot Script Inputs:") |
|
print(f"(X) {X_type}: {X_value_processed}") |
|
print(f"(Y) {Y_type}: {Y_value_processed}") |
|
print("-" * 40) |
|
|
|
|
|
if cache_models == "False": |
|
vae_cache = ckpt_cache = lora_cache = 1 |
|
else: |
|
|
|
vae_cache, ckpt_cache, lora_cache = get_cache_numbers("XY Plot") |
|
|
|
cache = (vae_cache, ckpt_cache, lora_cache) |
|
|
|
|
|
positive_prompt = (positive_prompt, positive_prompt) |
|
negative_prompt = (negative_prompt, negative_prompt) |
|
|
|
|
|
|
|
|
|
|
|
dict_map = {"VAE": [], "Checkpoint": [], "LoRA": []} |
|
|
|
|
|
type_value_pairs = [(X_type, X_value), (Y_type, Y_value)] |
|
|
|
|
|
for t, v in type_value_pairs: |
|
if t in dict_map: |
|
|
|
if t == "LoRA": |
|
dict_map[t] = [item for sublist in v for item in sublist] |
|
else: |
|
dict_map[t] = v |
|
|
|
ckpt_dict = [t[0] for t in dict_map.get("Checkpoint", [])] if dict_map.get("Checkpoint", []) else [] |
|
|
|
lora_dict = [[t,] for t in dict_map.get("LoRA", [])] if dict_map.get("LoRA", []) else [] |
|
|
|
|
|
if ckpt_dict and lora_dict: |
|
lora_dict = [(lora_params, ckpt) for ckpt in ckpt_dict for lora_params in lora_dict] |
|
|
|
elif lora_dict: |
|
lora_dict = [(lora_params, ckpt_name) for lora_params in lora_dict] |
|
|
|
vae_dict = dict_map.get("VAE", []) |
|
|
|
|
|
if X_type == "LoRA": |
|
ckpt_dict = [] |
|
if X_type == "Checkpoint": |
|
lora_dict = [] |
|
|
|
|
|
|
|
|
|
|
|
clear_cache_by_exception(script_node_id, vae_dict=vae_dict, ckpt_dict=ckpt_dict, lora_dict=lora_dict) |
|
|
|
|
|
|
|
|
|
|
|
|
|
def define_variable(var_type, var, seed, steps, cfg, sampler_name, scheduler, denoise, vae_name, ckpt_name, |
|
clip_skip, positive_prompt, negative_prompt, lora_params, var_label, num_label): |
|
|
|
|
|
max_label_len = 36 |
|
|
|
|
|
if var_type == "Seeds++ Batch": |
|
text = f"Seed: {seed}" |
|
|
|
|
|
elif var_type == "Steps": |
|
steps = var |
|
text = f"steps: {steps}" |
|
|
|
|
|
elif var_type == "CFG Scale": |
|
cfg = var |
|
text = f"CFG: {round(cfg,2)}" |
|
|
|
|
|
elif var_type == "Sampler": |
|
sampler_name = var[0] |
|
if var[1] == "": |
|
text = f"{sampler_name}" |
|
else: |
|
if var[1] != None: |
|
scheduler = (var[1], scheduler[1]) |
|
else: |
|
scheduler = (scheduler[1], scheduler[1]) |
|
text = f"{sampler_name} ({scheduler[0]})" |
|
text = text.replace("ancestral", "a").replace("uniform", "u").replace("exponential","exp") |
|
|
|
|
|
elif var_type == "Scheduler": |
|
if len(var) == 2: |
|
scheduler = (var[0], scheduler[1]) |
|
text = f"{sampler_name} ({scheduler[0]})" |
|
else: |
|
scheduler = (var, scheduler[1]) |
|
text = f"{scheduler[0]}" |
|
text = text.replace("ancestral", "a").replace("uniform", "u").replace("exponential","exp") |
|
|
|
|
|
elif var_type == "Denoise": |
|
denoise = var |
|
text = f"denoise: {round(denoise, 2)}" |
|
|
|
|
|
elif var_type == "VAE": |
|
vae_name = var |
|
vae_filename = os.path.splitext(os.path.basename(vae_name))[0] |
|
text = f"VAE: {vae_filename}" |
|
|
|
|
|
elif var_type == "Positive Prompt S/R": |
|
search_txt, replace_txt = var |
|
if replace_txt != None: |
|
positive_prompt = (positive_prompt[1].replace(search_txt, replace_txt, 1), positive_prompt[1]) |
|
else: |
|
positive_prompt = (positive_prompt[1], positive_prompt[1]) |
|
replace_txt = search_txt |
|
text = f"{replace_txt}" |
|
|
|
|
|
elif var_type == "Negative Prompt S/R": |
|
search_txt, replace_txt = var |
|
if replace_txt: |
|
negative_prompt = (negative_prompt[1].replace(search_txt, replace_txt, 1), negative_prompt[1]) |
|
else: |
|
negative_prompt = (negative_prompt[1], negative_prompt[1]) |
|
replace_txt = search_txt |
|
text = f"(-) {replace_txt}" |
|
|
|
|
|
elif var_type == "Checkpoint": |
|
ckpt_name = var[0] |
|
if var[1] == None: |
|
clip_skip = (clip_skip[1],clip_skip[1]) |
|
else: |
|
clip_skip = (var[1],clip_skip[1]) |
|
ckpt_filename = os.path.splitext(os.path.basename(ckpt_name))[0] |
|
text = f"{ckpt_filename}" |
|
|
|
elif var_type == "Clip Skip": |
|
clip_skip = (var, clip_skip[1]) |
|
text = f"Clip Skip ({clip_skip[0]})" |
|
|
|
elif var_type == "LoRA": |
|
lora_params = var |
|
max_label_len = 30 + (12 * (len(lora_params)-1)) |
|
if len(lora_params) == 1: |
|
lora_name, lora_model_wt, lora_clip_wt = lora_params[0] |
|
lora_filename = os.path.splitext(os.path.basename(lora_name))[0] |
|
lora_model_wt = format(float(lora_model_wt), ".2f").rstrip('0').rstrip('.') |
|
lora_clip_wt = format(float(lora_clip_wt), ".2f").rstrip('0').rstrip('.') |
|
lora_filename = lora_filename[:max_label_len - len(f"LoRA: ({lora_model_wt})")] |
|
if lora_model_wt == lora_clip_wt: |
|
text = f"LoRA: {lora_filename}({lora_model_wt})" |
|
else: |
|
text = f"LoRA: {lora_filename}({lora_model_wt},{lora_clip_wt})" |
|
elif len(lora_params) > 1: |
|
lora_filenames = [os.path.splitext(os.path.basename(lora_name))[0] for lora_name, _, _ in lora_params] |
|
lora_details = [(format(float(lora_model_wt), ".2f").rstrip('0').rstrip('.'), |
|
format(float(lora_clip_wt), ".2f").rstrip('0').rstrip('.')) for _, lora_model_wt, lora_clip_wt in lora_params] |
|
non_name_length = sum(len(f"({lora_details[i][0]},{lora_details[i][1]})") + 2 for i in range(len(lora_params))) |
|
available_space = max_label_len - non_name_length |
|
max_name_length = available_space // len(lora_params) |
|
lora_filenames = [filename[:max_name_length] for filename in lora_filenames] |
|
text_elements = [f"{lora_filename}({lora_details[i][0]})" if lora_details[i][0] == lora_details[i][1] else f"{lora_filename}({lora_details[i][0]},{lora_details[i][1]})" for i, lora_filename in enumerate(lora_filenames)] |
|
text = " ".join(text_elements) |
|
|
|
def truncate_texts(texts, num_label, max_label_len): |
|
truncate_length = max(min(max(len(text) for text in texts), max_label_len), 24) |
|
|
|
return [text if len(text) <= truncate_length else text[:truncate_length] + "..." for text in |
|
texts] |
|
|
|
|
|
if len(var_label) < num_label: |
|
var_label.append(text) |
|
|
|
|
|
if len(var_label) == num_label and (var_type == "VAE" or var_type == "Checkpoint" or var_type == "LoRA"): |
|
var_label = truncate_texts(var_label, num_label, max_label_len) |
|
|
|
|
|
return steps, cfg, sampler_name, scheduler, denoise, vae_name, ckpt_name, clip_skip, \ |
|
positive_prompt, negative_prompt, lora_params, var_label |
|
|
|
|
|
|
|
def define_model(model, clip, positive, negative, positive_prompt, negative_prompt, clip_skip, vae, |
|
vae_name, ckpt_name, lora_params, index, types, script_node_id, cache): |
|
|
|
|
|
def encode_prompt(positive_prompt, negative_prompt, clip, clip_skip): |
|
clip = CLIPSetLastLayer().set_last_layer(clip, clip_skip)[0] |
|
return [[clip.encode(positive_prompt), {}]], [[clip.encode(negative_prompt), {}]] |
|
|
|
|
|
encode = False |
|
|
|
|
|
X_type, Y_type = types |
|
|
|
|
|
|
|
|
|
if (X_type == "VAE" and index == 0) or Y_type == "VAE": |
|
vae = load_vae(vae_name, script_node_id, cache=cache[0]) |
|
|
|
|
|
if (X_type == "Checkpoint" and index == 0 and Y_type != "LoRA"): |
|
if lora_params is None: |
|
model, clip, _ = load_checkpoint(ckpt_name, script_node_id, output_vae=False, cache=cache[1]) |
|
else: |
|
model, clip = load_lora(lora_params, ckpt_name, script_node_id, |
|
cache=None, ckpt_cache=cache[1]) |
|
encode = True |
|
|
|
|
|
elif (X_type == "LoRA" and index == 0): |
|
|
|
model, clip = load_lora(lora_params, ckpt_name, script_node_id, cache=cache[2]) |
|
encode = True |
|
|
|
elif Y_type == "LoRA": |
|
model, clip = load_lora(lora_params, ckpt_name, script_node_id, |
|
cache=None, ckpt_cache=cache[1]) |
|
encode = True |
|
|
|
|
|
prompt_types = ["Positive Prompt S/R", "Negative Prompt S/R", "Clip Skip"] |
|
if (X_type in prompt_types and index == 0) or Y_type in prompt_types: |
|
encode = True |
|
|
|
|
|
if encode == True: |
|
positive, negative = encode_prompt(positive_prompt[0], negative_prompt[0], clip, clip_skip) |
|
|
|
return model, positive, negative, vae |
|
|
|
|
|
|
|
def process_values(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, |
|
denoise, vae, latent_list=[], image_tensor_list=[], image_pil_list=[]): |
|
|
|
|
|
samples = common_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, |
|
latent_image, denoise=denoise) |
|
|
|
|
|
latent = samples[0]["samples"] |
|
|
|
|
|
latent_list.append(latent) |
|
|
|
|
|
if xy_vae_tiled == False: |
|
image = vae.decode(latent).cpu() |
|
else: |
|
image = vae.decode_tiled(latent).cpu() |
|
|
|
|
|
image_tensor_list.append(image) |
|
|
|
|
|
image_pil_list.append(tensor2pil(image)) |
|
|
|
|
|
return latent_list, image_tensor_list, image_pil_list |
|
|
|
|
|
|
|
|
|
|
|
X_label = [] |
|
Y_label = [] |
|
|
|
|
|
seed_updated = seed |
|
|
|
|
|
scheduler = (scheduler, scheduler) |
|
|
|
|
|
clip_skip = (clip_skip, clip_skip) |
|
|
|
|
|
types = (X_type, Y_type) |
|
|
|
|
|
for X_index, X in enumerate(X_value): |
|
|
|
|
|
if X_type == "Seeds++ Batch": |
|
|
|
seed_updated = seed + X_index |
|
|
|
|
|
steps, cfg, sampler_name, scheduler, denoise, vae_name, ckpt_name, clip_skip, positive_prompt, negative_prompt, \ |
|
lora_params, X_label = \ |
|
define_variable(X_type, X, seed_updated, steps, cfg, sampler_name, scheduler, denoise, vae_name, ckpt_name, |
|
clip_skip, positive_prompt, negative_prompt, lora_params, X_label, len(X_value)) |
|
|
|
if X_type != "Nothing" and Y_type == "Nothing": |
|
|
|
|
|
model, positive, negative , vae = \ |
|
define_model(model, clip, positive, negative, positive_prompt, negative_prompt, clip_skip[0], vae, |
|
vae_name, ckpt_name, lora_params, 0, types, script_node_id, cache) |
|
|
|
|
|
latent_list, image_tensor_list, image_pil_list = \ |
|
process_values(model, seed_updated, steps, cfg, sampler_name, scheduler[0], |
|
positive, negative, latent_image, denoise, vae) |
|
|
|
elif X_type != "Nothing" and Y_type != "Nothing": |
|
|
|
for Y_index, Y in enumerate(Y_value): |
|
|
|
if Y_type == "Seeds++ Batch": |
|
|
|
seed_updated = seed + Y_index |
|
|
|
|
|
steps, cfg, sampler_name, scheduler, denoise, vae_name, ckpt_name, clip_skip, positive_prompt, negative_prompt, lora_params, Y_label = \ |
|
define_variable(Y_type, Y, seed_updated, steps, cfg, sampler_name, scheduler, denoise, vae_name, ckpt_name, |
|
clip_skip, positive_prompt, negative_prompt, lora_params, Y_label, len(Y_value)) |
|
|
|
|
|
model, positive, negative, vae = \ |
|
define_model(model, clip, positive, negative, positive_prompt, negative_prompt, clip_skip[0], vae, |
|
vae_name, ckpt_name, lora_params, Y_index, types, script_node_id, cache) |
|
|
|
|
|
latent_list, image_tensor_list, image_pil_list = \ |
|
process_values(model, seed_updated, steps, cfg, sampler_name, scheduler[0], |
|
positive, negative, latent_image, denoise, vae) |
|
|
|
|
|
if cache_models == "False": |
|
clear_cache_by_exception(script_node_id, vae_dict=[], ckpt_dict=[], lora_dict=[]) |
|
|
|
else: |
|
|
|
if X_type == "LoRA": |
|
clear_cache_by_exception(script_node_id, ckpt_dict=[]) |
|
elif X_type == "Checkpoint": |
|
clear_cache_by_exception(script_node_id, lora_dict=[]) |
|
|
|
|
|
def print_plot_variables(X_type, Y_type, X_value, Y_value, seed, ckpt_name, lora_params, |
|
vae_name, clip_skip, steps, cfg, sampler_name, scheduler, denoise, |
|
num_rows, num_cols, latent_height, latent_width): |
|
|
|
print("-" * 40) |
|
print("\033[32mXY Plot Results:\033[0m") |
|
|
|
def get_vae_name(X_type, Y_type, X_value, Y_value, vae_name): |
|
if X_type == "VAE": |
|
vae_name = ", ".join(map(lambda x: os.path.splitext(os.path.basename(str(x)))[0], X_value)) |
|
elif Y_type == "VAE": |
|
vae_name = ", ".join(map(lambda y: os.path.splitext(os.path.basename(str(y)))[0], Y_value)) |
|
else: |
|
vae_name = os.path.splitext(os.path.basename(str(vae_name)))[0] |
|
return vae_name |
|
|
|
def get_clip_skip(X_type, Y_type, X_value, Y_value, clip_skip): |
|
if X_type == "Clip Skip": |
|
clip_skip = ", ".join(map(str, X_value)) |
|
elif Y_type == "Clip Skip": |
|
clip_skip = ", ".join(map(str, Y_value)) |
|
else: |
|
clip_skip = clip_skip[1] |
|
return clip_skip |
|
|
|
def get_checkpoint_name(ckpt_type, ckpt_values, clip_skip_type, clip_skip_values, ckpt_name, clip_skip): |
|
if ckpt_type == "Checkpoint": |
|
if clip_skip_type == "Clip Skip": |
|
ckpt_name = ", ".join([os.path.splitext(os.path.basename(str(ckpt[0])))[0] for ckpt in ckpt_values]) |
|
else: |
|
ckpt_name = ", ".join([f"{os.path.splitext(os.path.basename(str(ckpt[0])))[0]}({str(ckpt[1]) if ckpt[1] is not None else str(clip_skip_values)})" |
|
for ckpt in ckpt_values]) |
|
clip_skip = "_" |
|
else: |
|
ckpt_name = os.path.splitext(os.path.basename(str(ckpt_name)))[0] |
|
|
|
return ckpt_name, clip_skip |
|
|
|
def get_lora_name(X_type, Y_type, X_value, Y_value, lora_params=None): |
|
if X_type != "LoRA" and Y_type != "LoRA": |
|
if lora_params: |
|
return f"[{', '.join([f'{os.path.splitext(os.path.basename(name))[0]}({round(model_wt, 3)},{round(clip_wt, 3)})' for name, model_wt, clip_wt in lora_params])}]" |
|
else: |
|
return None |
|
else: |
|
return get_lora_sublist_name(X_type, |
|
X_value) if X_type == "LoRA" else get_lora_sublist_name(Y_type, Y_value) if Y_type == "LoRA" else None |
|
|
|
def get_lora_sublist_name(lora_type, lora_value): |
|
return ", ".join([ |
|
f"[{', '.join([f'{os.path.splitext(os.path.basename(str(x[0])))[0]}({round(x[1], 3)},{round(x[2], 3)})' for x in sublist])}]" |
|
for sublist in lora_value]) |
|
|
|
|
|
ckpt_type, clip_skip_type = (X_type, Y_type) if X_type in ["Checkpoint", "Clip Skip"] else (Y_type, X_type) |
|
ckpt_values, clip_skip_values = (X_value, Y_value) if X_type in ["Checkpoint", "Clip Skip"] else (Y_value, X_value) |
|
|
|
clip_skip = get_clip_skip(X_type, Y_type, X_value, Y_value, clip_skip) |
|
ckpt_name, clip_skip = get_checkpoint_name(ckpt_type, ckpt_values, clip_skip_type, clip_skip_values, ckpt_name, clip_skip) |
|
vae_name = get_vae_name(X_type, Y_type, X_value, Y_value, vae_name) |
|
lora_name = get_lora_name(X_type, Y_type, X_value, Y_value, lora_params) |
|
|
|
seed_list = [seed + x for x in X_value] if X_type == "Seeds++ Batch" else\ |
|
[seed + y for y in Y_value] if Y_type == "Seeds++ Batch" else [seed] |
|
seed = ", ".join(map(str, seed_list)) |
|
|
|
steps = ", ".join(map(str, X_value)) if X_type == "Steps" else ", ".join( |
|
map(str, Y_value)) if Y_type == "Steps" else steps |
|
|
|
cfg = ", ".join(map(str, X_value)) if X_type == "CFG Scale" else ", ".join( |
|
map(str, Y_value)) if Y_type == "CFG Scale" else cfg |
|
|
|
if X_type == "Sampler": |
|
if Y_type == "Scheduler": |
|
sampler_name = ", ".join([f"{x[0]}" for x in X_value]) |
|
scheduler = ", ".join([f"{y}" for y in Y_value]) |
|
else: |
|
sampler_name = ", ".join( |
|
[f"{x[0]}({x[1] if x[1] != '' and x[1] is not None else scheduler[1]})" for x in X_value]) |
|
scheduler = "_" |
|
elif Y_type == "Sampler": |
|
if X_type == "Scheduler": |
|
sampler_name = ", ".join([f"{y[0]}" for y in Y_value]) |
|
scheduler = ", ".join([f"{x}" for x in X_value]) |
|
else: |
|
sampler_name = ", ".join( |
|
[f"{y[0]}({y[1] if y[1] != '' and y[1] is not None else scheduler[1]})" for y in Y_value]) |
|
scheduler = "_" |
|
else: |
|
scheduler = ", ".join([str(x[0]) if isinstance(x, tuple) else str(x) for x in X_value]) if X_type == "Scheduler" else \ |
|
", ".join([str(y[0]) if isinstance(y, tuple) else str(y) for y in Y_value]) if Y_type == "Scheduler" else scheduler[0] |
|
|
|
denoise = ", ".join(map(str, X_value)) if X_type == "Denoise" else ", ".join( |
|
map(str, Y_value)) if Y_type == "Denoise" else denoise |
|
|
|
|
|
print(f"img_count: {len(X_value)*len(Y_value)}") |
|
print(f"img_dims: {latent_height} x {latent_width}") |
|
print(f"plot_dim: {num_cols} x {num_rows}") |
|
if clip_skip == "_": |
|
print(f"ckpt(clipskip): {ckpt_name if ckpt_name is not None else ''}") |
|
else: |
|
print(f"ckpt: {ckpt_name if ckpt_name is not None else ''}") |
|
print(f"clip_skip: {clip_skip if clip_skip is not None else ''}") |
|
if lora_name: |
|
print(f"lora(mod,clip): {lora_name if lora_name is not None else ''}") |
|
print(f"vae: {vae_name if vae_name is not None else ''}") |
|
print(f"seed: {seed}") |
|
print(f"steps: {steps}") |
|
print(f"cfg: {cfg}") |
|
if scheduler == "_": |
|
print(f"sampler(schr): {sampler_name}") |
|
else: |
|
print(f"sampler: {sampler_name}") |
|
print(f"scheduler: {scheduler}") |
|
print(f"denoise: {denoise}") |
|
|
|
if X_type == "Positive Prompt S/R" or Y_type == "Positive Prompt S/R": |
|
positive_prompt = ", ".join([str(x[0]) if i == 0 else str(x[1]) for i, x in enumerate( |
|
X_value)]) if X_type == "Positive Prompt S/R" else ", ".join( |
|
[str(y[0]) if i == 0 else str(y[1]) for i, y in |
|
enumerate(Y_value)]) if Y_type == "Positive Prompt S/R" else positive_prompt |
|
print(f"+prompt_s/r: {positive_prompt}") |
|
|
|
if X_type == "Negative Prompt S/R" or Y_type == "Negative Prompt S/R": |
|
negative_prompt = ", ".join([str(x[0]) if i == 0 else str(x[1]) for i, x in enumerate( |
|
X_value)]) if X_type == "Negative Prompt S/R" else ", ".join( |
|
[str(y[0]) if i == 0 else str(y[1]) for i, y in |
|
enumerate(Y_value)]) if Y_type == "Negative Prompt S/R" else negative_prompt |
|
print(f"-prompt_s/r: {negative_prompt}") |
|
|
|
|
|
def adjusted_font_size(text, initial_font_size, latent_width): |
|
font = ImageFont.truetype(str(Path(font_path)), initial_font_size) |
|
text_width = font.getlength(text) |
|
|
|
if text_width > (latent_width * 0.9): |
|
scaling_factor = 0.9 |
|
new_font_size = int(initial_font_size * (latent_width / text_width) * scaling_factor) |
|
else: |
|
new_font_size = initial_font_size |
|
|
|
return new_font_size |
|
|
|
|
|
|
|
|
|
update_value_by_id("vae_decode", my_unique_id, False) |
|
|
|
def rearrange_list_A(arr, num_cols, num_rows): |
|
new_list = [] |
|
for i in range(num_rows): |
|
for j in range(num_cols): |
|
index = j * num_rows + i |
|
new_list.append(arr[index]) |
|
return new_list |
|
|
|
def rearrange_list_B(arr, num_rows, num_cols): |
|
new_list = [] |
|
for i in range(num_rows): |
|
for j in range(num_cols): |
|
index = i * num_cols + j |
|
new_list.append(arr[index]) |
|
return new_list |
|
|
|
|
|
num_rows = max(len(Y_value) if Y_value is not None else 0, 1) |
|
num_cols = max(len(X_value) if X_value is not None else 0, 1) |
|
|
|
|
|
if flip_xy == True: |
|
X_type, Y_type = Y_type, X_type |
|
X_value, Y_value = Y_value, X_value |
|
X_label, Y_label = Y_label, X_label |
|
num_rows, num_cols = num_cols, num_rows |
|
image_pil_list = rearrange_list_A(image_pil_list, num_rows, num_cols) |
|
else: |
|
image_pil_list = rearrange_list_B(image_pil_list, num_rows, num_cols) |
|
image_tensor_list = rearrange_list_A(image_tensor_list, num_cols, num_rows) |
|
latent_list = rearrange_list_A(latent_list, num_cols, num_rows) |
|
|
|
|
|
print_plot_variables(X_type, Y_type, X_value, Y_value, seed, ckpt_name, lora_params, vae_name, |
|
clip_skip, steps, cfg, sampler_name, scheduler, denoise, |
|
num_rows, num_cols, latent_height, latent_width) |
|
|
|
|
|
latent_list = torch.cat(latent_list, dim=0) |
|
|
|
|
|
update_value_by_id("latent", my_unique_id, latent_list) |
|
|
|
|
|
border_size_top = latent_width // 15 |
|
|
|
|
|
if len(Y_label) > 0: |
|
Y_label_longest = max(len(s) for s in Y_label) |
|
else: |
|
|
|
Y_label_longest = 0 |
|
|
|
Y_label_scale = min(Y_label_longest + 4,24) / 24 |
|
|
|
if Y_label_orientation == "Vertical": |
|
border_size_left = border_size_top |
|
else: |
|
|
|
border_size_left = min(latent_width, latent_height) + int(0.2 * abs(latent_width - latent_height)) |
|
border_size_left = int(border_size_left * Y_label_scale) |
|
|
|
|
|
if Y_type == "Nothing": |
|
bg_width = num_cols * latent_width + (num_cols - 1) * grid_spacing |
|
x_offset_initial = 0 |
|
else: |
|
if Y_label_orientation == "Vertical": |
|
bg_width = num_cols * latent_width + (num_cols - 1) * grid_spacing + 3 * border_size_left |
|
x_offset_initial = border_size_left * 3 |
|
else: |
|
bg_width = num_cols * latent_width + (num_cols - 1) * grid_spacing + border_size_left |
|
x_offset_initial = border_size_left |
|
|
|
|
|
if X_type == "Nothing": |
|
bg_height = num_rows * latent_height + (num_rows - 1) * grid_spacing |
|
y_offset = 0 |
|
else: |
|
bg_height = num_rows * latent_height + (num_rows - 1) * grid_spacing + 3 * border_size_top |
|
y_offset = border_size_top * 3 |
|
|
|
|
|
background = Image.new('RGBA', (int(bg_width), int(bg_height)), color=(255, 255, 255, 255)) |
|
|
|
for row in range(num_rows): |
|
|
|
|
|
x_offset = x_offset_initial |
|
|
|
for col in range(num_cols): |
|
|
|
index = col * num_rows + row |
|
img = image_pil_list[index] |
|
|
|
|
|
background.paste(img, (x_offset, y_offset)) |
|
|
|
if row == 0 and X_type != "Nothing": |
|
|
|
text = X_label[col] |
|
|
|
|
|
initial_font_size = int(48 * img.width / 512) |
|
font_size = adjusted_font_size(text, initial_font_size, img.width) |
|
label_height = int(font_size*1.5) |
|
|
|
|
|
label_bg = Image.new('RGBA', (img.width, label_height), color=(255, 255, 255, 0)) |
|
d = ImageDraw.Draw(label_bg) |
|
|
|
|
|
font = ImageFont.truetype(str(Path(font_path)), font_size) |
|
|
|
|
|
_, _, text_width, text_height = d.textbbox([0,0], text, font=font) |
|
text_x = (img.width - text_width) // 2 |
|
text_y = (label_height - text_height) // 2 |
|
|
|
|
|
d.text((text_x, text_y), text, fill='black', font=font) |
|
|
|
|
|
available_space = y_offset - label_height |
|
|
|
|
|
label_y = available_space // 2 |
|
|
|
|
|
background.alpha_composite(label_bg, (x_offset, label_y)) |
|
|
|
if col == 0 and Y_type != "Nothing": |
|
|
|
text = Y_label[row] |
|
|
|
|
|
if Y_label_orientation == "Vertical": |
|
initial_font_size = int(48 * latent_width / 512) |
|
font_size = adjusted_font_size(text, initial_font_size, latent_width) |
|
else: |
|
initial_font_size = int(48 * (border_size_left/Y_label_scale) / 512) |
|
font_size = adjusted_font_size(text, initial_font_size, int(border_size_left/Y_label_scale)) |
|
|
|
|
|
label_bg = Image.new('RGBA', (img.height, int(font_size*1.2)), color=(255, 255, 255, 0)) |
|
d = ImageDraw.Draw(label_bg) |
|
|
|
|
|
font = ImageFont.truetype(str(Path(font_path)), font_size) |
|
|
|
|
|
_, _, text_width, text_height = d.textbbox([0,0], text, font=font) |
|
text_x = (img.height - text_width) // 2 |
|
text_y = (font_size - text_height) // 2 |
|
|
|
|
|
d.text((text_x, text_y), text, fill='black', font=font) |
|
|
|
|
|
if Y_label_orientation == "Vertical": |
|
label_bg = label_bg.rotate(90, expand=True) |
|
|
|
|
|
available_space = x_offset - label_bg.width |
|
|
|
|
|
label_x = available_space // 2 |
|
|
|
|
|
if Y_label_orientation == "Vertical": |
|
label_y = y_offset + (img.height - label_bg.height) // 2 |
|
else: |
|
label_y = y_offset + img.height - (img.height - label_bg.height) // 2 |
|
|
|
|
|
background.alpha_composite(label_bg, (label_x, label_y)) |
|
|
|
|
|
x_offset += img.width + grid_spacing |
|
|
|
|
|
y_offset += img.height + grid_spacing |
|
|
|
images = pil2tensor(background) |
|
|
|
|
|
results = preview_images(images, filename_prefix) |
|
update_value_by_id("results", my_unique_id, results) |
|
|
|
|
|
if xyplot_as_output_image == False: |
|
image_tensor_list = torch.stack([tensor.squeeze() for tensor in image_tensor_list]) |
|
else: |
|
image_tensor_list = images |
|
update_value_by_id("images", my_unique_id, image_tensor_list) |
|
|
|
|
|
if cache_models == "True": |
|
print_loaded_objects_entries(script_node_id, prompt) |
|
|
|
print("-" * 40) |
|
|
|
images = list() if preview_image == "Output Only" else results |
|
|
|
return { |
|
"ui": {"images": images}, |
|
"result": (model, positive, negative, {"samples": latent_list}, vae, image_tensor_list,) |
|
} |
|
|
|
|
|
|
|
class TSC_XYplot: |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"grid_spacing": ("INT", {"default": 0, "min": 0, "max": 500, "step": 5}), |
|
"XY_flip": (["False","True"],), |
|
"Y_label_orientation": (["Horizontal", "Vertical"],), |
|
"cache_models": (["True", "False"],), |
|
"ksampler_output_image": (["Plot", "Images"],),}, |
|
"optional": { |
|
"dependencies": ("DEPENDENCIES", ), |
|
"X": ("XY", ), |
|
"Y": ("XY", ),}, |
|
} |
|
|
|
RETURN_TYPES = ("SCRIPT",) |
|
RETURN_NAMES = ("SCRIPT",) |
|
FUNCTION = "XYplot" |
|
CATEGORY = "Efficiency Nodes/XY Plot" |
|
|
|
def XYplot(self, grid_spacing, XY_flip, Y_label_orientation, cache_models, ksampler_output_image, dependencies=None, X=None, Y=None): |
|
|
|
|
|
if X != None: |
|
X_type, X_value = X |
|
else: |
|
X_type = "Nothing" |
|
X_value = [""] |
|
if Y != None: |
|
Y_type, Y_value = Y |
|
else: |
|
Y_type = "Nothing" |
|
Y_value = [""] |
|
|
|
|
|
if (X_type == Y_type): |
|
if X_type != "Nothing": |
|
print(f"\033[31mXY Plot Error:\033[0m X and Y input types must be different.") |
|
return (None,) |
|
|
|
|
|
types = ("Checkpoint", "LoRA", "Positive Prompt S/R", "Negative Prompt S/R") |
|
if X_type in types or Y_type in types: |
|
if dependencies == None: |
|
print(f"\033[31mXY Plot Error:\033[0m The dependencies input must be connected for certain plot types.") |
|
|
|
return (None,) |
|
|
|
|
|
if X_type == "Seeds++ Batch": |
|
X_value = [i for i in range(X_value[0])] |
|
if Y_type == "Seeds++ Batch": |
|
Y_value = [i for i in range(Y_value[0])] |
|
|
|
|
|
if X_type == "Sampler" and Y_type == "Scheduler": |
|
|
|
X_value = [(x[0], "") for x in X_value] |
|
elif Y_type == "Sampler" and X_type == "Scheduler": |
|
|
|
Y_value = [(y[0], "") for y in Y_value] |
|
|
|
|
|
if X_type == "Scheduler" and Y_type != "Sampler": |
|
|
|
X_value = [(x, None) for x in X_value] |
|
|
|
if Y_type == "Scheduler" and X_type != "Sampler": |
|
|
|
Y_value = [(y, None) for y in Y_value] |
|
|
|
|
|
if Y_type == "Checkpoint" or \ |
|
Y_type == "LoRA" and X_type not in {"Checkpoint"} or \ |
|
Y_type == "VAE" and X_type not in {"Checkpoint", "LoRA"} or \ |
|
Y_type == "Positive Prompt S/R" and X_type not in {"Checkpoint", "LoRA", "VAE", |
|
"Negative Prompt S/R"} or \ |
|
Y_type == "Negative Prompt S/R" and X_type not in {"Checkpoint", "LoRA", "VAE", |
|
"Positive Prompt S/R"} or \ |
|
X_type == "Nothing" and Y_type != "Nothing": |
|
flip_xy = True |
|
X_type, Y_type = Y_type, X_type |
|
X_value, Y_value = Y_value, X_value |
|
else: |
|
flip_xy = False |
|
|
|
|
|
if XY_flip == "True": |
|
X_type, Y_type = Y_type, X_type |
|
X_value, Y_value = Y_value, X_value |
|
|
|
|
|
xyplot_as_output_image = ksampler_output_image == "Plot" |
|
|
|
return ((X_type, X_value, Y_type, Y_value, grid_spacing, Y_label_orientation, cache_models, |
|
xyplot_as_output_image, flip_xy, dependencies),) |
|
|
|
|
|
|
|
class TSC_XYplot_SeedsBatch: |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"batch_count": ("INT", {"default": 1, "min": 0, "max": 50}),}, |
|
} |
|
|
|
RETURN_TYPES = ("XY",) |
|
RETURN_NAMES = ("X or Y",) |
|
FUNCTION = "xy_value" |
|
CATEGORY = "Efficiency Nodes/XY Plot/XY Inputs" |
|
|
|
def xy_value(self, batch_count): |
|
if batch_count == 0: |
|
return (None,) |
|
xy_type = "Seeds++ Batch" |
|
xy_value = [batch_count] |
|
return ((xy_type, xy_value),) |
|
|
|
|
|
class TSC_XYplot_Steps: |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"select_count": ("INT", {"default": 0, "min": 0, "max": 5}), |
|
"steps_1": ("INT", {"default": 20, "min": 1, "max": 10000}), |
|
"steps_2": ("INT", {"default": 20, "min": 1, "max": 10000}), |
|
"steps_3": ("INT", {"default": 20, "min": 1, "max": 10000}), |
|
"steps_4": ("INT", {"default": 20, "min": 1, "max": 10000}), |
|
"steps_5": ("INT", {"default": 20, "min": 1, "max": 10000}),}, |
|
} |
|
|
|
RETURN_TYPES = ("XY",) |
|
RETURN_NAMES = ("X or Y",) |
|
FUNCTION = "xy_value" |
|
CATEGORY = "Efficiency Nodes/XY Plot/XY Inputs" |
|
|
|
def xy_value(self, select_count, steps_1, steps_2, steps_3, steps_4, steps_5): |
|
xy_type = "Steps" |
|
xy_value = [step for idx, step in enumerate([steps_1, steps_2, steps_3, steps_4, steps_5], start=1) if |
|
idx <= select_count] |
|
if not xy_value: |
|
return (None,) |
|
return ((xy_type, xy_value),) |
|
|
|
|
|
|
|
class TSC_XYplot_CFG: |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"select_count": ("INT", {"default": 0, "min": 0, "max": 5}), |
|
"cfg_1": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0}), |
|
"cfg_2": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0}), |
|
"cfg_3": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0}), |
|
"cfg_4": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0}), |
|
"cfg_5": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0}),}, |
|
} |
|
|
|
RETURN_TYPES = ("XY",) |
|
RETURN_NAMES = ("X or Y",) |
|
FUNCTION = "xy_value" |
|
CATEGORY = "Efficiency Nodes/XY Plot/XY Inputs" |
|
|
|
def xy_value(self, select_count, cfg_1, cfg_2, cfg_3, cfg_4, cfg_5): |
|
xy_type = "CFG Scale" |
|
xy_value = [cfg for idx, cfg in enumerate([cfg_1, cfg_2, cfg_3, cfg_4, cfg_5], start=1) if idx <= select_count] |
|
if not xy_value: |
|
return (None,) |
|
return ((xy_type, xy_value),) |
|
|
|
|
|
|
|
class TSC_XYplot_Sampler: |
|
|
|
samplers = ["None"] + comfy.samplers.KSampler.SAMPLERS |
|
schedulers = ["None"] + comfy.samplers.KSampler.SCHEDULERS |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"sampler_1": (cls.samplers,), |
|
"scheduler_1": (cls.schedulers,), |
|
"sampler_2": (cls.samplers,), |
|
"scheduler_2": (cls.schedulers,), |
|
"sampler_3": (cls.samplers,), |
|
"scheduler_3": (cls.schedulers,), |
|
"sampler_4": (cls.samplers,), |
|
"scheduler_4": (cls.schedulers,), |
|
"sampler_5": (cls.samplers,), |
|
"scheduler_5": (cls.schedulers,),}, |
|
} |
|
RETURN_TYPES = ("XY",) |
|
RETURN_NAMES = ("X or Y",) |
|
FUNCTION = "xy_value" |
|
CATEGORY = "Efficiency Nodes/XY Plot/XY Inputs" |
|
|
|
def xy_value(self, sampler_1, scheduler_1, sampler_2, scheduler_2, sampler_3, scheduler_3, |
|
sampler_4, scheduler_4, sampler_5, scheduler_5): |
|
|
|
samplers = [sampler_1, sampler_2, sampler_3, sampler_4, sampler_5] |
|
schedulers = [scheduler_1, scheduler_2, scheduler_3, scheduler_4, scheduler_5] |
|
|
|
pairs = [] |
|
for sampler, scheduler in zip(samplers, schedulers): |
|
if sampler != "None": |
|
if scheduler != "None": |
|
pairs.append((sampler, scheduler)) |
|
else: |
|
pairs.append((sampler,None)) |
|
|
|
xy_type = "Sampler" |
|
xy_value = pairs |
|
if not xy_value: |
|
return (None,) |
|
return ((xy_type, xy_value),) |
|
|
|
|
|
|
|
class TSC_XYplot_Scheduler: |
|
|
|
schedulers = ["None"] + comfy.samplers.KSampler.SCHEDULERS |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"scheduler_1": (cls.schedulers,), |
|
"scheduler_2": (cls.schedulers,), |
|
"scheduler_3": (cls.schedulers,), |
|
"scheduler_4": (cls.schedulers,), |
|
"scheduler_5": (cls.schedulers,),}, |
|
} |
|
|
|
RETURN_TYPES = ("XY",) |
|
RETURN_NAMES = ("X or Y",) |
|
FUNCTION = "xy_value" |
|
CATEGORY = "Efficiency Nodes/XY Plot/XY Inputs" |
|
|
|
def xy_value(self, scheduler_1, scheduler_2, scheduler_3, scheduler_4, scheduler_5): |
|
xy_type = "Scheduler" |
|
xy_value = [scheduler for scheduler in [scheduler_1, scheduler_2, scheduler_3, scheduler_4, scheduler_5] if |
|
scheduler != "None"] |
|
if not xy_value: |
|
return (None,) |
|
return ((xy_type, xy_value),) |
|
|
|
|
|
|
|
class TSC_XYplot_Denoise: |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"select_count": ("INT", {"default": 0, "min": 0, "max": 5}), |
|
"denoise_1": ("FLOAT", {"default": 1.0, "min": 0.00, "max": 1.0, "step": 0.01}), |
|
"denoise_2": ("FLOAT", {"default": 1.0, "min": 0.00, "max": 1.0, "step": 0.01}), |
|
"denoise_3": ("FLOAT", {"default": 1.0, "min": 0.00, "max": 1.0, "step": 0.01}), |
|
"denoise_4": ("FLOAT", {"default": 1.0, "min": 0.00, "max": 1.0, "step": 0.01}), |
|
"denoise_5": ("FLOAT", {"default": 1.0, "min": 0.00, "max": 1.0, "step": 0.01}),}, |
|
} |
|
|
|
RETURN_TYPES = ("XY",) |
|
RETURN_NAMES = ("X or Y",) |
|
FUNCTION = "xy_value" |
|
CATEGORY = "Efficiency Nodes/XY Plot/XY Inputs" |
|
|
|
def xy_value(self, select_count, denoise_1, denoise_2, denoise_3, denoise_4, denoise_5): |
|
xy_type = "Denoise" |
|
xy_value = [denoise for idx, denoise in |
|
enumerate([denoise_1, denoise_2, denoise_3, denoise_4, denoise_5], start=1) if idx <= select_count] |
|
if not xy_value: |
|
return (None,) |
|
return ((xy_type, xy_value),) |
|
|
|
|
|
|
|
class TSC_XYplot_VAE: |
|
|
|
vaes = ["None"] + folder_paths.get_filename_list("vae") |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"vae_name_1": (cls.vaes,), |
|
"vae_name_2": (cls.vaes,), |
|
"vae_name_3": (cls.vaes,), |
|
"vae_name_4": (cls.vaes,), |
|
"vae_name_5": (cls.vaes,),}, |
|
} |
|
|
|
RETURN_TYPES = ("XY",) |
|
RETURN_NAMES = ("X or Y",) |
|
FUNCTION = "xy_value" |
|
CATEGORY = "Efficiency Nodes/XY Plot/XY Inputs" |
|
|
|
def xy_value(self, vae_name_1, vae_name_2, vae_name_3, vae_name_4, vae_name_5): |
|
xy_type = "VAE" |
|
xy_value = [vae for vae in [vae_name_1, vae_name_2, vae_name_3, vae_name_4, vae_name_5] if vae != "None"] |
|
if not xy_value: |
|
return (None,) |
|
return ((xy_type, xy_value),) |
|
|
|
|
|
|
|
class TSC_XYplot_PromptSR_Positive: |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"search_txt": ("STRING", {"default": "", "multiline": False}), |
|
"replace_count": ("INT", {"default": 0, "min": 0, "max": 4}), |
|
"replace_1":("STRING", {"default": "", "multiline": False}), |
|
"replace_2": ("STRING", {"default": "", "multiline": False}), |
|
"replace_3": ("STRING", {"default": "", "multiline": False}), |
|
"replace_4": ("STRING", {"default": "", "multiline": False}),}, |
|
} |
|
|
|
RETURN_TYPES = ("XY",) |
|
RETURN_NAMES = ("X or Y",) |
|
FUNCTION = "xy_value" |
|
CATEGORY = "Efficiency Nodes/XY Plot/XY Inputs" |
|
|
|
def xy_value(self, search_txt, replace_count, replace_1, replace_2, replace_3, replace_4): |
|
|
|
if search_txt == "": |
|
return (None,) |
|
|
|
xy_type = "Positive Prompt S/R" |
|
|
|
|
|
replacements = [replace_1, replace_2, replace_3, replace_4] |
|
|
|
|
|
xy_values = [(search_txt, None)] |
|
|
|
if replace_count > 0: |
|
|
|
xy_values.extend([(search_txt, replacements[i]) for i in range(replace_count)]) |
|
|
|
return ((xy_type, xy_values),) |
|
|
|
|
|
class TSC_XYplot_PromptSR_Negative: |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"search_txt": ("STRING", {"default": "", "multiline": False}), |
|
"replace_count": ("INT", {"default": 0, "min": 0, "max": 4}), |
|
"replace_1":("STRING", {"default": "", "multiline": False}), |
|
"replace_2": ("STRING", {"default": "", "multiline": False}), |
|
"replace_3": ("STRING", {"default": "", "multiline": False}), |
|
"replace_4": ("STRING", {"default": "", "multiline": False}),}, |
|
} |
|
|
|
RETURN_TYPES = ("XY",) |
|
RETURN_NAMES = ("X or Y",) |
|
FUNCTION = "xy_value" |
|
CATEGORY = "Efficiency Nodes/XY Plot/XY Inputs" |
|
|
|
def xy_value(self, search_txt, replace_count, replace_1, replace_2, replace_3, replace_4): |
|
|
|
if search_txt == "": |
|
return (None,) |
|
|
|
xy_type = "Negative Prompt S/R" |
|
|
|
|
|
replacements = [replace_1, replace_2, replace_3, replace_4] |
|
|
|
|
|
xy_values = [(search_txt, None)] |
|
|
|
if replace_count > 0: |
|
|
|
xy_values.extend([(search_txt, replacements[i]) for i in range(replace_count)]) |
|
|
|
return ((xy_type, xy_values),) |
|
|
|
|
|
class TSC_XYplot_Checkpoint: |
|
|
|
checkpoints = ["None"] + folder_paths.get_filename_list("checkpoints") |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"ckpt_name_1": (cls.checkpoints,), |
|
"clip_skip1": ("INT", {"default": -1, "min": -24, "max": -1, "step": 1}), |
|
"ckpt_name_2": (cls.checkpoints,), |
|
"clip_skip2": ("INT", {"default": -1, "min": -24, "max": -1, "step": 1}), |
|
"ckpt_name_3": (cls.checkpoints,), |
|
"clip_skip3": ("INT", {"default": -1, "min": -24, "max": -1, "step": 1}), |
|
"ckpt_name_4": (cls.checkpoints,), |
|
"clip_skip4": ("INT", {"default": -1, "min": -24, "max": -1, "step": 1}), |
|
"ckpt_name_5": (cls.checkpoints,), |
|
"clip_skip5": ("INT", {"default": -1, "min": -24, "max": -1, "step": 1}),}, |
|
} |
|
|
|
RETURN_TYPES = ("XY",) |
|
RETURN_NAMES = ("X or Y",) |
|
FUNCTION = "xy_value" |
|
CATEGORY = "Efficiency Nodes/XY Plot/XY Inputs" |
|
|
|
def xy_value(self, ckpt_name_1, clip_skip1, ckpt_name_2, clip_skip2, ckpt_name_3, clip_skip3, |
|
ckpt_name_4, clip_skip4, ckpt_name_5, clip_skip5): |
|
xy_type = "Checkpoint" |
|
checkpoints = [ckpt_name_1, ckpt_name_2, ckpt_name_3, ckpt_name_4, ckpt_name_5] |
|
clip_skips = [clip_skip1, clip_skip2, clip_skip3, clip_skip4, clip_skip5] |
|
xy_value = [(checkpoint, clip_skip) for checkpoint, clip_skip in zip(checkpoints, clip_skips) if |
|
checkpoint != "None"] |
|
if not xy_value: |
|
return (None,) |
|
return ((xy_type, xy_value),) |
|
|
|
|
|
class TSC_XYplot_ClipSkip: |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"select_count": ("INT", {"default": 0, "min": 0, "max": 5}), |
|
"clip_skip_1": ("INT", {"default": -1, "min": -24, "max": -1, "step": 1}), |
|
"clip_skip_2": ("INT", {"default": -2, "min": -24, "max": -1, "step": 1}), |
|
"clip_skip_3": ("INT", {"default": -3, "min": -24, "max": -1, "step": 1}), |
|
"clip_skip_4": ("INT", {"default": -4, "min": -24, "max": -1, "step": 1}), |
|
"clip_skip_5": ("INT", {"default": -5, "min": -24, "max": -1, "step": 1}),}, |
|
} |
|
|
|
RETURN_TYPES = ("XY",) |
|
RETURN_NAMES = ("X or Y",) |
|
FUNCTION = "xy_value" |
|
CATEGORY = "Efficiency Nodes/XY Plot/XY Inputs" |
|
|
|
def xy_value(self, select_count, clip_skip_1, clip_skip_2, clip_skip_3, clip_skip_4, clip_skip_5): |
|
xy_type = "Clip Skip" |
|
xy_value = [clip_skip for idx, clip_skip in |
|
enumerate([clip_skip_1, clip_skip_2, clip_skip_3, clip_skip_4, clip_skip_5], start=1) if idx <= select_count] |
|
if not xy_value: |
|
return (None,) |
|
return ((xy_type, xy_value),) |
|
|
|
|
|
class TSC_XYplot_LoRA: |
|
|
|
loras = ["None"] + folder_paths.get_filename_list("loras") |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"model_strengths": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), |
|
"clip_strengths": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), |
|
"lora_name_1": (cls.loras,), |
|
"lora_name_2": (cls.loras,), |
|
"lora_name_3": (cls.loras,), |
|
"lora_name_4": (cls.loras,), |
|
"lora_name_5": (cls.loras,)}, |
|
"optional": {"lora_stack": ("LORA_STACK", )} |
|
} |
|
|
|
RETURN_TYPES = ("XY",) |
|
RETURN_NAMES = ("X or Y",) |
|
FUNCTION = "xy_value" |
|
CATEGORY = "Efficiency Nodes/XY Plot/XY Inputs" |
|
|
|
def xy_value(self, model_strengths, clip_strengths, lora_name_1, lora_name_2, lora_name_3, lora_name_4, lora_name_5, |
|
lora_stack=None): |
|
xy_type = "LoRA" |
|
loras = [lora_name_1, lora_name_2, lora_name_3, lora_name_4, lora_name_5] |
|
|
|
|
|
xy_value = [[(lora, model_strengths, clip_strengths)] + (lora_stack if lora_stack else []) for lora in loras if |
|
lora != "None"] |
|
|
|
if not xy_value: |
|
return (None,) |
|
return ((xy_type, xy_value),) |
|
|
|
|
|
|
|
class TSC_XYplot_LoRA_Adv: |
|
|
|
loras = ["None"] + folder_paths.get_filename_list("loras") |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"lora_name_1": (cls.loras,), |
|
"model_str_1": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), |
|
"clip_str_1": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), |
|
"lora_name_2": (cls.loras,), |
|
"model_str_2": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), |
|
"clip_str_2": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), |
|
"lora_name_3": (cls.loras,), |
|
"model_str_3": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), |
|
"clip_str_3": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), |
|
"lora_name_4": (cls.loras,), |
|
"model_str_4": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), |
|
"clip_str_4": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), |
|
"lora_name_5": (cls.loras,), |
|
"model_str_5": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), |
|
"clip_str_5": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),}, |
|
"optional": {"lora_stack": ("LORA_STACK",)} |
|
} |
|
|
|
RETURN_TYPES = ("XY",) |
|
RETURN_NAMES = ("X or Y",) |
|
FUNCTION = "xy_value" |
|
CATEGORY = "Efficiency Nodes/XY Plot/XY Inputs" |
|
|
|
def xy_value(self, lora_name_1, model_str_1, clip_str_1, lora_name_2, model_str_2, clip_str_2, lora_name_3, |
|
model_str_3, |
|
clip_str_3, lora_name_4, model_str_4, clip_str_4, lora_name_5, model_str_5, clip_str_5, |
|
lora_stack=None): |
|
xy_type = "LoRA" |
|
loras = [lora_name_1, lora_name_2, lora_name_3, lora_name_4, lora_name_5] |
|
model_strs = [model_str_1, model_str_2, model_str_3, model_str_4, model_str_5] |
|
clip_strs = [clip_str_1, clip_str_2, clip_str_3, clip_str_4, clip_str_5] |
|
|
|
|
|
xy_value = [[(lora, model_str, clip_str)] + (lora_stack if lora_stack else []) for lora, model_str, clip_str in |
|
zip(loras, model_strs, clip_strs) if lora != "None"] |
|
|
|
if not xy_value: |
|
return (None,) |
|
return ((xy_type, xy_value),) |
|
|
|
|
|
|
|
class TSC_XYplot_LoRA_Stacks: |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"node_state": (["Enabled", "Disabled"],)}, |
|
"optional": { |
|
"lora_stack_1": ("LORA_STACK",), |
|
"lora_stack_2": ("LORA_STACK",), |
|
"lora_stack_3": ("LORA_STACK",), |
|
"lora_stack_4": ("LORA_STACK",), |
|
"lora_stack_5": ("LORA_STACK",),}, |
|
} |
|
|
|
RETURN_TYPES = ("XY",) |
|
RETURN_NAMES = ("X or Y",) |
|
FUNCTION = "xy_value" |
|
CATEGORY = "Efficiency Nodes/XY Plot/XY Inputs" |
|
|
|
def xy_value(self, node_state, lora_stack_1=None, lora_stack_2=None, lora_stack_3=None, lora_stack_4=None, lora_stack_5=None): |
|
xy_type = "LoRA" |
|
xy_value = [stack for stack in [lora_stack_1, lora_stack_2, lora_stack_3, lora_stack_4, lora_stack_5] if stack is not None] |
|
if not xy_value or not any(xy_value) or node_state == "Disabled": |
|
return (None,) |
|
else: |
|
return ((xy_type, xy_value),) |
|
|
|
|
|
class TSC_XYplot_Manual_XY_Entry_Info: |
|
|
|
syntax = "(X/Y_types) (X/Y_values)\n" \ |
|
"Seeds++ Batch batch_count\n" \ |
|
"Steps steps_1;steps_2;...\n" \ |
|
"CFG Scale cfg_1;cfg_2;...\n" \ |
|
"Sampler(1) sampler_1;sampler_2;...\n" \ |
|
"Sampler(2) sampler_1,scheduler_1;...\n" \ |
|
"Sampler(3) sampler_1;...;,default_scheduler\n" \ |
|
"Scheduler scheduler_1;scheduler_2;...\n" \ |
|
"Denoise denoise_1;denoise_2;...\n" \ |
|
"VAE vae_1;vae_2;vae_3;...\n" \ |
|
"+Prompt S/R search_txt;replace_1;replace_2;...\n" \ |
|
"-Prompt S/R search_txt;replace_1;replace_2;...\n" \ |
|
"Checkpoint(1) ckpt_1;ckpt_2;ckpt_3;...\n" \ |
|
"Checkpoint(2) ckpt_1,clip_skip_1;...\n" \ |
|
"Checkpoint(3) ckpt_1;ckpt_2;...;,default_clip_skip\n" \ |
|
"Clip Skip clip_skip_1;clip_skip_2;...\n" \ |
|
"LoRA(1) lora_1;lora_2;lora_3;...\n" \ |
|
"LoRA(2) lora_1;...;,default_model_str,default_clip_str\n" \ |
|
"LoRA(3) lora_1,model_str_1,clip_str_1;..." |
|
|
|
samplers = ";\n".join(comfy.samplers.KSampler.SAMPLERS) |
|
schedulers = ";\n".join(comfy.samplers.KSampler.SCHEDULERS) |
|
vaes = ";\n".join(folder_paths.get_filename_list("vae")) |
|
ckpts = ";\n".join(folder_paths.get_filename_list("checkpoints")) |
|
loras = ";\n".join(folder_paths.get_filename_list("loras")) |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"notes": ("STRING", {"default": |
|
f"_____________SYNTAX_____________\n{cls.syntax}\n\n" |
|
f"____________SAMPLERS____________\n{cls.samplers}\n\n" |
|
f"___________SCHEDULERS___________\n{cls.schedulers}\n\n" |
|
f"_____________VAES_______________\n{cls.vaes}\n\n" |
|
f"___________CHECKPOINTS__________\n{cls.ckpts}\n\n" |
|
f"_____________LORAS______________\n{cls.loras}\n","multiline": True}),},} |
|
|
|
RETURN_TYPES = () |
|
CATEGORY = "Efficiency Nodes/XY Plot/XY Inputs" |
|
|
|
|
|
class TSC_XYplot_Manual_XY_Entry: |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"X_type": (["Nothing", "Seeds++ Batch", "Steps", "CFG Scale", "Sampler", "Scheduler", "Denoise", "VAE", |
|
"Positive Prompt S/R", "Negative Prompt S/R", "Checkpoint", "Clip Skip", "LoRA"],), |
|
"X_value": ("STRING", {"default": "", "multiline": True}), |
|
"Y_type": (["Nothing", "Seeds++ Batch", "Steps", "CFG Scale", "Sampler", "Scheduler", "Denoise", "VAE", |
|
"Positive Prompt S/R", "Negative Prompt S/R", "Checkpoint", "Clip Skip", "LoRA"],), |
|
"Y_value": ("STRING", {"default": "", "multiline": True}),},} |
|
|
|
RETURN_TYPES = ("XY", "XY",) |
|
RETURN_NAMES = ("X", "Y",) |
|
FUNCTION = "xy_value" |
|
CATEGORY = "Efficiency Nodes/XY Plot/XY Inputs" |
|
|
|
def xy_value(self, X_type, X_value, Y_type, Y_value, prompt=None, my_unique_id=None): |
|
|
|
|
|
if X_type not in {"Positive Prompt S/R", "Negative Prompt S/R", "VAE", "Checkpoint", "LoRA"}: |
|
X_value = X_value.replace(" ", "") |
|
X_value = X_value.replace("\n", "") |
|
X_value = X_value.rstrip(";") |
|
X_value = X_value.split(";") |
|
|
|
|
|
if Y_type not in {"Positive Prompt S/R", "Negative Prompt S/R", "VAE", "Checkpoint", "LoRA"}: |
|
Y_value = Y_value.replace(" ", "") |
|
Y_value = Y_value.replace("\n", "") |
|
Y_value = Y_value.rstrip(";") |
|
Y_value = Y_value.split(";") |
|
|
|
|
|
bounds = { |
|
"Seeds++ Batch": {"min": 0, "max": 50}, |
|
"Steps": {"min": 1, "max": 10000}, |
|
"CFG Scale": {"min": 0, "max": 100}, |
|
"Sampler": {"options": comfy.samplers.KSampler.SAMPLERS}, |
|
"Scheduler": {"options": comfy.samplers.KSampler.SCHEDULERS}, |
|
"Denoise": {"min": 0, "max": 1}, |
|
"VAE": {"options": folder_paths.get_filename_list("vae")}, |
|
"Checkpoint": {"options": folder_paths.get_filename_list("checkpoints")}, |
|
"Clip Skip": {"min": -24, "max": -1}, |
|
"LoRA": {"options": folder_paths.get_filename_list("loras"), |
|
"model_str": {"min": 0, "max": 10},"clip_str": {"min": 0, "max": 10},}, |
|
} |
|
|
|
|
|
def validate_value(value, value_type, bounds): |
|
|
|
|
|
if value_type == "Seeds++ Batch": |
|
try: |
|
x = int(float(value)) |
|
if x < bounds["Seeds++ Batch"]["min"]: |
|
x = bounds["Seeds++ Batch"]["min"] |
|
elif x > bounds["Seeds++ Batch"]["max"]: |
|
x = bounds["Seeds++ Batch"]["max"] |
|
except ValueError: |
|
print(f"\033[31mXY Plot Error:\033[0m '{value}' is not a valid batch count.") |
|
return None |
|
if float(value) != x: |
|
print(f"\033[31mmXY Plot Error:\033[0m '{value}' is not a valid batch count.") |
|
return None |
|
return x |
|
|
|
|
|
elif value_type == "Steps": |
|
try: |
|
x = int(value) |
|
if x < bounds["Steps"]["min"]: |
|
x = bounds["Steps"]["min"] |
|
elif x > bounds["Steps"]["max"]: |
|
x = bounds["Steps"]["max"] |
|
return x |
|
except ValueError: |
|
print( |
|
f"\033[31mXY Plot Error:\033[0m '{value}' is not a valid Step count.") |
|
return None |
|
|
|
|
|
elif value_type == "CFG Scale": |
|
try: |
|
x = float(value) |
|
if x < bounds["CFG Scale"]["min"]: |
|
x = bounds["CFG Scale"]["min"] |
|
elif x > bounds["CFG Scale"]["max"]: |
|
x = bounds["CFG Scale"]["max"] |
|
return x |
|
except ValueError: |
|
print( |
|
f"\033[31mXY Plot Error:\033[0m '{value}' is not a number between {bounds['CFG Scale']['min']}" |
|
f" and {bounds['CFG Scale']['max']} for CFG Scale.") |
|
return None |
|
|
|
|
|
elif value_type == "Sampler": |
|
if isinstance(value, str) and ',' in value: |
|
value = tuple(map(str.strip, value.split(','))) |
|
if isinstance(value, tuple): |
|
if len(value) >= 2: |
|
value = value[:2] |
|
sampler, scheduler = value |
|
scheduler = scheduler.lower() |
|
if sampler not in bounds["Sampler"]["options"]: |
|
valid_samplers = '\n'.join(bounds["Sampler"]["options"]) |
|
print( |
|
f"\033[31mXY Plot Error:\033[0m '{sampler}' is not a valid sampler. Valid samplers are:\n{valid_samplers}") |
|
sampler = None |
|
if scheduler not in bounds["Scheduler"]["options"]: |
|
valid_schedulers = '\n'.join(bounds["Scheduler"]["options"]) |
|
print( |
|
f"\033[31mXY Plot Error:\033[0m '{scheduler}' is not a valid scheduler. Valid schedulers are:\n{valid_schedulers}") |
|
scheduler = None |
|
if sampler is None or scheduler is None: |
|
return None |
|
else: |
|
return sampler, scheduler |
|
else: |
|
print( |
|
f"\033[31mXY Plot Error:\033[0m '{value}' is not a valid sampler.'") |
|
return None |
|
else: |
|
if value not in bounds["Sampler"]["options"]: |
|
valid_samplers = '\n'.join(bounds["Sampler"]["options"]) |
|
print( |
|
f"\033[31mXY Plot Error:\033[0m '{value}' is not a valid sampler. Valid samplers are:\n{valid_samplers}") |
|
return None |
|
else: |
|
return value, None |
|
|
|
|
|
elif value_type == "Scheduler": |
|
if value not in bounds["Scheduler"]["options"]: |
|
valid_schedulers = '\n'.join(bounds["Scheduler"]["options"]) |
|
print( |
|
f"\033[31mXY Plot Error:\033[0m '{value}' is not a valid Scheduler. Valid Schedulers are:\n{valid_schedulers}") |
|
return None |
|
else: |
|
return value |
|
|
|
|
|
elif value_type == "Denoise": |
|
try: |
|
x = float(value) |
|
if x < bounds["Denoise"]["min"]: |
|
x = bounds["Denoise"]["min"] |
|
elif x > bounds["Denoise"]["max"]: |
|
x = bounds["Denoise"]["max"] |
|
return x |
|
except ValueError: |
|
print( |
|
f"\033[31mXY Plot Error:\033[0m '{value}' is not a number between {bounds['Denoise']['min']} " |
|
f"and {bounds['Denoise']['max']} for Denoise.") |
|
return None |
|
|
|
|
|
elif value_type == "VAE": |
|
if value not in bounds["VAE"]["options"]: |
|
valid_vaes = '\n'.join(bounds["VAE"]["options"]) |
|
print(f"\033[31mXY Plot Error:\033[0m '{value}' is not a valid VAE. Valid VAEs are:\n{valid_vaes}") |
|
return None |
|
else: |
|
return value |
|
|
|
|
|
elif value_type == "Checkpoint": |
|
if isinstance(value, str) and ',' in value: |
|
value = tuple(map(str.strip, value.split(','))) |
|
if isinstance(value, tuple): |
|
if len(value) >= 2: |
|
value = value[:2] |
|
checkpoint, clip_skip = value |
|
try: |
|
clip_skip = int(clip_skip) |
|
except ValueError: |
|
print(f"\033[31mXY Plot Error:\033[0m '{clip_skip}' is not a valid clip_skip. " |
|
f"Valid clip skip values are integers between {bounds['Clip Skip']['min']} and {bounds['Clip Skip']['max']}.") |
|
return None |
|
if checkpoint not in bounds["Checkpoint"]["options"]: |
|
valid_checkpoints = '\n'.join(bounds["Checkpoint"]["options"]) |
|
print( |
|
f"\033[31mXY Plot Error:\033[0m '{checkpoint}' is not a valid checkpoint. Valid checkpoints are:\n{valid_checkpoints}") |
|
checkpoint = None |
|
if clip_skip < bounds["Clip Skip"]["min"] or clip_skip > bounds["Clip Skip"]["max"]: |
|
print(f"\033[31mXY Plot Error:\033[0m '{clip_skip}' is not a valid clip skip. " |
|
f"Valid clip skip values are integers between {bounds['Clip Skip']['min']} and {bounds['Clip Skip']['max']}.") |
|
clip_skip = None |
|
if checkpoint is None or clip_skip is None: |
|
return None |
|
else: |
|
return checkpoint, clip_skip |
|
else: |
|
print( |
|
f"\033[31mXY Plot Error:\033[0m '{value}' is not a valid checkpoint.'") |
|
return None |
|
else: |
|
if value not in bounds["Checkpoint"]["options"]: |
|
valid_checkpoints = '\n'.join(bounds["Checkpoint"]["options"]) |
|
print( |
|
f"\033[31mXY Plot Error:\033[0m '{value}' is not a valid checkpoint. Valid checkpoints are:\n{valid_checkpoints}") |
|
return None |
|
else: |
|
return value, None |
|
|
|
|
|
elif value_type == "Clip Skip": |
|
try: |
|
x = int(value) |
|
if x < bounds["Clip Skip"]["min"]: |
|
x = bounds["Clip Skip"]["min"] |
|
elif x > bounds["Clip Skip"]["max"]: |
|
x = bounds["Clip Skip"]["max"] |
|
return x |
|
except ValueError: |
|
print(f"\033[31mXY Plot Error:\033[0m '{value}' is not a valid Clip Skip.") |
|
return None |
|
|
|
|
|
elif value_type == "LoRA": |
|
if isinstance(value, str) and ',' in value: |
|
value = tuple(map(str.strip, value.split(','))) |
|
|
|
if isinstance(value, tuple): |
|
lora_name, model_str, clip_str = (value + (1.0, 1.0))[:3] |
|
|
|
if lora_name not in bounds["LoRA"]["options"]: |
|
valid_loras = '\n'.join(bounds["LoRA"]["options"]) |
|
print(f"\033[31mXY Plot Error:\033[0m '{lora_name}' is not a valid LoRA. Valid LoRAs are:\n{valid_loras}") |
|
lora_name = None |
|
|
|
try: |
|
model_str = float(model_str) |
|
clip_str = float(clip_str) |
|
except ValueError: |
|
print(f"\033[31mXY Plot Error:\033[0m The LoRA model strength and clip strength values should be numbers" |
|
f" between {bounds['LoRA']['model_str']['min']} and {bounds['LoRA']['model_str']['max']}.") |
|
return None |
|
|
|
if model_str < bounds["LoRA"]["model_str"]["min"] or model_str > bounds["LoRA"]["model_str"]["max"]: |
|
print(f"\033[31mXY Plot Error:\033[0m '{model_str}' is not a valid LoRA model strength value. " |
|
f"Valid lora model strength values are between {bounds['LoRA']['model_str']['min']} and {bounds['LoRA']['model_str']['max']}.") |
|
model_str = None |
|
|
|
if clip_str < bounds["LoRA"]["clip_str"]["min"] or clip_str > bounds["LoRA"]["clip_str"]["max"]: |
|
print(f"\033[31mXY Plot Error:\033[0m '{clip_str}' is not a valid LoRA clip strength value. " |
|
f"Valid lora clip strength values are between {bounds['LoRA']['clip_str']['min']} and {bounds['LoRA']['clip_str']['max']}.") |
|
clip_str = None |
|
|
|
if lora_name is None or model_str is None or clip_str is None: |
|
return None |
|
else: |
|
return lora_name, model_str, clip_str |
|
else: |
|
if value not in bounds["LoRA"]["options"]: |
|
valid_loras = '\n'.join(bounds["LoRA"]["options"]) |
|
print( |
|
f"\033[31mXY Plot Error:\033[0m '{value}' is not a valid LoRA. Valid LoRAs are:\n{valid_loras}") |
|
return None |
|
else: |
|
return value, 1.0, 1.0 |
|
|
|
|
|
else: |
|
return None |
|
|
|
|
|
if len(X_value) != 1 and X_type == "Seeds++ Batch": |
|
print(f"\033[31mXY Plot Error:\033[0m '{';'.join(X_value)}' is not a valid batch count.") |
|
return (None,None,) |
|
|
|
|
|
if len(Y_value) != 1 and Y_type == "Seeds++ Batch": |
|
print(f"\033[31mXY Plot Error:\033[0m '{';'.join(Y_value)}' is not a valid batch count.") |
|
return (None,None,) |
|
|
|
|
|
if X_type in ["Sampler", "Checkpoint", "LoRA"]: |
|
if X_value[-1].startswith(','): |
|
|
|
suffixes = X_value.pop().lstrip(',').split(',') |
|
|
|
X_value = [entry.split(',') for entry in X_value] |
|
|
|
for entry in X_value: |
|
entry += suffixes[len(entry) - 1:] |
|
|
|
X_value = [','.join(entry) for entry in X_value] |
|
|
|
|
|
if Y_type in ["Sampler", "Checkpoint", "LoRA"]: |
|
if Y_value[-1].startswith(','): |
|
|
|
suffixes = Y_value.pop().lstrip(',').split(',') |
|
|
|
Y_value = [entry.split(',') for entry in Y_value] |
|
|
|
for entry in Y_value: |
|
entry += suffixes[len(entry) - 1:] |
|
|
|
Y_value = [','.join(entry) for entry in Y_value] |
|
|
|
|
|
if X_type in {"Positive Prompt S/R", "Negative Prompt S/R"}: |
|
if X_value[0] == '': |
|
print(f"\033[31mXY Plot Error:\033[0m Prompt S/R value can not be empty.") |
|
return (None, None,) |
|
else: |
|
X_value = [(X_value[0], None) if i == 0 else (X_value[0], x) for i, x in enumerate(X_value)] |
|
|
|
|
|
if Y_type in {"Positive Prompt S/R", "Negative Prompt S/R"}: |
|
if Y_value[0] == '': |
|
print(f"\033[31mXY Plot Error:\033[0m Prompt S/R value can not be empty.") |
|
return (None, None,) |
|
else: |
|
Y_value = [(Y_value[0], None) if i == 0 else (Y_value[0], y) for i, y in enumerate(Y_value)] |
|
|
|
|
|
if X_type not in {"Nothing", "Positive Prompt S/R", "Negative Prompt S/R"}: |
|
for i in range(len(X_value)): |
|
X_value[i] = validate_value(X_value[i], X_type, bounds) |
|
if X_value[i] == None: |
|
return (None,None,) |
|
|
|
|
|
if Y_type not in {"Nothing", "Positive Prompt S/R", "Negative Prompt S/R"}: |
|
for i in range(len(Y_value)): |
|
Y_value[i] = validate_value(Y_value[i], Y_type, bounds) |
|
if Y_value[i] == None: |
|
return (None,None,) |
|
|
|
|
|
if X_type == "LoRA": |
|
X_value = [X_value] |
|
if Y_type == "LoRA": |
|
Y_value = [Y_value] |
|
|
|
|
|
if X_type == "Nothing": |
|
X_value = [""] |
|
if Y_type == "Nothing": |
|
Y_value = [""] |
|
|
|
return ((X_type, X_value), (Y_type, Y_value),) |
|
|
|
|
|
class TSC_XYplot_JoinInputs: |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"XY_1": ("XY",), |
|
"XY_2": ("XY",),}, |
|
} |
|
|
|
RETURN_TYPES = ("XY",) |
|
RETURN_NAMES = ("X or Y",) |
|
FUNCTION = "xy_value" |
|
CATEGORY = "Efficiency Nodes/XY Plot/XY Inputs" |
|
|
|
def xy_value(self, XY_1, XY_2): |
|
xy_type_1, xy_value_1 = XY_1 |
|
xy_type_2, xy_value_2 = XY_2 |
|
|
|
if xy_type_1 != xy_type_2: |
|
print(f"\033[31mJoin XY Inputs Error:\033[0m Input types must match") |
|
return (None,) |
|
elif xy_type_1 == "Seeds++ Batch": |
|
xy_type = xy_type_1 |
|
xy_value = [xy_value_1[0] + xy_value_2[0]] |
|
elif xy_type_1 == "Positive Prompt S/R" or xy_type_1 == "Negative Prompt S/R": |
|
xy_type = xy_type_1 |
|
xy_value = xy_value_1 + [(xy_value_1[0][0], t[1]) for t in xy_value_2[1:]] |
|
else: |
|
xy_type = xy_type_1 |
|
xy_value = xy_value_1 + xy_value_2 |
|
return ((xy_type, xy_value),) |
|
|
|
|
|
|
|
class TSC_ImageOverlay: |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return { |
|
"required": { |
|
"base_image": ("IMAGE",), |
|
"overlay_image": ("IMAGE",), |
|
"overlay_resize": (["None", "Fit", "Resize by rescale_factor", "Resize to width & heigth"],), |
|
"resize_method": (["nearest-exact", "bilinear", "area"],), |
|
"rescale_factor": ("FLOAT", {"default": 1, "min": 0.01, "max": 16.0, "step": 0.1}), |
|
"width": ("INT", {"default": 512, "min": 0, "max": MAX_RESOLUTION, "step": 64}), |
|
"height": ("INT", {"default": 512, "min": 0, "max": MAX_RESOLUTION, "step": 64}), |
|
"x_offset": ("INT", {"default": 0, "min": -48000, "max": 48000, "step": 10}), |
|
"y_offset": ("INT", {"default": 0, "min": -48000, "max": 48000, "step": 10}), |
|
"rotation": ("INT", {"default": 0, "min": -180, "max": 180, "step": 5}), |
|
"opacity": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 5}), |
|
}, |
|
"optional": {"optional_mask": ("MASK",),} |
|
} |
|
|
|
RETURN_TYPES = ("IMAGE",) |
|
FUNCTION = "apply_overlay_image" |
|
CATEGORY = "Efficiency Nodes/Image" |
|
|
|
def apply_overlay_image(self, base_image, overlay_image, overlay_resize, resize_method, rescale_factor, |
|
width, height, x_offset, y_offset, rotation, opacity, optional_mask=None): |
|
|
|
|
|
size = width, height |
|
location = x_offset, y_offset |
|
mask = optional_mask |
|
|
|
|
|
if overlay_resize != "None": |
|
|
|
overlay_image_size = overlay_image.size() |
|
overlay_image_size = (overlay_image_size[2], overlay_image_size[1]) |
|
if overlay_resize == "Fit": |
|
overlay_image_size = (base_image.size[0],base_image.size[1]) |
|
elif overlay_resize == "Resize by rescale_factor": |
|
overlay_image_size = tuple(int(dimension * rescale_factor) for dimension in overlay_image_size) |
|
elif overlay_resize == "Resize to width & heigth": |
|
overlay_image_size = (size[0], size[1]) |
|
|
|
samples = overlay_image.movedim(-1, 1) |
|
overlay_image = comfy.utils.common_upscale(samples, overlay_image_size[0], overlay_image_size[1], resize_method, False) |
|
overlay_image = overlay_image.movedim(1, -1) |
|
|
|
overlay_image = tensor2pil(overlay_image) |
|
|
|
|
|
overlay_image = overlay_image.convert('RGBA') |
|
overlay_image.putalpha(Image.new("L", overlay_image.size, 255)) |
|
|
|
|
|
if mask is not None: |
|
|
|
mask = tensor2pil(mask) |
|
mask = mask.resize(overlay_image.size) |
|
|
|
overlay_image.putalpha(ImageOps.invert(mask)) |
|
|
|
|
|
overlay_image = overlay_image.rotate(rotation, expand=True) |
|
|
|
|
|
r, g, b, a = overlay_image.split() |
|
a = a.point(lambda x: max(0, int(x * (1 - opacity / 100)))) |
|
overlay_image.putalpha(a) |
|
|
|
|
|
base_image_list = torch.unbind(base_image, dim=0) |
|
|
|
|
|
processed_base_image_list = [] |
|
for tensor in base_image_list: |
|
|
|
image = tensor2pil(tensor) |
|
|
|
|
|
if mask is None: |
|
image.paste(overlay_image, location) |
|
else: |
|
image.paste(overlay_image, location, overlay_image) |
|
|
|
|
|
processed_tensor = pil2tensor(image) |
|
|
|
|
|
processed_base_image_list.append(processed_tensor) |
|
|
|
|
|
base_image = torch.stack([tensor.squeeze() for tensor in processed_base_image_list]) |
|
|
|
|
|
return (base_image,) |
|
|
|
|
|
|
|
def install_simpleeval(): |
|
if 'simpleeval' not in packages(): |
|
print("\033[32mEfficiency Nodes:\033[0m") |
|
subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'simpleeval']) |
|
|
|
def packages(versions=False): |
|
return [(r.decode().split('==')[0] if not versions else r.decode()) for r in subprocess.check_output([sys.executable, '-m', 'pip', 'freeze']).split()] |
|
|
|
install_simpleeval() |
|
from simpleeval import simple_eval |
|
|
|
|
|
class TSC_EvaluateInts: |
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"python_expression": ("STRING", {"default": "((a + b) - c) / 2", "multiline": False}), |
|
"print_to_console": (["False", "True"],),}, |
|
"optional": { |
|
"a": ("INT", {"default": 0, "min": -48000, "max": 48000, "step": 1}), |
|
"b": ("INT", {"default": 0, "min": -48000, "max": 48000, "step": 1}), |
|
"c": ("INT", {"default": 0, "min": -48000, "max": 48000, "step": 1}),}, |
|
} |
|
RETURN_TYPES = ("INT", "FLOAT", "STRING",) |
|
OUTPUT_NODE = True |
|
FUNCTION = "evaluate" |
|
CATEGORY = "Efficiency Nodes/Simple Eval" |
|
|
|
def evaluate(self, python_expression, print_to_console, a=0, b=0, c=0): |
|
|
|
result = simple_eval(python_expression, names={'a': a, 'b': b, 'c': c}) |
|
int_result = int(result) |
|
float_result = float(result) |
|
string_result = str(result) |
|
if print_to_console == "True": |
|
print("\n\033[31mEvaluate Integers:\033[0m") |
|
print(f"\033[90m{{a = {a} , b = {b} , c = {c}}} \033[0m") |
|
print(f"{python_expression} = \033[92m INT: " + str(int_result) + " , FLOAT: " + str( |
|
float_result) + ", STRING: " + string_result + "\033[0m") |
|
return (int_result, float_result, string_result,) |
|
|
|
|
|
class TSC_EvaluateFloats: |
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"python_expression": ("STRING", {"default": "((a + b) - c) / 2", "multiline": False}), |
|
"print_to_console": (["False", "True"],),}, |
|
"optional": { |
|
"a": ("FLOAT", {"default": 0, "min": -sys.float_info.max, "max": sys.float_info.max, "step": 1}), |
|
"b": ("FLOAT", {"default": 0, "min": -sys.float_info.max, "max": sys.float_info.max, "step": 1}), |
|
"c": ("FLOAT", {"default": 0, "min": -sys.float_info.max, "max": sys.float_info.max, "step": 1}),}, |
|
} |
|
RETURN_TYPES = ("INT", "FLOAT", "STRING",) |
|
OUTPUT_NODE = True |
|
FUNCTION = "evaluate" |
|
CATEGORY = "Efficiency Nodes/Simple Eval" |
|
|
|
def evaluate(self, python_expression, print_to_console, a=0, b=0, c=0): |
|
|
|
result = simple_eval(python_expression, names={'a': a, 'b': b, 'c': c}) |
|
int_result = int(result) |
|
float_result = float(result) |
|
string_result = str(result) |
|
if print_to_console == "True": |
|
print("\n\033[31mEvaluate Floats:\033[0m") |
|
print(f"\033[90m{{a = {a} , b = {b} , c = {c}}} \033[0m") |
|
print(f"{python_expression} = \033[92m INT: " + str(int_result) + " , FLOAT: " + str( |
|
float_result) + ", STRING: " + string_result + "\033[0m") |
|
return (int_result, float_result, string_result,) |
|
|
|
|
|
class TSC_EvaluateStrs: |
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { |
|
"python_expression": ("STRING", {"default": "a + b + c", "multiline": False}), |
|
"print_to_console": (["False", "True"],)}, |
|
"optional": { |
|
"a": ("STRING", {"default": "Hello", "multiline": False}), |
|
"b": ("STRING", {"default": " World", "multiline": False}), |
|
"c": ("STRING", {"default": "!", "multiline": False}),} |
|
} |
|
RETURN_TYPES = ("STRING",) |
|
OUTPUT_NODE = True |
|
FUNCTION = "evaluate" |
|
CATEGORY = "Efficiency Nodes/Simple Eval" |
|
|
|
def evaluate(self, python_expression, print_to_console, a="", b="", c=""): |
|
variables = {'a': a, 'b': b, 'c': c} |
|
functions = {"len": len} |
|
result = simple_eval(python_expression, names=variables, functions=functions) |
|
if print_to_console == "True": |
|
print("\n\033[31mEvaluate Strings:\033[0m") |
|
print(f"\033[90ma = {a} \nb = {b} \nc = {c}\033[0m") |
|
print(f"{python_expression} = \033[92m" + str(result) + "\033[0m") |
|
return (str(result),) |
|
|
|
|
|
class TSC_EvalExamples: |
|
filepath = os.path.join(my_dir, 'workflows', 'SimpleEval_Node_Examples.txt') |
|
with open(filepath, 'r') as file: |
|
examples = file.read() |
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return {"required": { "models_text": ("STRING", {"default": cls.examples ,"multiline": True}),},} |
|
RETURN_TYPES = () |
|
CATEGORY = "Efficiency Nodes/Simple Eval" |
|
|
|
|
|
NODE_CLASS_MAPPINGS = { |
|
"KSampler (Efficient)": TSC_KSampler, |
|
"Efficient Loader": TSC_EfficientLoader, |
|
"LoRA Stacker": TSC_LoRA_Stacker, |
|
"LoRA Stacker Adv.": TSC_LoRA_Stacker_Adv, |
|
"XY Plot": TSC_XYplot, |
|
"XY Input: Seeds++ Batch": TSC_XYplot_SeedsBatch, |
|
"XY Input: Steps": TSC_XYplot_Steps, |
|
"XY Input: CFG Scale": TSC_XYplot_CFG, |
|
"XY Input: Sampler": TSC_XYplot_Sampler, |
|
"XY Input: Scheduler": TSC_XYplot_Scheduler, |
|
"XY Input: Denoise": TSC_XYplot_Denoise, |
|
"XY Input: VAE": TSC_XYplot_VAE, |
|
"XY Input: Positive Prompt S/R": TSC_XYplot_PromptSR_Positive, |
|
"XY Input: Negative Prompt S/R": TSC_XYplot_PromptSR_Negative, |
|
"XY Input: Checkpoint": TSC_XYplot_Checkpoint, |
|
"XY Input: Clip Skip": TSC_XYplot_ClipSkip, |
|
"XY Input: LoRA": TSC_XYplot_LoRA, |
|
"XY Input: LoRA Adv.": TSC_XYplot_LoRA_Adv, |
|
"XY Input: LoRA Stacks": TSC_XYplot_LoRA_Stacks, |
|
"XY Input: Manual XY Entry": TSC_XYplot_Manual_XY_Entry, |
|
"Manual XY Entry Info": TSC_XYplot_Manual_XY_Entry_Info, |
|
"Join XY Inputs of Same Type": TSC_XYplot_JoinInputs, |
|
"Image Overlay": TSC_ImageOverlay, |
|
"Evaluate Integers": TSC_EvaluateInts, |
|
"Evaluate Floats": TSC_EvaluateFloats, |
|
"Evaluate Strings": TSC_EvaluateStrs, |
|
"Simple Eval Examples": TSC_EvalExamples |
|
} |