diff --git a/README.en.md b/README.en.md
index fc61421..a57f1c5 100644
--- a/README.en.md
+++ b/README.en.md
@@ -27,11 +27,23 @@ To use the Lora Block Weight usage, make sure that [ComfyUI-Inspire-Pack](https:
After installing the node package, the UI interface will be automatically switched, if you need to change other themes, please switch and refresh the page in Settings -> Color Palette.
+### Stable Cascade
+
+[WorkFlow Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#StableCascade)
+Currently, txt2img and img2img are supported,Lora and Controlnet are comming soon!
+
+Usage:
+1.There is no need to load the rest of the VAE and clips when you are choose [checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints) models.
+2.You need to load it extra [stage_a](https://huggingface.co/stabilityai/stable-cascade/blob/main/stage_a.safetensors)、[clip](https://huggingface.co/stabilityai/stable-cascade/resolve/main/text_encoder/model.safetensors) and [effnet_encoder](https://huggingface.co/stabilityai/stable-cascade/resolve/main/effnet_encoder.safetensors?download=true)、[previewer](https://huggingface.co/stabilityai/stable-cascade/resolve/main/previewer.safetensors) for img2img when you are choose unet models.
+
+
## Changelog
-**2024-02-20**
+**v1.0.8(2024-02-25)**
-- Optimize the image to image[Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#image-to-image)
+- `easy cascadeLoader` stage_c and stage_b support the checkpoint model (Download [checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints) models)
+- `easy styleSelector` The search box is modified to be case-insensitive
+- Optimize the code, reduce a lot of redundant code and improve the running speed
**v1.0.7 (2024-02-19)**
@@ -39,8 +51,8 @@ After installing the node package, the UI interface will be automatically switch
- Added `easy preSamplingCascade` - stable cascade preSampling Settings
- Added `easy fullCascadeKSampler` - stable cascade stage-c ksampler full
- Added `easy cascadeKSampler` - stable cascade stage-c ksampler simple
-
-[SC Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#StableCascade)
+-
+- Optimize the image to image[Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#image-to-image)
**v1.0.6**
diff --git a/README.md b/README.md
index ea3b3c8..dfe5e06 100644
--- a/README.md
+++ b/README.md
@@ -35,22 +35,31 @@
如果您下载了 [AIGODLIKE-COMFYUI-TRANSLATION](https://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Translation)
, 程序将在启动时拷贝中文对照文件至该节点包目录下,当您选择语言为中文时即可看到已更改后的简体中文节点。
+### Stable Cascade
+
+[工作流示例](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#StableCascade)
+目前支持文生图与图生图,还未支持Lora和Controlnet,敬请期待!
+stage_c 与 stage_b 可以使用[checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints)模型或原来的unet模型
+
+使用方式:
+1.选择[checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints)模型无需额外加载其余的VAE及clip
+2.选择Unet模型的话需要额外加载[stage_a](https://huggingface.co/stabilityai/stable-cascade/blob/main/stage_a.safetensors)、[clip](https://huggingface.co/stabilityai/stable-cascade/resolve/main/text_encoder/model.safetensors)及图生图需要用到的[effnet_encoder](https://huggingface.co/stabilityai/stable-cascade/resolve/main/effnet_encoder.safetensors?download=true)和[previewer](https://huggingface.co/stabilityai/stable-cascade/resolve/main/previewer.safetensors)。
+
## 更新日志
-**2024-02-20**
+**v1.0.8(2024-02-25)**
-- 优化图生图流程[示例](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#图生图)
+- `easy cascadeLoader` stage_c 与 stage_b 支持checkpoint模型 (需要下载[checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints))
+- `easy styleSelector` 搜索框修改为不区分大小写匹配
+- 优化代码,减少了诸多冗余,提升运行速度
-**v1.0.7 (2024-02-18)**
+**v1.0.7**
- 增加 `easy cascadeLoader` - stable cascade 加载器
- 增加 `easy preSamplingCascade` - stabled cascade stage_c 预采样参数
- 增加 `easy fullCascadeKSampler` - stable cascade stage_c 完整版采样器
- 增加 `easy cascadeKSampler` - stable cascade stage-c ksampler simple
-
-[Stable Cascade](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#StableCascade)
-目前支持 文生图与图生图,还未支持Lora和Controlnet,敬请期待!
-02-17 前更新的用户,需将原工程中的`easy preSamplingCascade`更换成`easy fullCascadeKSampler` 重新连接进行使用。
+- 优化图生图流程[示例](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#图生图)
**v1.0.6 (2024-02-16)**
@@ -86,7 +95,8 @@
- 修改首次安装节点包不再自动替换主题,需手动调整并刷新页面
-**v1.0.3**
+
+v1.0.3
- 增加 `easy stylesSelector` 风格化提示词选择器
- 增加队列进度条设置项,默认为未启用状态
@@ -104,7 +114,7 @@
- 调整UI主题
-
+
v1.0.2
diff --git a/__init__.py b/__init__.py
index 0661d80..948c17a 100644
--- a/__init__.py
+++ b/__init__.py
@@ -10,7 +10,6 @@ node_list = [
"api",
"easyNodes",
"image",
- "lllite",
"logic"
]
@@ -87,4 +86,4 @@ WEB_DIRECTORY = "./web"
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', "WEB_DIRECTORY"]
-print('\033[34mComfy-Easy-Use (v1.0.7): \033[92mLoaded\033[0m')
\ No newline at end of file
+print('\033[34mComfy-Easy-Use (v1.0.8): \033[92mLoaded\033[0m')
\ No newline at end of file
diff --git a/py/adv_encode.py b/py/adv_encode.py
index e04a785..db90c8b 100644
--- a/py/adv_encode.py
+++ b/py/adv_encode.py
@@ -1,11 +1,12 @@
import torch
import numpy as np
import itertools
-from math import gcd
from comfy import model_management
from comfy.sdxl_clip import SDXLClipModel, SDXLRefinerClipModel, SDXLClipG
+from nodes import NODE_CLASS_MAPPINGS
+
def _grouper(n, iterable):
it = iter(iterable)
@@ -258,7 +259,17 @@ def prepareXL(embs_l, embs_g, pooled, clip_balance):
def advanced_encode(clip, text, token_normalization, weight_interpretation, w_max=1.0, clip_balance=.5,
- apply_to_pooled=True, width=1024, height=1024, crop_w=0, crop_h=0, target_width=1024, target_height=1024):
+ apply_to_pooled=True, width=1024, height=1024, crop_w=0, crop_h=0, target_width=1024, target_height=1024, a1111_prompt_style=False, steps=1):
+
+ # Use clip text encode by smzNodes like same as a1111, when if you need installed the smzNodes
+ if a1111_prompt_style:
+ if "smZ CLIPTextEncode" in NODE_CLASS_MAPPINGS:
+ cls = NODE_CLASS_MAPPINGS['smZ CLIPTextEncode']
+ embeddings_final, = cls().encode(clip, text,weight_interpretation, True, True, False, False, 6, 1024, 1024, 0, 0, 1024, 1024, '', '', steps)
+ return embeddings_final
+ else:
+ raise Exception(f"[smzNodes Not Found] you need to install 'ComfyUI-smzNodes'")
+
tokenized = clip.tokenize(text, return_word_ids=True)
if isinstance(clip.cond_stage_model, (SDXLClipModel, SDXLRefinerClipModel, SDXLClipG)):
embs_l = None
@@ -293,32 +304,3 @@ def advanced_encode(clip, text, token_normalization, weight_interpretation, w_ma
return [[embeddings_final, {"pooled_output": pooled}]]
-def advanced_encode_XL(clip, text1, text2, token_normalization, weight_interpretation, w_max=1.0, clip_balance=.5,
- apply_to_pooled=True,width=1024, height=1024, crop_w=0, crop_h=0, target_width=1024, target_height=1024):
- tokenized1 = clip.tokenize(text1, return_word_ids=True)
- tokenized2 = clip.tokenize(text2, return_word_ids=True)
-
- embs_l, _ = advanced_encode_from_tokens(tokenized1['l'],
- token_normalization,
- weight_interpretation,
- lambda x: encode_token_weights(clip, x, encode_token_weights_l),
- w_max=w_max,
- return_pooled=False)
-
- embs_g, pooled = advanced_encode_from_tokens(tokenized2['g'],
- token_normalization,
- weight_interpretation,
- lambda x: encode_token_weights(clip, x, encode_token_weights_g),
- w_max=w_max,
- return_pooled=True,
- apply_to_pooled=apply_to_pooled)
-
- gcd_num = gcd(embs_l.shape[1], embs_g.shape[1])
- repeat_l = int((embs_g.shape[1] / gcd_num) * embs_l.shape[1])
- repeat_g = int((embs_l.shape[1] / gcd_num) * embs_g.shape[1])
-
- embeddings_final, pooled = prepareXL(embs_l.expand((-1, repeat_l, -1)), embs_g.expand((-1, repeat_g, -1)), pooled, clip_balance)
-
- return [[embeddings_final,
- {"pooled_output": pooled, "width": width, "height": height, "crop_w": crop_w, "crop_h": crop_h,
- "target_width": target_width, "target_height": target_height}]]
\ No newline at end of file
diff --git a/py/config.py b/py/config.py
index 1f7ab17..4512320 100644
--- a/py/config.py
+++ b/py/config.py
@@ -32,6 +32,7 @@ BASE_RESOLUTIONS = [
(1920, 1080),
(2560, 1440),
]
+MAX_SEED_NUM = 1125899906842624
INPAINT_DIR = os.path.join(folder_paths.models_dir, "inpaint")
@@ -56,8 +57,4 @@ FOOOCUS_INPAINT_PATCH = {
"inpaint (1.32GB)": {
"model_url": "https://huggingface.co/lllyasviel/fooocus_inpaint/resolve/main/inpaint.fooocus.patch"
},
-}
-
-OPEN_CLIP = {
-
}
\ No newline at end of file
diff --git a/py/easyNodes.py b/py/easyNodes.py
index 203e656..5c63394 100644
--- a/py/easyNodes.py
+++ b/py/easyNodes.py
@@ -1,1206 +1,29 @@
-import sys
-import os
-import re
-import json
-import time
-import math
+import sys, os, re, json, time, math
import torch
-import torchvision
-import psutil
-import datetime
-import comfy.sd
-import comfy.utils
-import numpy as np
import folder_paths
-import comfy.samplers
-import comfy.controlnet
-import latent_preview
-import comfy.model_base
-import comfy.model_management
+import comfy.utils, comfy.samplers, comfy.controlnet, comfy.model_base, comfy.model_management
from comfy.sd import CLIP, VAE
-from pathlib import Path
-from urllib.request import urlopen
-from collections import defaultdict
-from PIL.PngImagePlugin import PngInfo
-from PIL import Image, ImageDraw, ImageFont
from comfy.model_patcher import ModelPatcher
from comfy_extras.chainner_models import model_loading
-from typing import Dict, List, Optional, Tuple, Union, Any
-from .adv_encode import advanced_encode, advanced_encode_XL
+from comfy_extras.nodes_mask import LatentCompositeMasked
+from urllib.request import urlopen
+from PIL import Image
from server import PromptServer
-from nodes import VAELoader, MAX_RESOLUTION, RepeatLatentBatch, NODE_CLASS_MAPPINGS as ALL_NODE_CLASS_MAPPINGS, ConditioningSetMask, ConditioningConcat, PreviewImage, SaveImage
-from comfy_extras.nodes_mask import LatentCompositeMasked
-from .config import BASE_RESOLUTIONS, RESOURCES_DIR, INPAINT_DIR, FOOOCUS_STYLES_DIR, FOOOCUS_INPAINT_HEAD, FOOOCUS_INPAINT_PATCH
-from .log import log_node_info, log_node_error, log_node_warn, log_node_success
+from nodes import MAX_RESOLUTION, RepeatLatentBatch, NODE_CLASS_MAPPINGS as ALL_NODE_CLASS_MAPPINGS, ConditioningSetMask, ConditioningConcat, CLIPTextEncode
+from .config import MAX_SEED_NUM, BASE_RESOLUTIONS, RESOURCES_DIR, INPAINT_DIR, FOOOCUS_STYLES_DIR, FOOOCUS_INPAINT_HEAD, FOOOCUS_INPAINT_PATCH
+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
-# 加载器
-class easyLoader:
- def __init__(self):
- self.loaded_objects = {
- "ckpt": defaultdict(tuple), # {ckpt_name: (model, ...)}
- "unet": defaultdict(tuple),
- "clip": defaultdict(tuple),
- "clip_vision": defaultdict(tuple),
- "bvae": defaultdict(tuple),
- "vae": defaultdict(object),
- "lora": defaultdict(dict), # {lora_name: {UID: (model_lora, clip_lora)}}
- }
- self.memory_threshold = self.determine_memory_threshold(0.7)
+from .libs.utils import find_nearest_steps, find_wildcards_seed, is_linked_styles_selector, easySave, get_local_filepath
+from .libs.loader import easyLoader
+from .libs.sampler import easySampler
+from .libs.xyplot import easyXYPlot
+from .libs.controlnet import easyControlnet
- def clean_values(self, values: str):
- original_values = values.split("; ")
- cleaned_values = []
-
- for value in original_values:
- cleaned_value = value.strip(';').strip()
-
- if cleaned_value == "":
- continue
-
- try:
- cleaned_value = int(cleaned_value)
- except ValueError:
- try:
- cleaned_value = float(cleaned_value)
- except ValueError:
- pass
-
- cleaned_values.append(cleaned_value)
-
- return cleaned_values
-
- def clear_unused_objects(self, desired_names: set, object_type: str):
- keys = set(self.loaded_objects[object_type].keys())
- for key in keys - desired_names:
- del self.loaded_objects[object_type][key]
-
- def get_input_value(self, entry, key):
- val = entry["inputs"][key]
- return val if isinstance(val, str) else val[0]
-
- def process_pipe_loader(self, entry,
- desired_ckpt_names, desired_vae_names,
- desired_lora_names, desired_lora_settings, num_loras=3, suffix=""):
- for idx in range(1, num_loras + 1):
- lora_name_key = f"{suffix}lora{idx}_name"
- desired_lora_names.add(self.get_input_value(entry, lora_name_key))
- setting = f'{self.get_input_value(entry, lora_name_key)};{entry["inputs"][f"{suffix}lora{idx}_model_strength"]};{entry["inputs"][f"{suffix}lora{idx}_clip_strength"]}'
- desired_lora_settings.add(setting)
-
- desired_ckpt_names.add(self.get_input_value(entry, f"{suffix}ckpt_name"))
- desired_vae_names.add(self.get_input_value(entry, f"{suffix}vae_name"))
-
- def update_loaded_objects(self, prompt):
- desired_ckpt_names = set()
- desired_unet_names = set()
- desired_clip_names = set()
- desired_vae_names = set()
- desired_lora_names = set()
- desired_lora_settings = set()
-
- for entry in prompt.values():
- class_type = entry["class_type"]
-
- if class_type == "easy a1111Loader" or class_type == "easy comfyLoader":
- lora_name = self.get_input_value(entry, "lora_name")
- desired_lora_names.add(lora_name)
- setting = f'{lora_name};{entry["inputs"]["lora_model_strength"]};{entry["inputs"]["lora_clip_strength"]}'
- desired_lora_settings.add(setting)
-
- desired_ckpt_names.add(self.get_input_value(entry, "ckpt_name"))
- desired_vae_names.add(self.get_input_value(entry, "vae_name"))
- elif class_type == "easy zero123Loader" or class_type == 'easy svdLoader':
- desired_ckpt_names.add(self.get_input_value(entry, "ckpt_name"))
- desired_vae_names.add(self.get_input_value(entry, "vae_name"))
-
- elif class_type == "easy cascadeLoader":
- desired_unet_names.add(self.get_input_value(entry, "stage_c"))
- desired_unet_names.add(self.get_input_value(entry, "stage_b"))
- desired_clip_names.add(self.get_input_value(entry, "clip_name"))
- desired_vae_names.add(self.get_input_value(entry, "stage_a"))
-
- elif class_type == "easy preSamplingCascade" or class_type == 'easy fullCascadeKSampler':
- encode_vae_name = self.get_input_value(entry, "encode_vae_name")
- decode_vae_name = self.get_input_value(entry, "decode_vae_name")
- if encode_vae_name and encode_vae_name != 'None':
- desired_vae_names.add(encode_vae_name)
- if decode_vae_name and decode_vae_name != 'None':
- desired_vae_names.add(decode_vae_name)
-
- elif class_type == "easy XYInputs: ModelMergeBlocks":
- desired_ckpt_names.add(self.get_input_value(entry, "ckpt_name_1"))
- desired_ckpt_names.add(self.get_input_value(entry, "ckpt_name_2"))
- vae_use = self.get_input_value(entry, "vae_use")
- if vae_use != 'Use Model 1' and vae_use != 'Use Model 2':
- desired_vae_names.add(vae_use)
-
- object_types = ["ckpt", "unet", "clip", "bvae", "vae", "lora"]
- for object_type in object_types:
- if object_type == 'unet':
- desired_names = desired_unet_names
- elif object_type in ["ckpt", "clip", "bvae"]:
- if object_type == 'clip':
- desired_names = desired_ckpt_names.union(desired_clip_names)
- else:
- desired_names = desired_ckpt_names
- elif object_type == "vae":
- desired_names = desired_vae_names
- else:
- desired_names = desired_lora_names
- self.clear_unused_objects(desired_names, object_type)
-
- def add_to_cache(self, obj_type, key, value):
- """
- Add an item to the cache with the current timestamp.
- """
- timestamped_value = (value, time.time())
- self.loaded_objects[obj_type][key] = timestamped_value
-
- def determine_memory_threshold(self, percentage=0.8):
- """
- Determines the memory threshold as a percentage of the total available memory.
-
- Args:
- - percentage (float): The fraction of total memory to use as the threshold.
- Should be a value between 0 and 1. Default is 0.8 (80%).
-
- Returns:
- - memory_threshold (int): Memory threshold in bytes.
- """
- total_memory = psutil.virtual_memory().total
- memory_threshold = total_memory * percentage
- return memory_threshold
-
- def get_memory_usage(self):
- """
- Returns the memory usage of the current process in bytes.
- """
- process = psutil.Process(os.getpid())
- return process.memory_info().rss
-
- def eviction_based_on_memory(self):
- """
- Evicts objects from cache based on memory usage and priority.
- """
-
- current_memory = self.get_memory_usage()
-
-
- if current_memory < self.memory_threshold:
- return
-
- eviction_order = ["vae", "lora", "bvae", "clip", "ckpt"]
-
- for obj_type in eviction_order:
-
- if current_memory < self.memory_threshold:
- break
-
- # Sort items based on age (using the timestamp)
- items = list(self.loaded_objects[obj_type].items())
- items.sort(key=lambda x: x[1][1]) # Sorting by timestamp
-
-
- for item in items:
- if current_memory < self.memory_threshold:
- break
-
- del self.loaded_objects[obj_type][item[0]]
- current_memory = self.get_memory_usage()
-
- def load_checkpoint(self, ckpt_name, config_name=None, load_vision=False):
- cache_name = ckpt_name
- if config_name not in [None, "Default"]:
- cache_name = ckpt_name + "_" + config_name
- if cache_name in self.loaded_objects["ckpt"]:
- cache_out = self.loaded_objects["clip_vision"][cache_name][0] if load_vision else self.loaded_objects["clip"][cache_name][0]
- return self.loaded_objects["ckpt"][cache_name][0], cache_out, self.loaded_objects["bvae"][cache_name][0]
-
- ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
-
- output_clip = False if load_vision else True
- output_clipvision = True if load_vision else False
- if config_name not in [None, "Default"]:
- config_path = folder_paths.get_full_path("configs", config_name)
- loaded_ckpt = comfy.sd.load_checkpoint(config_path, ckpt_path, output_vae=True, output_clip=output_clip, embedding_directory=folder_paths.get_folder_paths("embeddings"))
- else:
- loaded_ckpt = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=output_clip, output_clipvision=output_clipvision, embedding_directory=folder_paths.get_folder_paths("embeddings"))
-
- self.add_to_cache("ckpt", cache_name, loaded_ckpt[0])
- self.add_to_cache("bvae", cache_name, loaded_ckpt[2])
- if load_vision:
- out = loaded_ckpt[3]
- self.add_to_cache("clip_vision", cache_name, out)
- else:
- out = loaded_ckpt[1]
- self.add_to_cache("clip", cache_name, loaded_ckpt[1])
-
- self.eviction_based_on_memory()
-
- return loaded_ckpt[0], out, loaded_ckpt[2]
-
- def load_vae(self, vae_name):
- if vae_name in self.loaded_objects["vae"]:
- return self.loaded_objects["vae"][vae_name][0]
-
- vae_path = folder_paths.get_full_path("vae", vae_name)
- sd = comfy.utils.load_torch_file(vae_path)
- loaded_vae = comfy.sd.VAE(sd=sd)
- self.add_to_cache("vae", vae_name, loaded_vae)
- self.eviction_based_on_memory()
-
- return loaded_vae
-
- def load_unet(self, unet_name):
- if unet_name in self.loaded_objects["unet"]:
- return self.loaded_objects["unet"][unet_name][0]
-
- unet_path = folder_paths.get_full_path("unet", unet_name)
- model = comfy.sd.load_unet(unet_path)
- self.add_to_cache("unet", unet_name, model)
- self.eviction_based_on_memory()
-
- return model
-
- def load_clip(self, clip_name, type='stable_diffusion'):
- if type == 'stable_diffusion':
- clip_type = comfy.sd.CLIPType.STABLE_DIFFUSION
- else:
- clip_type = comfy.sd.CLIPType.STABLE_CASCADE
- clip_path = folder_paths.get_full_path("clip", clip_name)
- load_clip = comfy.sd.load_clip(ckpt_paths=[clip_path],
- embedding_directory=folder_paths.get_folder_paths("embeddings"), clip_type=clip_type)
- self.add_to_cache("clip", clip_name, load_clip)
- self.eviction_based_on_memory()
-
- return load_clip
-
- def load_lora(self, lora_name, model, clip, strength_model, strength_clip):
- model_hash = str(model)[44:-1]
- clip_hash = str(clip)[25:-1]
-
- unique_id = f'{model_hash};{clip_hash};{lora_name};{strength_model};{strength_clip}'
-
- if unique_id in self.loaded_objects["lora"] and unique_id in self.loaded_objects["lora"][lora_name]:
- return self.loaded_objects["lora"][unique_id][0]
-
- lora_path = folder_paths.get_full_path("loras", lora_name)
- lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
- model_lora, clip_lora = comfy.sd.load_lora_for_models(model, clip, lora, strength_model, strength_clip)
-
- self.add_to_cache("lora", unique_id, (model_lora, clip_lora))
- self.eviction_based_on_memory()
-
- return model_lora, clip_lora
-
-# 采样器
-class easySampler:
- def __init__(self):
- self.last_helds: dict[str, list] = {
- "results": [],
- "pipe_line": [],
- }
-
- @staticmethod
- def tensor2pil(image: torch.Tensor) -> Image.Image:
- """Convert a torch tensor to a PIL image."""
- return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
-
- @staticmethod
- def pil2tensor(image: Image.Image) -> torch.Tensor:
- """Convert a PIL image to a torch tensor."""
- return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
-
- @staticmethod
- def enforce_mul_of_64(d):
- d = int(d)
- if d <= 7:
- d = 8
- leftover = d % 8 # 8 is the number of pixels per byte
- if leftover != 0: # if the number of pixels is not a multiple of 8
- if (leftover < 4): # if the number of pixels is less than 4
- d -= leftover # remove the leftover pixels
- else: # if the number of pixels is more than 4
- d += 8 - leftover # add the leftover pixels
-
- return int(d)
-
- @staticmethod
- def safe_split(to_split: str, delimiter: str) -> List[str]:
- """Split the input string and return a list of non-empty parts."""
- parts = to_split.split(delimiter)
- parts = [part for part in parts if part not in ('', ' ', ' ')]
-
- while len(parts) < 2:
- parts.append('None')
- return parts
-
- def common_ksampler(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent, denoise=1.0,
- disable_noise=False, start_step=None, last_step=None, force_full_denoise=False,
- preview_latent=True, disable_pbar=False):
- device = comfy.model_management.get_torch_device()
- latent_image = latent["samples"]
-
- if disable_noise:
- noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, device="cpu")
- else:
- batch_inds = latent["batch_index"] if "batch_index" in latent else None
- noise = comfy.sample.prepare_noise(latent_image, seed, batch_inds)
-
- noise_mask = None
- if "noise_mask" in latent:
- noise_mask = latent["noise_mask"]
-
- preview_format = "JPEG"
- if preview_format not in ["JPEG", "PNG"]:
- preview_format = "JPEG"
-
- previewer = False
-
- if preview_latent:
- previewer = latent_preview.get_previewer(device, model.model.latent_format)
-
- pbar = comfy.utils.ProgressBar(steps)
-
- def callback(step, x0, x, total_steps):
- preview_bytes = None
- if previewer:
- preview_bytes = previewer.decode_latent_to_preview_image(preview_format, x0)
- pbar.update_absolute(step + 1, total_steps, preview_bytes)
-
- samples = comfy.sample.sample(model, noise, steps, cfg, sampler_name, scheduler, positive, negative,
- latent_image,
- denoise=denoise, disable_noise=disable_noise, start_step=start_step,
- last_step=last_step,
- force_full_denoise=force_full_denoise, noise_mask=noise_mask, callback=callback,
- disable_pbar=disable_pbar, seed=seed)
-
- out = latent.copy()
- out["samples"] = samples
- return out
-
- def custom_ksampler(self, model, seed, steps, cfg, _sampler, sigmas, positive, negative, latent,
- disable_noise=False, preview_latent=True, disable_pbar=False):
-
- device = comfy.model_management.get_torch_device()
- latent_image = latent["samples"]
-
- if disable_noise:
- noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, device="cpu")
- else:
- batch_inds = latent["batch_index"] if "batch_index" in latent else None
- noise = comfy.sample.prepare_noise(latent_image, seed, batch_inds)
-
- noise_mask = None
- if "noise_mask" in latent:
- noise_mask = latent["noise_mask"]
-
- preview_format = "JPEG"
- if preview_format not in ["JPEG", "PNG"]:
- preview_format = "JPEG"
-
- previewer = False
-
- if preview_latent:
- previewer = latent_preview.get_previewer(device, model.model.latent_format)
-
- pbar = comfy.utils.ProgressBar(steps)
-
- def callback(step, x0, x, total_steps):
- preview_bytes = None
- if previewer:
- preview_bytes = previewer.decode_latent_to_preview_image(preview_format, x0)
- pbar.update_absolute(step + 1, total_steps, preview_bytes)
-
- samples = comfy.sample.sample_custom(model, noise, cfg, _sampler, sigmas, positive, negative, latent_image,
- noise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar,
- seed=seed)
-
- out = latent.copy()
- out["samples"] = samples
- return out
-
- def get_value_by_id(self, key: str, my_unique_id: Any) -> Optional[Any]:
- """Retrieve value by its associated ID."""
- try:
- for value, id_ in self.last_helds[key]:
- if id_ == my_unique_id:
- return value
- except KeyError:
- return None
-
- def update_value_by_id(self, key: str, my_unique_id: Any, new_value: Any) -> Union[bool, None]:
- """Update the value associated with a given ID. Return True if updated, False if appended, None if key doesn't exist."""
- try:
- for i, (value, id_) in enumerate(self.last_helds[key]):
- if id_ == my_unique_id:
- self.last_helds[key][i] = (new_value, id_)
- return True
- self.last_helds[key].append((new_value, my_unique_id))
- return False
- except KeyError:
- return False
-
- def upscale(self, samples, upscale_method, scale_by, crop):
- s = samples.copy()
- width = self.enforce_mul_of_64(round(samples["samples"].shape[3] * scale_by))
- height = self.enforce_mul_of_64(round(samples["samples"].shape[2] * scale_by))
-
- if (width > MAX_RESOLUTION):
- width = MAX_RESOLUTION
- if (height > MAX_RESOLUTION):
- height = MAX_RESOLUTION
-
- s["samples"] = comfy.utils.common_upscale(samples["samples"], width, height, upscale_method, crop)
- return (s,)
-
- def handle_upscale(self, samples: dict, upscale_method: str, factor: float, crop: bool) -> dict:
- """Upscale the samples if the upscale_method is not set to 'None'."""
- if upscale_method != "None":
- samples = self.upscale(samples, upscale_method, factor, crop)[0]
- return samples
-
- def init_state(self, my_unique_id: Any, key: str, default: Any) -> Any:
- """Initialize the state by either fetching the stored value or setting a default."""
- value = self.get_value_by_id(key, my_unique_id)
- if value is not None:
- return value
- return default
-
- def get_output(self, pipe: dict,) -> Tuple:
- """Return a tuple of various elements fetched from the input pipe dictionary."""
- return (
- pipe,
- pipe.get("images"),
- pipe.get("model"),
- pipe.get("positive"),
- pipe.get("negative"),
- pipe.get("samples"),
- pipe.get("vae"),
- pipe.get("clip"),
- pipe.get("seed"),
- )
-
- def get_output_sdxl(self, sdxl_pipe: dict) -> Tuple:
- """Return a tuple of various elements fetched from the input sdxl_pipe dictionary."""
- return (
- sdxl_pipe,
- sdxl_pipe.get("model"),
- sdxl_pipe.get("positive"),
- sdxl_pipe.get("negative"),
- sdxl_pipe.get("vae"),
- sdxl_pipe.get("refiner_model"),
- sdxl_pipe.get("refiner_positive"),
- sdxl_pipe.get("refiner_negative"),
- sdxl_pipe.get("refiner_vae"),
- sdxl_pipe.get("samples"),
- sdxl_pipe.get("clip"),
- sdxl_pipe.get("images"),
- sdxl_pipe.get("seed")
- )
-
-# XY图表
-class easyXYPlot:
- def __init__(self, xyPlotData, save_prefix, image_output, prompt, extra_pnginfo, my_unique_id):
- self.x_node_type, self.x_type = easySampler.safe_split(xyPlotData.get("x_axis"), ': ')
- self.y_node_type, self.y_type = easySampler.safe_split(xyPlotData.get("y_axis"), ': ')
- self.x_values = xyPlotData.get("x_vals") if self.x_type != "None" else []
- self.y_values = xyPlotData.get("y_vals") if self.y_type != "None" else []
-
- self.grid_spacing = xyPlotData.get("grid_spacing")
- self.latent_id = 0
- self.output_individuals = xyPlotData.get("output_individuals")
-
- self.x_label, self.y_label = [], []
- self.max_width, self.max_height = 0, 0
- self.latents_plot = []
- self.image_list = []
-
- self.num_cols = len(self.x_values) if len(self.x_values) > 0 else 1
- self.num_rows = len(self.y_values) if len(self.y_values) > 0 else 1
-
- self.total = self.num_cols * self.num_rows
- self.num = 0
-
- self.save_prefix = save_prefix
- self.image_output = image_output
- self.prompt = prompt
- self.extra_pnginfo = extra_pnginfo
- self.my_unique_id = my_unique_id
-
- # Helper Functions
- @staticmethod
- def define_variable(plot_image_vars, value_type, value, index):
-
-
- plot_image_vars[value_type] = value
- if value_type in ["seed", "Seeds++ Batch"]:
- value_label = f"{value}"
- else:
- value_label = f"{value_type}: {value}"
-
- if "ControlNet" in value_type:
- value_label = f"ControlNet {index + 1}"
-
- if value_type in ['Lora', 'Checkpoint']:
- value_label = f"{os.path.basename(os.path.splitext(value.split(',')[0])[0])}"
-
- if value_type in ["ModelMergeBlocks"]:
- if ":" in value:
- line = value.split(':')
- value_label = f"{line[0]}"
- elif len(value) > 16:
- value_label = f"ModelMergeBlocks {index + 1}"
- else:
- value_label = f"MMB: {value}"
-
- if value_type in ["Pos Condition"]:
- value_label = f"pos cond {index + 1}" if index>0 else f"pos cond"
- if value_type in ["Neg Condition"]:
- value_label = f"neg cond {index + 1}" if index>0 else f"neg cond"
-
- if value_type in ["Positive Prompt S/R"]:
- value_label = f"pos prompt {index + 1}" if index>0 else f"pos prompt"
- if value_type in ["Negative Prompt S/R"]:
- value_label = f"neg prompt {index + 1}" if index>0 else f"neg prompt"
-
- if value_type in ["steps", "cfg", "denoise", "clip_skip",
- "lora_model_strength", "lora_clip_strength"]:
- value_label = f"{value_type}: {value}"
-
- if value_type == "positive":
- value_label = f"pos prompt {index + 1}"
- elif value_type == "negative":
- value_label = f"neg prompt {index + 1}"
-
- return plot_image_vars, value_label
-
- @staticmethod
- def get_font(font_size):
- return ImageFont.truetype(str(Path(os.path.join(RESOURCES_DIR, 'OpenSans-Medium.ttf'))), font_size)
-
- @staticmethod
- def update_label(label, value, num_items):
- if len(label) < num_items:
- return [*label, value]
- return label
-
- @staticmethod
- def rearrange_tensors(latent, num_cols, num_rows):
- new_latent = []
- for i in range(num_rows):
- for j in range(num_cols):
- index = j * num_rows + i
- new_latent.append(latent[index])
- return new_latent
-
- def calculate_background_dimensions(self):
- border_size = int((self.max_width // 8) * 1.5) if self.y_type != "None" or self.x_type != "None" else 0
- bg_width = self.num_cols * (self.max_width + self.grid_spacing) - self.grid_spacing + border_size * (
- self.y_type != "None")
- bg_height = self.num_rows * (self.max_height + self.grid_spacing) - self.grid_spacing + border_size * (
- self.x_type != "None")
-
- x_offset_initial = border_size if self.y_type != "None" else 0
- y_offset = border_size if self.x_type != "None" else 0
-
- return bg_width, bg_height, x_offset_initial, y_offset
-
- def adjust_font_size(self, text, initial_font_size, label_width):
- font = self.get_font(initial_font_size)
- text_width = font.getbbox(text)
- if text_width and text_width[2]:
- text_width = text_width[2]
-
- scaling_factor = 0.9
- if text_width > (label_width * scaling_factor):
- return int(initial_font_size * (label_width / text_width) * scaling_factor)
- else:
- return initial_font_size
-
- def textsize(self, d, text, font):
- _, _, width, height = d.textbbox((0, 0), text=text, font=font)
- return width, height
-
- def create_label(self, img, text, initial_font_size, is_x_label=True, max_font_size=70, min_font_size=10):
- label_width = img.width if is_x_label else img.height
-
- # Adjust font size
- font_size = self.adjust_font_size(text, initial_font_size, label_width)
- font_size = min(max_font_size, font_size) # Ensure font isn't too large
- font_size = max(min_font_size, font_size) # Ensure font isn't too small
-
- label_height = int(font_size * 1.5) if is_x_label else font_size
-
- label_bg = Image.new('RGBA', (label_width, label_height), color=(255, 255, 255, 0))
- d = ImageDraw.Draw(label_bg)
-
- font = self.get_font(font_size)
-
- # Check if text will fit, if not insert ellipsis and reduce text
- if self.textsize(d, text, font=font)[0] > label_width:
- while self.textsize(d, text + '...', font=font)[0] > label_width and len(text) > 0:
- text = text[:-1]
- text = text + '...'
-
- # Compute text width and height for multi-line text
- text_lines = text.split('\n')
- text_widths, text_heights = zip(*[self.textsize(d, line, font=font) for line in text_lines])
- max_text_width = max(text_widths)
- total_text_height = sum(text_heights)
-
- # Compute position for each line of text
- lines_positions = []
- current_y = 0
- for line, line_width, line_height in zip(text_lines, text_widths, text_heights):
- text_x = (label_width - line_width) // 2
- text_y = current_y + (label_height - total_text_height) // 2
- current_y += line_height
- lines_positions.append((line, (text_x, text_y)))
-
- # Draw each line of text
- for line, (text_x, text_y) in lines_positions:
- d.text((text_x, text_y), line, fill='black', font=font)
-
- return label_bg
-
- def sample_plot_image(self, plot_image_vars, samples, preview_latent, latents_plot, image_list, disable_noise,
- start_step, last_step, force_full_denoise, x_value=None, y_value=None):
- model, clip, vae, positive, negative, seed, steps, cfg = None, None, None, None, None, None, None, None
- sampler_name, scheduler, denoise = None, None, None
-
- # 高级用法
- if plot_image_vars["x_node_type"] == "advanced" or plot_image_vars["y_node_type"] == "advanced":
-
- if self.x_type == "Seeds++ Batch" or self.y_type == "Seeds++ Batch":
- seed = int(x_value) if self.x_type == "Seeds++ Batch" else int(y_value)
- if self.x_type == "Steps" or self.y_type == "Steps":
- steps = int(x_value) if self.x_type == "Steps" else int(y_value)
- if self.x_type == "StartStep" or self.y_type == "StartStep":
- start_step = int(x_value) if self.x_type == "StartStep" else int(y_value)
- if self.x_type == "EndStep" or self.y_type == "EndStep":
- last_step = int(x_value) if self.x_type == "EndStep" else int(y_value)
- if self.x_type == "CFG Scale" or self.y_type == "CFG Scale":
- cfg = float(x_value) if self.x_type == "CFG Scale" else float(y_value)
- if self.x_type == "Sampler" or self.y_type == "Sampler":
- sampler_name = x_value if self.x_type == "Sampler" else y_value
- if self.x_type == "Scheduler" or self.y_type == "Scheduler":
- scheduler = x_value if self.x_type == "Scheduler" else y_value
- if self.x_type == "Sampler&Scheduler" or self.y_type == "Sampler&Scheduler":
- arr = x_value.split(',') if self.x_type == "Sampler&Scheduler" else y_value.split(',')
- if arr[0] and arr[0]!= 'None':
- sampler_name = arr[0]
- if arr[1] and arr[1]!= 'None':
- scheduler = arr[1]
- if self.x_type == "Denoise" or self.y_type == "Denoise":
- denoise = float(x_value) if self.x_type == "Denoise" else float(y_value)
- if self.x_type == "Pos Condition" or self.y_type == "Pos Condition":
- positive = plot_image_vars['positive_cond_stack'][int(x_value)] if self.x_type == "Pos Condition" else plot_image_vars['positive_cond_stack'][int(y_value)]
- if self.x_type == "Neg Condition" or self.y_type == "Neg Condition":
- negative = plot_image_vars['negative_cond_stack'][int(x_value)] if self.x_type == "Neg Condition" else plot_image_vars['negative_cond_stack'][int(y_value)]
- # 模型叠加
- if self.x_type == "ModelMergeBlocks" or self.y_type == "ModelMergeBlocks":
- ckpt_name_1, ckpt_name_2 = plot_image_vars['models']
- model1, clip1, vae1 = easyCache.load_checkpoint(ckpt_name_1)
- model2, clip2, vae2 = easyCache.load_checkpoint(ckpt_name_2)
- xy_values = x_value if self.x_type == "ModelMergeBlocks" else y_value
- if ":" in xy_values:
- xy_line = xy_values.split(':')
- xy_values = xy_line[1]
-
- xy_arrs = xy_values.split(',')
- # ModelMergeBlocks
- if len(xy_arrs) == 3:
- input, middle, out = xy_arrs
- kwargs = {
- "input": input,
- "middle": middle,
- "out": out
- }
- elif len(xy_arrs) == 30:
- kwargs = {}
- kwargs["time_embed."] = xy_arrs[0]
- kwargs["label_emb."] = xy_arrs[1]
-
- for i in range(12):
- kwargs["input_blocks.{}.".format(i)] = xy_arrs[2+i]
-
- for i in range(3):
- kwargs["middle_block.{}.".format(i)] = xy_arrs[14+i]
-
- for i in range(12):
- kwargs["output_blocks.{}.".format(i)] = xy_arrs[17+i]
-
- kwargs["out."] = xy_arrs[29]
- else:
- raise Exception("ModelMergeBlocks weight length error")
- default_ratio = next(iter(kwargs.values()))
-
- m = model1.clone()
- kp = model2.get_key_patches("diffusion_model.")
-
- for k in kp:
- ratio = float(default_ratio)
- k_unet = k[len("diffusion_model."):]
-
- last_arg_size = 0
- for arg in kwargs:
- if k_unet.startswith(arg) and last_arg_size < len(arg):
- ratio = float(kwargs[arg])
- last_arg_size = len(arg)
-
- m.add_patches({k: kp[k]}, 1.0 - ratio, ratio)
-
- vae_use = plot_image_vars['vae_use']
-
- clip = clip2 if vae_use == 'Use Model 2' else clip1
- if vae_use == 'Use Model 2':
- vae = vae2
- elif vae_use == 'Use Model 1':
- vae = vae1
- else:
- (vae,) = VAELoader().load_vae(vae_use)
- model = m
-
- # 如果存在lora_stack叠加lora
- optional_lora_stack = plot_image_vars['lora_stack']
- if optional_lora_stack is not None and optional_lora_stack != []:
- for lora in optional_lora_stack:
- lora_name = lora["lora_name"]
- model = model if model is not None else lora["model"]
- clip = clip if clip is not None else lora["clip"]
- lora_model_strength = lora["lora_model_strength"]
- lora_clip_strength = lora["lora_clip_strength"]
- if "lbw" in lora:
- lbw = lora["lbw"]
- lbw_a = lora["lbw_a"]
- lbw_b = lora["lbw_b"]
- cls = ALL_NODE_CLASS_MAPPINGS['LoraLoaderBlockWeight //Inspire']
- model, clip, _ = cls().doit(model, clip, lora_name, lora_model_strength, lora_clip_strength, False, 0,
- lbw_a, lbw_b, "", lbw)
- model, clip = easyCache.load_lora(lora_name, model, clip, lora_model_strength, lora_clip_strength)
-
- # 处理clip
- clip = clip.clone()
- if plot_image_vars['clip_skip'] != 0:
- clip.clip_layer(plot_image_vars['clip_skip'])
-
- # CheckPoint
- if self.x_type == "Checkpoint" or self.y_type == "Checkpoint":
- xy_values = x_value if self.x_type == "Checkpoint" else y_value
- ckpt_name, clip_skip, vae_name = xy_values.split(",")
- ckpt_name = ckpt_name.replace('*', ',')
- vae_name = vae_name.replace('*', ',')
- model, clip, vae = easyCache.load_checkpoint(ckpt_name)
- if vae_name != 'None':
- vae = easyCache.load_vae(vae_name)
-
- # 如果存在lora_stack叠加lora
- optional_lora_stack = plot_image_vars['lora_stack']
- if optional_lora_stack is not None and optional_lora_stack != []:
- for lora in optional_lora_stack:
- lora_name = lora["lora_name"]
- model = model if model is not None else lora["model"]
- clip = clip if clip is not None else lora["clip"]
- lora_model_strength = lora["lora_model_strength"]
- lora_clip_strength = lora["lora_clip_strength"]
- if "lbw" in lora:
- lbw = lora["lbw"]
- lbw_a = lora["lbw_a"]
- lbw_b = lora["lbw_b"]
- cls = ALL_NODE_CLASS_MAPPINGS['LoraLoaderBlockWeight //Inspire']
- model, clip, _ = cls().doit(model, clip, lora_name, lora_model_strength,
- lora_clip_strength, False, 0,
- lbw_a, lbw_b, "", lbw)
- model, clip = easyCache.load_lora(lora_name, model, clip, lora_model_strength,
- lora_clip_strength)
- # 处理clip
- clip = clip.clone()
- if clip_skip != 'None':
- clip.clip_layer(int(clip_skip))
- positive = plot_image_vars['positive']
- negative = plot_image_vars['negative']
- if plot_image_vars['a1111_prompt_style']:
- if "smZ CLIPTextEncode" in ALL_NODE_CLASS_MAPPINGS:
- cls = ALL_NODE_CLASS_MAPPINGS['smZ CLIPTextEncode']
- steps = plot_image_vars['steps']
- positive, = cls().encode(clip, positive, "A1111", True, True, False, False, 6,
- 1024, 1024, 0, 0, 1024, 1024, '', '', steps)
- negative, = cls().encode(clip, negative, "A1111", True, True, False, False, 6,
- 1024, 1024, 0, 0, 1024, 1024, '', '', steps)
- else:
- raise Exception(
- f"[ERROR] To use clip text encode same as webui, you need to install 'smzNodes'")
- else:
- clip = clip if clip is not None else plot_image_vars["clip"]
- positive = advanced_encode(clip, positive,
- plot_image_vars['positive_token_normalization'],
- plot_image_vars[
- 'positive_weight_interpretation'],
- w_max=1.0,
- apply_to_pooled="enable")
-
- negative = advanced_encode(clip, negative,
- plot_image_vars['negative_token_normalization'],
- plot_image_vars[
- 'negative_weight_interpretation'],
- w_max=1.0,
- apply_to_pooled="enable")
- if "positive_cond" in plot_image_vars:
- positive = positive + plot_image_vars["positive_cond"]
- if "negative_cond" in plot_image_vars:
- negative = negative + plot_image_vars["negative_cond"]
-
- # Lora
- if self.x_type == "Lora" or self.y_type == "Lora":
- model = model if model is not None else plot_image_vars["model"]
- clip = clip if clip is not None else plot_image_vars["clip"]
-
- xy_values = x_value if self.x_type == "Lora" else y_value
- lora_name, lora_model_strength, lora_clip_strength = xy_values.split(",")
- lora_stack = [{"lora_name": lora_name, "model": model, "clip" :clip, "lora_model_strength": float(lora_model_strength), "lora_clip_strength": float(lora_clip_strength)}]
- if 'lora_stack' in plot_image_vars:
- lora_stack = lora_stack + plot_image_vars['lora_stack']
-
- if lora_stack is not None and lora_stack != []:
- for lora in lora_stack:
- lora_name = lora["lora_name"]
- model = model if model is not None else lora["model"]
- clip = clip if clip is not None else lora["clip"]
- lora_model_strength = lora["lora_model_strength"]
- lora_clip_strength = lora["lora_clip_strength"]
- if "lbw" in lora:
- lbw = lora["lbw"]
- lbw_a = lora["lbw_a"]
- lbw_b = lora["lbw_b"]
- cls = ALL_NODE_CLASS_MAPPINGS['LoraLoaderBlockWeight //Inspire']
- model, clip, _ = cls().doit(model, clip, lora_name, lora_model_strength, lora_clip_strength,
- False, 0,
- lbw_a, lbw_b, "", lbw)
- model, clip = easyCache.load_lora(lora_name, model, clip, lora_model_strength,
- lora_clip_strength)
-
- # 提示词
- if "Positive" in self.x_type or "Positive" in self.y_type:
- if self.x_type == 'Positive Prompt S/R' or self.y_type == 'Positive Prompt S/R':
- positive = x_value if self.x_type == "Positive Prompt S/R" else y_value
- if plot_image_vars['a1111_prompt_style']:
- if "smZ CLIPTextEncode" in ALL_NODE_CLASS_MAPPINGS:
- cls = ALL_NODE_CLASS_MAPPINGS['smZ CLIPTextEncode']
- steps = plot_image_vars['steps']
- clip = clip if clip is not None else plot_image_vars["clip"]
- positive, = cls().encode(clip, positive, "A1111", True, True, False, False, 6,
- 1024, 1024, 0, 0, 1024, 1024, '', '', steps)
- else:
- raise Exception(
- f"[ERROR] To use clip text encode same as webui, you need to install 'smzNodes'")
- else:
- clip = clip if clip is not None else plot_image_vars["clip"]
- positive = advanced_encode(clip, positive,
- plot_image_vars['positive_token_normalization'],
- plot_image_vars[
- 'positive_weight_interpretation'],
- w_max=1.0,
- apply_to_pooled="enable")
- if "positive_cond" in plot_image_vars:
- positive = positive + plot_image_vars["positive_cond"]
-
- if "Negative" in self.x_type or "Negative" in self.y_type:
- if self.x_type == 'Negative Prompt S/R' or self.y_type == 'Negative Prompt S/R':
- negative = x_value if self.x_type == "Negative Prompt S/R" else y_value
- if plot_image_vars['a1111_prompt_style']:
- if "smZ CLIPTextEncode" in ALL_NODE_CLASS_MAPPINGS:
- cls = ALL_NODE_CLASS_MAPPINGS['smZ CLIPTextEncode']
- steps = plot_image_vars['steps']
- clip = clip if clip is not None else plot_image_vars["clip"]
- negative, = cls().encode(clip, negative, "A1111", True, True, False, False, 6,
- 1024, 1024, 0, 0, 1024, 1024, '', '', steps)
- else:
- raise Exception(
- f"[ERROR] To use clip text encode same as webui, you need to install 'smzNodes'")
- else:
- clip = clip if clip is not None else plot_image_vars["clip"]
- negative = advanced_encode(clip, negative,
- plot_image_vars['negative_token_normalization'],
- plot_image_vars[
- 'negative_weight_interpretation'],
- w_max=1.0,
- apply_to_pooled="enable")
- if "negative_cond" in plot_image_vars:
- positive = positive + plot_image_vars["negative_cond"]
-
- # ControlNet
- if "ControlNet" in self.x_type or "ControlNet" in self.y_type:
- _pipe = {
- "model": model if model is not None else plot_image_vars["model"],
- "positive": positive if positive is not None else plot_image_vars["positive_cond"],
- "negative": negative if negative is not None else plot_image_vars["negative_cond"],
- "vae": vae if vae is not None else plot_image_vars['vae'],
- "clip": clip if clip is not None else plot_image_vars['clip'],
- "samples": None,
- "images": None,
- "loader_settings": {}
- }
- cnet = plot_image_vars["cnet"] if "cnet" in plot_image_vars else None
- if cnet:
- index = x_value if "ControlNet" in self.x_type else y_value
- controlnet = cnet[index]
- for index, item in enumerate(controlnet):
- control_net_name = item[0]
- image = item[1]
- strength = item[2]
- start_percent = item[3]
- end_percent = item[4]
- _pipe, positive, negative = controlnetAdvanced().controlnetApply(_pipe, image, control_net_name, None, strength, start_percent, end_percent, 1)
-
- del _pipe
-
- # 简单用法
- if plot_image_vars["x_node_type"] == "loader" or plot_image_vars["y_node_type"] == "loader":
- model, clip, vae = easyCache.load_checkpoint(plot_image_vars['ckpt_name'])
-
- if plot_image_vars['lora_name'] != "None":
- model, clip = easyCache.load_lora(plot_image_vars['lora_name'], model, clip,
- plot_image_vars['lora_model_strength'],
- plot_image_vars['lora_clip_strength'])
-
- # Check for custom VAE
- if plot_image_vars['vae_name'] not in ["Baked-VAE", "Baked VAE"]:
- vae = easyCache.load_vae(plot_image_vars['vae_name'])
-
- # CLIP skip
- if not clip:
- raise Exception("No CLIP found")
- clip = clip.clone()
- clip.clip_layer(plot_image_vars['clip_skip'])
-
- if plot_image_vars['a1111_prompt_style']:
- if "smZ CLIPTextEncode" in ALL_NODE_CLASS_MAPPINGS:
- cls = ALL_NODE_CLASS_MAPPINGS['smZ CLIPTextEncode']
- steps = plot_image_vars['steps']
- positive, = cls().encode(clip, plot_image_vars['positive'], "A1111", True, True, False, False, 6, 1024, 1024, 0, 0, 1024, 1024, '', '', steps)
- negative, = cls().encode(clip, plot_image_vars['negative'], "A1111", True, True, False, False, 6, 1024, 1024, 0, 0, 1024, 1024, '', '', steps)
- else:
- raise Exception(f"[ERROR] To use clip text encode same as webui, you need to install 'smzNodes'")
- else:
- positive = advanced_encode(clip, plot_image_vars['positive'],
- plot_image_vars['positive_token_normalization'],
- plot_image_vars['positive_weight_interpretation'], w_max=1.0,
- apply_to_pooled="enable")
-
- negative = advanced_encode(clip, plot_image_vars['negative'],
- plot_image_vars['negative_token_normalization'],
- plot_image_vars['negative_weight_interpretation'], w_max=1.0,
- apply_to_pooled="enable")
-
- model = model if model is not None else plot_image_vars["model"]
- vae = vae if vae is not None else plot_image_vars["vae"]
- positive = positive if positive is not None else plot_image_vars["positive_cond"]
- negative = negative if negative is not None else plot_image_vars["negative_cond"]
-
- seed = seed if seed is not None else plot_image_vars["seed"]
- steps = steps if steps is not None else plot_image_vars["steps"]
- cfg = cfg if cfg is not None else plot_image_vars["cfg"]
- sampler_name = sampler_name if sampler_name is not None else plot_image_vars["sampler_name"]
- scheduler = scheduler if scheduler is not None else plot_image_vars["scheduler"]
- denoise = denoise if denoise is not None else plot_image_vars["denoise"]
- # Sample
- samples = sampler.common_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, samples,
- denoise=denoise, disable_noise=disable_noise, preview_latent=preview_latent,
- start_step=start_step, last_step=last_step,
- force_full_denoise=force_full_denoise)
-
- # Decode images and store
- latent = samples["samples"]
-
- # Add the latent tensor to the tensors list
- latents_plot.append(latent)
-
- # Decode the image
- image = vae.decode(latent).cpu()
-
- if self.output_individuals in [True, "True"]:
- easySave(image, self.save_prefix, self.image_output)
-
- # Convert the image from tensor to PIL Image and add it to the list
- pil_image = easySampler.tensor2pil(image)
- image_list.append(pil_image)
-
- # Update max dimensions
- self.max_width = max(self.max_width, pil_image.width)
- self.max_height = max(self.max_height, pil_image.height)
-
- # Return the touched variables
- return image_list, self.max_width, self.max_height, latents_plot
-
- # Process Functions
- def validate_xy_plot(self):
- if self.x_type == 'None' and self.y_type == 'None':
- log_node_warn(f'easyKsampler[{self.my_unique_id}]','No Valid Plot Types - Reverting to default sampling...')
- return False
- else:
- return True
-
- def get_latent(self, samples):
- # Extract the 'samples' tensor from the dictionary
- latent_image_tensor = samples["samples"]
-
- # Split the tensor into individual image tensors
- image_tensors = torch.split(latent_image_tensor, 1, dim=0)
-
- # Create a list of dictionaries containing the individual image tensors
- latent_list = [{'samples': image} for image in image_tensors]
-
- # Set latent only to the first latent of batch
- if self.latent_id >= len(latent_list):
- log_node_warn(f'easy kSampler[{self.my_unique_id}]',f'The selected latent_id ({self.latent_id}) is out of range.')
- log_node_warn(f'easy kSampler[{self.my_unique_id}]', f'Automatically setting the latent_id to the last image in the list (index: {len(latent_list) - 1}).')
-
- self.latent_id = len(latent_list) - 1
-
- return latent_list[self.latent_id]
-
- def get_labels_and_sample(self, plot_image_vars, latent_image, preview_latent, start_step, last_step,
- force_full_denoise, disable_noise):
- for x_index, x_value in enumerate(self.x_values):
- plot_image_vars, x_value_label = self.define_variable(plot_image_vars, self.x_type, x_value,
- x_index)
- self.x_label = self.update_label(self.x_label, x_value_label, len(self.x_values))
- if self.y_type != 'None':
- for y_index, y_value in enumerate(self.y_values):
- plot_image_vars, y_value_label = self.define_variable(plot_image_vars, self.y_type, y_value,
- y_index)
- self.y_label = self.update_label(self.y_label, y_value_label, len(self.y_values))
- # ttNl(f'{CC.GREY}X: {x_value_label}, Y: {y_value_label}').t(
- # f'Plot Values {self.num}/{self.total} ->').p()
-
- self.image_list, self.max_width, self.max_height, self.latents_plot = self.sample_plot_image(
- plot_image_vars, latent_image, preview_latent, self.latents_plot, self.image_list,
- disable_noise, start_step, last_step, force_full_denoise, x_value, y_value)
- self.num += 1
- else:
- # ttNl(f'{CC.GREY}X: {x_value_label}').t(f'Plot Values {self.num}/{self.total} ->').p()
- self.image_list, self.max_width, self.max_height, self.latents_plot = self.sample_plot_image(
- plot_image_vars, latent_image, preview_latent, self.latents_plot, self.image_list, disable_noise,
- start_step, last_step, force_full_denoise, x_value)
- self.num += 1
-
- # Rearrange latent array to match preview image grid
- self.latents_plot = self.rearrange_tensors(self.latents_plot, self.num_cols, self.num_rows)
-
- # Concatenate the tensors along the first dimension (dim=0)
- self.latents_plot = torch.cat(self.latents_plot, dim=0)
-
- return self.latents_plot
-
- def plot_images_and_labels(self):
- # Calculate the background dimensions
- bg_width, bg_height, x_offset_initial, y_offset = self.calculate_background_dimensions()
-
- # Create the white background image
- background = Image.new('RGBA', (int(bg_width), int(bg_height)), color=(255, 255, 255, 255))
-
- output_image = []
- for row_index in range(self.num_rows):
- x_offset = x_offset_initial
-
- for col_index in range(self.num_cols):
- index = col_index * self.num_rows + row_index
- img = self.image_list[index]
- output_image.append(sampler.pil2tensor(img))
- background.paste(img, (x_offset, y_offset))
-
- # Handle X label
- if row_index == 0 and self.x_type != "None":
- label_bg = self.create_label(img, self.x_label[col_index], int(48 * img.width / 512))
- label_y = (y_offset - label_bg.height) // 2
- background.alpha_composite(label_bg, (x_offset, label_y))
-
- # Handle Y label
- if col_index == 0 and self.y_type != "None":
- label_bg = self.create_label(img, self.y_label[row_index], int(48 * img.height / 512), False)
- label_bg = label_bg.rotate(90, expand=True)
-
- label_x = (x_offset - label_bg.width) // 2
- label_y = y_offset + (img.height - label_bg.height) // 2
- background.alpha_composite(label_bg, (label_x, label_y))
-
- x_offset += img.width + self.grid_spacing
-
- y_offset += img.height + self.grid_spacing
-
- return (sampler.pil2tensor(background), output_image)
-
-easyCache = easyLoader()
sampler = easySampler()
-
-def easySave(images, filename_prefix, output_type, prompt=None, extra_pnginfo=None):
-
- if output_type == "Hide":
- return list()
- if output_type == "Preview":
- filename_prefix = 'easyPreview'
- results = PreviewImage().save_images(images, filename_prefix, prompt, extra_pnginfo)
- return results['ui']['images']
- else:
- results = SaveImage().save_images(images, filename_prefix, prompt, extra_pnginfo)
- return results['ui']['images']
-
-def check_link_to_clip(node_id, clip_id, visited=None, node=None):
- """Check if a given node links directly or indirectly to a loader node."""
- if visited is None:
- visited = set()
-
- if node_id in visited:
- return False
- visited.add(node_id)
- if "pipe" in node["inputs"]:
- link_ids = node["inputs"]["pipe"]
- for id in link_ids:
- if id != 0 and id == str(clip_id):
- return True
- return False
-
-def find_nearest_steps(clip_id, prompt):
- """Find the nearest KSampler or preSampling node that references the given id."""
- for id in prompt:
- node = prompt[id]
- if "Sampler" in node["class_type"] or "sampler" in node["class_type"] or "Sampling" in node["class_type"]:
- # Check if this KSampler node directly or indirectly references the given CLIPTextEncode node
- if check_link_to_clip(id, clip_id, None, node):
- steps = node["inputs"]["steps"] if "steps" in node["inputs"] else 1
- return steps
- return 1
-def find_wildcards_seed(clip_id, text, prompt):
- def find_link_clip_id(id, seed, wildcard_id):
- node = prompt[id]
- if "positive" in node['inputs']:
- link_ids = node["inputs"]["positive"]
- if type(link_ids) == list:
- for id in link_ids:
- if id != 0:
- if id == wildcard_id:
- wildcard_node = prompt[wildcard_id]
- seed = wildcard_node["inputs"]["seed_num"] if "seed_num" in wildcard_node["inputs"] else None
- return seed
- else:
- return find_link_clip_id(id, seed, wildcard_id)
- else:
- return None
- else:
- return None
- if "__" in text:
- seed = None
- for id in prompt:
- node = prompt[id]
- if "wildcards" in node["class_type"]:
- wildcard_id = id
- return find_link_clip_id(str(clip_id), seed, wildcard_id)
- return seed
- else:
- return None
+easyCache = easyLoader()
# ---------------------------------------------------------------提示词 开始----------------------------------------------------------------------#
@@ -1239,7 +62,7 @@ class wildcardsPrompt:
"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_num": ("INT", {"default": 0, "min": 0, "max": 1125899906842624}),
+ "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
@@ -1704,7 +527,7 @@ class latentCompositeMaskedWithCond:
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"]
@@ -1723,20 +546,11 @@ class latentCompositeMaskedWithCond:
positive_cond = pipe["positive"]
log_node_warn("正在处理提示词编码...")
- # Use new clip text encode by smzNodes like same as webui, when if you installed the smzNodes
- if a1111_prompt_style:
- if "smZ CLIPTextEncode" in ALL_NODE_CLASS_MAPPINGS:
- cls = ALL_NODE_CLASS_MAPPINGS['smZ CLIPTextEncode']
- steps = pipe["loader_settings"]["steps"] if "steps" in pipe["loader_settings"] else 5
- positive_embeddings_final, = cls().encode(clip, positive, "A1111", True, True, False, False, 6, 1024,
- 1024, 0, 0, 1024, 1024, '', '', steps)
- else:
- raise Exception(f"[ERROR] To use clip text encode same as webui, you need to install 'smzNodes'")
- else:
- positive_embeddings_final = advanced_encode(clip, positive,
- positive_token_normalization,
- positive_weight_interpretation, w_max=1.0,
- apply_to_pooled='enable')
+ 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)
@@ -1766,7 +580,7 @@ class easySeed:
def INPUT_TYPES(s):
return {
"required": {
- "seed_num": ("INT", {"default": 0, "min": 0, "max": 1125899906842624}),
+ "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
@@ -1781,13 +595,14 @@ class easySeed:
def doit(self, seed_num=0, prompt=None, extra_pnginfo=None, my_unique_id=None):
return seed_num,
+
# 全局随机种
class globalSeed:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
- "value": ("INT", {"default": 0, "min": 0, "max": 1125899906842624}),
+ "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"], ),
@@ -1811,6 +626,7 @@ class globalSeed:
# 简易加载器完整
class fullLoader:
+
@classmethod
def INPUT_TYPES(cls):
resolution_strings = [f"{width} x {height}" for width, height in BASE_RESOLUTIONS]
@@ -1891,7 +707,7 @@ class fullLoader:
node = prompt[xy_model_id]
if "ckpt_name_1" in node["inputs"]:
ckpt_name_1 = node["inputs"]["ckpt_name_1"]
- model, clip, vae = easyCache.load_checkpoint(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:
@@ -1905,19 +721,21 @@ class fullLoader:
elif clip_override is not None:
raise Exception(f"[ERROR] model or vae is missing")
else:
- model, clip, vae = easyCache.load_checkpoint(ckpt_name, config_name)
+ model, clip, vae, clip_vision = easyCache.load_checkpoint(ckpt_name, config_name)
- if optional_lora_stack is not None:
+ if optional_lora_stack is not None and can_load_lora:
for lora in optional_lora_stack:
- if can_load_lora:
- model, clip = easyCache.load_lora(lora[0], model, clip, lora[1], lora[2])
- pipe_lora_stack.append({"lora_name": lora[0], "model": model, "clip": clip, "lora_model_strength": lora[1], "lora_clip_strength": lora[2]})
+ 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":
- if can_load_lora:
- model, clip = easyCache.load_lora(lora_name, model, clip, lora_model_strength, lora_clip_strength)
- pipe_lora_stack.append({"lora_name": lora_name, "model": model, "clip": clip, "lora_model_strength": lora_model_strength,
- "lora_clip_strength": lora_clip_strength})
+ 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"]:
@@ -1927,22 +745,16 @@ class fullLoader:
raise Exception("No CLIP found")
# 判断是否连接 styles selector
- is_positive_linked_styles_selector = False
- inputs_positive_values = prompt[my_unique_id]['inputs']['positive'] if "positive" in prompt[my_unique_id]['inputs'] else None
- if type(inputs_positive_values) == list and inputs_positive_values != 'undefined' and inputs_positive_values[0]:
- is_positive_linked_styles_selector = True if prompt[inputs_positive_values[0]] and prompt[inputs_positive_values[0]]['class_type'] == 'easy stylesSelector' else False
- is_negative_linked_styles_selector = False
- inputs_negative_values = prompt[my_unique_id]['inputs']['negative'] if "negative" in prompt[my_unique_id]['inputs'] else None
- if type(inputs_negative_values) == list and inputs_negative_values != 'undefined' and inputs_negative_values[0]:
- is_negative_linked_styles_selector = True if prompt[inputs_negative_values[0]] and prompt[inputs_negative_values[0]]['class_type'] == 'easy stylesSelector' else False
+ 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, clip, positive, positive_decode, show_positive_prompt, pipe_lora_stack = process_with_loras(positive, model, clip, "Positive", positive_seed, can_load_lora, pipe_lora_stack)
+ model, clip, positive, positive_decode, show_positive_prompt, pipe_lora_stack = process_with_loras(positive, model, 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, clip, negative, negative_decode, show_negative_prompt, pipe_lora_stack = process_with_loras(negative, model, clip,
- "Negative", negative_seed, can_load_lora, pipe_lora_stack)
+ "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 ""
clipped = clip.clone()
@@ -1950,23 +762,14 @@ class fullLoader:
clipped.clip_layer(clip_skip)
log_node_warn("正在处理提示词编码...")
- # Use new clip text encode by smzNodes like same as webui, when if you installed the smzNodes
- if a1111_prompt_style:
- if "smZ CLIPTextEncode" in ALL_NODE_CLASS_MAPPINGS:
- cls = ALL_NODE_CLASS_MAPPINGS['smZ CLIPTextEncode']
- steps = find_nearest_steps(my_unique_id, prompt)
- positive_embeddings_final, = cls().encode(clipped, positive, "A1111", True, True, False, False, 6, 1024, 1024, 0, 0, 1024, 1024, '', '', steps)
- negative_embeddings_final, = cls().encode(clipped, negative, "A1111", True, True, False, False, 6, 1024, 1024, 0, 0, 1024, 1024, '', '', steps)
- else:
- raise Exception(f"[ERROR] To use clip text encode same as webui, you need to install 'smzNodes'")
- else:
- positive_embeddings_final = advanced_encode(clipped, positive, positive_token_normalization,
- positive_weight_interpretation, w_max=1.0,
- apply_to_pooled='enable')
+ steps = find_nearest_steps(my_unique_id, prompt)
+ positive_embeddings_final = advanced_encode(clipped, positive, positive_token_normalization,
+ positive_weight_interpretation, w_max=1.0,
+ apply_to_pooled='enable', a1111_prompt_style=a1111_prompt_style, steps=steps)
- negative_embeddings_final = advanced_encode(clipped, negative, negative_token_normalization,
- negative_weight_interpretation, w_max=1.0,
- apply_to_pooled='enable')
+ negative_embeddings_final = advanced_encode(clipped, negative, negative_token_normalization,
+ negative_weight_interpretation, w_max=1.0,
+ apply_to_pooled='enable', a1111_prompt_style=a1111_prompt_style, steps=steps)
image = easySampler.pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0)))
log_node_warn("处理结束...")
@@ -2114,19 +917,6 @@ class comfyLoader:
my_unique_id
)
-
-class effientNetEncoder(torch.nn.Module):
- def __init__(self, c_latent=16):
- super().__init__()
- self.backbone = torchvision.models.efficientnet_v2_s(weights='DEFAULT').features.eval()
- self.mapper = torch.nn.Sequential(
- torch.nn.Conv2d(1280, c_latent, kernel_size=1, bias=False),
- torch.nn.BatchNorm2d(c_latent, affine=False), # then normalize them to have mean 0 and std 1
- )
-
- def forward(self, x):
- return self.mapper(self.backbone(x))
-
# stable Cascade
class cascadeLoader:
def __init__(self):
@@ -2137,9 +927,9 @@ class cascadeLoader:
resolution_strings = [f"{width} x {height}" for width, height in BASE_RESOLUTIONS]
return {"required": {
- "stage_c": (folder_paths.get_filename_list("unet"),),
- "stage_b": (folder_paths.get_filename_list("unet"),),
- "stage_a": (folder_paths.get_filename_list("vae"),),
+ "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"),),
"resolution": (resolution_strings, {"default": "1024 x 1024"}),
@@ -2162,6 +952,13 @@ class cascadeLoader:
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,
resolution, empty_latent_width, empty_latent_height, compression,
positive, negative, batch_size, prompt=None,
@@ -2192,31 +989,30 @@ class cascadeLoader:
# Clean models from loaded_objects
easyCache.update_loaded_objects(prompt)
- # Load unet
- model_c = easyCache.load_unet(stage_c)
- model_b = easyCache.load_unet(stage_b)
+ print(self.is_ckpt(stage_c))
+ 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
+
model = (model_c, model_b)
# Load clip
- clip = easyCache.load_clip(clip_name, "stable_cascade")
-
+ if clip_name != 'None':
+ clip = easyCache.load_clip(clip_name, "stable_cascade")
# Load vae
- vae = easyCache.load_vae(stage_a)
+ 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 = False
- inputs_positive_values = prompt[my_unique_id]['inputs']['positive'] if "positive" in prompt[my_unique_id][
- 'inputs'] else None
- if type(inputs_positive_values) == list and inputs_positive_values != 'undefined' and inputs_positive_values[0]:
- is_positive_linked_styles_selector = True if prompt[inputs_positive_values[0]] and \
- prompt[inputs_positive_values[0]][
- 'class_type'] == 'easy stylesSelector' else False
- is_negative_linked_styles_selector = False
- inputs_negative_values = prompt[my_unique_id]['inputs']['negative'] if "negative" in prompt[my_unique_id][
- 'inputs'] else None
- if type(inputs_negative_values) == list and inputs_negative_values != 'undefined' and inputs_negative_values[0]:
- is_negative_linked_styles_selector = True if prompt[inputs_negative_values[0]] and \
- prompt[inputs_negative_values[0]][
- 'class_type'] == 'easy stylesSelector' else False
+ 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)
@@ -2225,7 +1021,8 @@ class cascadeLoader:
"Positive",
positive_seed,
can_load_lora,
- pipe_lora_stack)
+ 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,
@@ -2233,7 +1030,8 @@ class cascadeLoader:
"Negative",
negative_seed,
can_load_lora,
- pipe_lora_stack)
+ pipe_lora_stack,
+ easyCache)
negative_wildcard_prompt = negative_decode if show_negative_prompt or is_negative_linked_styles_selector else ""
tokens = clip.tokenize(positive)
@@ -2337,7 +1135,7 @@ class zero123Loader:
# Clean models from loaded_objects
easyCache.update_loaded_objects(prompt)
- model, clip_vision, vae = easyCache.load_checkpoint(ckpt_name, "Default", True)
+ 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)
@@ -2391,7 +1189,7 @@ class svdLoader:
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]
+ 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")),),
@@ -2439,12 +1237,13 @@ class svdLoader:
# Clean models from loaded_objects
easyCache.update_loaded_objects(prompt)
- model, clip_vision, vae = easyCache.load_checkpoint(ckpt_name, "Default", True)
+ print(ckpt_name)
+
+ 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)
+ 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
@@ -2455,21 +1254,17 @@ class svdLoader:
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 clip_name != 'None':
- clip_path = folder_paths.get_full_path("clip", clip_name)
- clip = comfy.sd.load_clip(ckpt_paths=[clip_path],
- embedding_directory=folder_paths.get_folder_paths("embeddings"))
- if optional_positive is not None:
- tokens = clip.tokenize(optional_positive)
- cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True)
- positive_embeddings_final = [[cond, {"pooled_output": pooled}]]
- positive, = ConditioningConcat().concat(positive, positive_embeddings_final)
- if optional_negative is not None:
- tokens = clip.tokenize(optional_negative)
- cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True)
- negative_embeddings_final = [[cond, {"pooled_output": pooled}]]
- negative, = ConditioningConcat().concat(negative, negative_embeddings_final)
-
+ if optional_positive is not None:
+ 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:
+ 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}
@@ -2629,59 +1424,8 @@ class controlnetSimple:
CATEGORY = "EasyUse/Loaders"
def controlnetApply(self, pipe, image, control_net_name, control_net=None, strength=1, scale_soft_weights=1):
- if control_net is None:
- if scale_soft_weights < 1:
- if "ScaledSoftControlNetWeights" in ALL_NODE_CLASS_MAPPINGS:
- soft_weight_cls = ALL_NODE_CLASS_MAPPINGS['ScaledSoftControlNetWeights']
- (weights, timestep_keyframe) = soft_weight_cls().load_weights(scale_soft_weights, False)
- cn_adv_cls = ALL_NODE_CLASS_MAPPINGS['ControlNetLoaderAdvanced']
- control_net, = cn_adv_cls().load_controlnet(control_net_name, timestep_keyframe)
- else:
- raise Exception(f"[ERROR] To use Scale soft weight, you need to install 'COMFYUI-Advanced-ControlNet'")
- else:
- controlnet_path = folder_paths.get_full_path("controlnet", control_net_name)
- control_net = comfy.controlnet.load_controlnet(controlnet_path)
- control_hint = image.movedim(-1, 1)
-
- positive = pipe["positive"]
- negative = pipe["negative"]
-
- if strength != 0:
- if negative is None:
- p = []
- for t in positive:
- n = [t[0], t[1].copy()]
- c_net = control_net.copy().set_cond_hint(control_hint, strength)
- if 'control' in t[1]:
- c_net.set_previous_controlnet(t[1]['control'])
- n[1]['control'] = c_net
- n[1]['control_apply_to_uncond'] = True
- p.append(n)
- positive = p
- else:
- cnets = {}
- out = []
- for conditioning in [positive, negative]:
- c = []
- for t in conditioning:
- d = t[1].copy()
-
- prev_cnet = d.get('control', None)
- if prev_cnet in cnets:
- c_net = cnets[prev_cnet]
- else:
- c_net = control_net.copy().set_cond_hint(control_hint, strength)
- c_net.set_previous_controlnet(prev_cnet)
- cnets[prev_cnet] = c_net
-
- d['control'] = c_net
- d['control_apply_to_uncond'] = False
- n = [t[0], d]
- c.append(n)
- out.append(c)
- positive = out[0]
- negative = out[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'],
@@ -2697,7 +1441,6 @@ class controlnetSimple:
"loader_settings": pipe["loader_settings"]
}
-
return (new_pipe, positive, negative)
# controlnetADV
@@ -2732,59 +1475,8 @@ class controlnetAdvanced:
def controlnetApply(self, pipe, image, control_net_name, control_net=None, strength=1, start_percent=0, end_percent=1, scale_soft_weights=1):
- if control_net is None:
- if scale_soft_weights < 1:
- if "ScaledSoftControlNetWeights" in ALL_NODE_CLASS_MAPPINGS:
- soft_weight_cls = ALL_NODE_CLASS_MAPPINGS['ScaledSoftControlNetWeights']
- (weights, timestep_keyframe) = soft_weight_cls().load_weights(scale_soft_weights, False)
- cn_adv_cls = ALL_NODE_CLASS_MAPPINGS['ControlNetLoaderAdvanced']
- control_net, = cn_adv_cls().load_controlnet(control_net_name, timestep_keyframe)
- else:
- raise Exception(
- f"[ERROR] To use Scale soft weight, you need to install 'COMFYUI-Advanced-ControlNet'")
- else:
- controlnet_path = folder_paths.get_full_path("controlnet", control_net_name)
- control_net = comfy.controlnet.load_controlnet(controlnet_path)
-
- control_hint = image.movedim(-1, 1)
- positive = pipe["positive"]
- negative = pipe["negative"]
-
- if strength != 0:
- if negative is None:
- p = []
- for t in positive:
- n = [t[0], t[1].copy()]
- c_net = control_net.copy().set_cond_hint(control_hint, strength)
- if 'control' in t[1]:
- c_net.set_previous_controlnet(t[1]['control'])
- n[1]['control'] = c_net
- n[1]['control_apply_to_uncond'] = True
- p.append(n)
- positive = p
- else:
- cnets = {}
- out = []
- for conditioning in [positive, negative]:
- c = []
- for t in conditioning:
- d = t[1].copy()
-
- prev_cnet = d.get('control', None)
- if prev_cnet in cnets:
- c_net = cnets[prev_cnet]
- else:
- c_net = control_net.copy().set_cond_hint(control_hint, strength, (start_percent, end_percent))
- c_net.set_previous_controlnet(prev_cnet)
- cnets[prev_cnet] = c_net
-
- d['control'] = c_net
- d['control_apply_to_uncond'] = False
- n = [t[0], d]
- c.append(n)
- out.append(c)
- positive = out[0]
- negative = out[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'],
@@ -2804,8 +1496,47 @@ class controlnetAdvanced:
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,)
+
# FooocusInpaint (Testing)
-from .fooocus import InpaintHead, InpaintWorker, get_local_filepath
+from .fooocus import InpaintHead, InpaintWorker
inpaint_head_model = None
class fooocusInpaintLoader:
@classmethod
@@ -2835,6 +1566,7 @@ class fooocusInpaintLoader:
inpaint_lora = comfy.utils.load_torch_file(patch_file, safe_load=True)
return ((inpaint_head_model, inpaint_lora),)
+
#---------------------------------------------------------------预采样 开始----------------------------------------------------------------------#
# 预采样设置(基础)
@@ -2852,7 +1584,7 @@ class samplerSettings:
"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_num": ("INT", {"default": 0, "min": 0, "max": 1125899906842624}),
+ "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"optional": {
"image_to_latent": ("IMAGE",),
@@ -2927,7 +1659,7 @@ class samplerSettingsAdvanced:
"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_num": ("INT", {"default": 0, "min": 0, "max": 1125899906842624}),
+ "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"optional": {
"image_to_latent": ("IMAGE",),
@@ -3009,7 +1741,7 @@ class sdTurboSettings:
"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_num": ("INT", {"default": 0, "min": 0, "max": 1125899906842624}),
+ "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
@@ -3095,14 +1827,14 @@ class cascadeSettings:
def INPUT_TYPES(cls):
return {"required":
{"pipe": ("PIPE_LINE",),
- "encode_vae_name": (["None"] + folder_paths.get_filename_list("vae"), {"default": "effnet_encoder.safetensors"}),
- "decode_vae_name": (["None"] + folder_paths.get_filename_list("vae"), {"default": "previewer.safetensors"}),
+ "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_num": ("INT", {"default": 0, "min": 0, "max": 1125899906842624}),
+ "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"optional": {
"image_to_latent_c": ("IMAGE",),
@@ -3126,8 +1858,11 @@ class cascadeSettings:
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 and encode_vae_name != 'None':
- encode_vae = easyCache.load_vae(encode_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']
@@ -3204,7 +1939,7 @@ class dynamicCFGSettings:
"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_num": ("INT", {"default": 0, "min": 0, "max": 1125899906842624}),
+ "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"optional":{
"image_to_latent": ("IMAGE",),
@@ -3348,7 +2083,7 @@ class samplerFull:
"save_prefix": ("STRING", {"default": "ComfyUI"}),
},
"optional": {
- "seed_num": ("INT", {"default": 0, "min": 0, "max": 1125899906842624}),
+ "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
"model": ("MODEL",),
"positive": ("CONDITIONING",),
"negative": ("CONDITIONING",),
@@ -3500,7 +2235,7 @@ class samplerFull:
steps, cfg, sampler_name, scheduler, denoise,
image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, preview_latent, xyPlot):
- sampleXYplot = easyXYPlot(xyPlot, save_prefix, image_output, prompt, extra_pnginfo, my_unique_id)
+ 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,
@@ -3515,8 +2250,8 @@ class samplerFull:
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 "lora_model_strength" in pipe["loader_settings"] else None,
- "lora_clip_strength": pipe["loader_settings"]["lora_clip_strength"] if "lora_clip_strength" 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,
@@ -3969,8 +2704,8 @@ class samplerCascadeFull:
def INPUT_TYPES(cls):
return {"required":
{"pipe": ("PIPE_LINE",),
- "encode_vae_name": (["None"] + folder_paths.get_filename_list("vae"), {"default": "effnet_encoder.safetensors"}),
- "decode_vae_name": (["None"] + folder_paths.get_filename_list("vae"), {"default": "previewer.safetensors"}),
+ "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"}),
@@ -3979,7 +2714,7 @@ class samplerCascadeFull:
"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_num": ("INT", {"default": 0, "min": 0, "max": 1125899906842624}),
+ "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"optional": {
@@ -4005,8 +2740,11 @@ class samplerCascadeFull:
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 and encode_vae_name != 'None':
- encode_vae = easyCache.load_vae(encode_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")
@@ -4017,8 +2755,7 @@ class samplerCascadeFull:
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)
+ "center").movedim(1, -1)
latent_c = encode_vae.encode(s[:, :, :, :3])
latent_b = torch.zeros([latent_c.shape[0], 4, height // 4, width // 4])
@@ -4066,8 +2803,11 @@ class samplerCascadeFull:
stage_c = samp_samples["samples"]
results = None
- if image_output not in ['Hide', 'Hide/Save'] and decode_vae_name != 'None':
- decode_vae = easyCache.load_vae(decode_vae_name)
+ 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)
@@ -4101,7 +2841,7 @@ class samplerCascadeFull:
"model": pipe['model'][1],
"positive": c2,
"negative": c1,
- "vae": pipe['vae'],
+ "vae": pipe['vae'][1],
"clip": pipe['clip'],
"samples": samples_b,
@@ -4881,7 +3621,7 @@ class pipeXYPlot:
"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": 1125899906842624},
+ "seed": {"min": 0, "max": MAX_SEED_NUM},
}
plot_dict = {**sampler_dict, **loader_dict}
@@ -5056,8 +3796,8 @@ class pipeXYPlotAdvanced:
if lora_stack is not None:
for lora in lora_stack:
_lora_stack.append(
- {"lora_name": lora[0], "model": pipe['model'], "clip": pipe['clip'], "lora_model_strength": lora[1],
- "lora_clip_strength": lora[2]})
+ {"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
@@ -5072,8 +3812,8 @@ class pipeXYPlotAdvanced:
if lora_stack is not None:
for lora in lora_stack:
_lora_stack.append(
- {"lora_name": lora[0], "model": pipe['model'], "clip": pipe['clip'], "lora_model_strength": lora[1],
- "lora_clip_strength": lora[2]})
+ {"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
@@ -5212,7 +3952,6 @@ class pipeXYPlotAdvanced:
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)
@@ -5766,6 +4505,7 @@ class XYplot_Lora:
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,)
@@ -5883,7 +4623,6 @@ class showLoaderSettingsNames:
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
@@ -5902,12 +4641,13 @@ NODE_CLASS_MAPPINGS = {
"easy fullLoader": fullLoader,
"easy a1111Loader": a1111Loader,
"easy comfyLoader": comfyLoader,
- "easy cascadeLoader": cascadeLoader,
"easy zero123Loader": zero123Loader,
"easy svdLoader": svdLoader,
+ "easy cascadeLoader": cascadeLoader,
"easy loraStack": loraStackLoader,
"easy controlnetLoader": controlnetSimple,
"easy controlnetLoaderADV": controlnetAdvanced,
+ "easy LLLiteLoader": LLLiteLoader,
# latent 潜空间
"easy latentNoisy": latentNoisy,
"easy latentCompositeMaskedWithCond": latentCompositeMaskedWithCond,
@@ -5983,7 +4723,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"easy loraStack": "EasyLoraStack",
"easy controlnetLoader": "EasyControlnet",
"easy controlnetLoaderADV": "EasyControlnet (Advanced)",
- "easy photoMakerApply": "Apply PhotoMaker",
+ "easy LLLiteLoader": "EasyLLLite",
# latent 潜空间
"easy latentNoisy": "LatentNoisy",
"easy latentCompositeMaskedWithCond": "LatentCompositeMaskedWithCond",
diff --git a/py/fooocus.py b/py/fooocus.py
index 8e53e70..93a3296 100644
--- a/py/fooocus.py
+++ b/py/fooocus.py
@@ -1,23 +1,9 @@
-import os
import torch
import comfy
from comfy.model_patcher import ModelPatcher
from comfy.model_management import cast_to_device
-from urllib.parse import urlparse
-from torch.hub import download_url_to_file
-from .log import log_node_warn, log_node_error, log_node_info
-def get_local_filepath(url, dirname, local_file_name=None):
- if not os.path.exists(dirname):
- os.makedirs(dirname)
- if not local_file_name:
- parsed_url = urlparse(url)
- local_file_name = os.path.basename(parsed_url.path)
- destination = os.path.join(dirname, local_file_name)
- if not os.path.exists(destination):
- log_node_warn(f'downloading {url} to {destination}')
- download_url_to_file(url, destination)
- return destination
+from .log import log_node_warn, log_node_error, log_node_info
# Inpaint
class InpaintHead(torch.nn.Module):
diff --git a/py/libs/controlnet.py b/py/libs/controlnet.py
new file mode 100644
index 0000000..c37c8b2
--- /dev/null
+++ b/py/libs/controlnet.py
@@ -0,0 +1,61 @@
+import folder_paths
+import comfy.controlnet
+from nodes import NODE_CLASS_MAPPINGS
+
+class easyControlnet:
+ def __init__(self):
+ pass
+
+ def apply(self, control_net_name, image, positive, negative, strength, start_percent=0, end_percent=1, control_net=None, scale_soft_weights=1):
+ if control_net is None:
+ if scale_soft_weights < 1:
+ if "ScaledSoftControlNetWeights" in NODE_CLASS_MAPPINGS:
+ soft_weight_cls = NODE_CLASS_MAPPINGS['ScaledSoftControlNetWeights']
+ (weights, timestep_keyframe) = soft_weight_cls().load_weights(scale_soft_weights, False)
+ cn_adv_cls = NODE_CLASS_MAPPINGS['ControlNetLoaderAdvanced']
+ control_net, = cn_adv_cls().load_controlnet(control_net_name, timestep_keyframe)
+ else:
+ raise Exception(f"[Advanced-ControlNet Not Found] you need to install 'COMFYUI-Advanced-ControlNet'")
+ else:
+ controlnet_path = folder_paths.get_full_path("controlnet", control_net_name)
+ control_net = comfy.controlnet.load_controlnet(controlnet_path)
+
+ control_hint = image.movedim(-1, 1)
+
+ if strength != 0:
+ if negative is None:
+ p = []
+ for t in positive:
+ n = [t[0], t[1].copy()]
+ c_net = control_net.copy().set_cond_hint(control_hint, strength, (start_percent, end_percent))
+ if 'control' in t[1]:
+ c_net.set_previous_controlnet(t[1]['control'])
+ n[1]['control'] = c_net
+ n[1]['control_apply_to_uncond'] = True
+ p.append(n)
+ positive = p
+ else:
+ cnets = {}
+ out = []
+ for conditioning in [positive, negative]:
+ c = []
+ for t in conditioning:
+ d = t[1].copy()
+
+ prev_cnet = d.get('control', None)
+ if prev_cnet in cnets:
+ c_net = cnets[prev_cnet]
+ else:
+ c_net = control_net.copy().set_cond_hint(control_hint, strength, (start_percent, end_percent))
+ c_net.set_previous_controlnet(prev_cnet)
+ cnets[prev_cnet] = c_net
+
+ d['control'] = c_net
+ d['control_apply_to_uncond'] = False
+ n = [t[0], d]
+ c.append(n)
+ out.append(c)
+ positive = out[0]
+ negative = out[1]
+
+ return (positive, negative)
\ No newline at end of file
diff --git a/py/libs/loader.py b/py/libs/loader.py
new file mode 100644
index 0000000..3fe9dc7
--- /dev/null
+++ b/py/libs/loader.py
@@ -0,0 +1,279 @@
+import time, os, psutil
+import comfy.utils
+import comfy.sd
+import folder_paths
+from nodes import NODE_CLASS_MAPPINGS
+from collections import defaultdict
+from ..log import log_node_info, log_node_error
+
+stable_diffusion_loaders = ["easy a1111Loader", "easy comfyLoader", "easy zero123Loader", "easy svdLoader"]
+stable_cascade_loaders = ["easy cascadeLoader"]
+cascade_vae_node = ["easy preSamplingCascade", "easy fullCascadeKSampler"]
+model_merge_node = ["easy XYInputs: ModelMergeBlocks"]
+lora_widget = ["easy a1111Loader", "easy comfyLoader"]
+
+class easyLoader:
+ def __init__(self):
+ self.loaded_objects = {
+ "ckpt": defaultdict(tuple), # {ckpt_name: (model, ...)}
+ "unet": defaultdict(tuple),
+ "clip": defaultdict(tuple),
+ "clip_vision": defaultdict(tuple),
+ "bvae": defaultdict(tuple),
+ "vae": defaultdict(object),
+ "lora": defaultdict(dict), # {lora_name: {UID: (model_lora, clip_lora)}}
+ }
+ self.memory_threshold = self.determine_memory_threshold(0.7)
+
+ def clean_values(self, values: str):
+ original_values = values.split("; ")
+ cleaned_values = []
+
+ for value in original_values:
+ cleaned_value = value.strip(';').strip()
+ if cleaned_value == "":
+ continue
+ try:
+ cleaned_value = int(cleaned_value)
+ except ValueError:
+ try:
+ cleaned_value = float(cleaned_value)
+ except ValueError:
+ pass
+ cleaned_values.append(cleaned_value)
+
+ return cleaned_values
+
+ def clear_unused_objects(self, desired_names: set, object_type: str):
+ keys = set(self.loaded_objects[object_type].keys())
+ for key in keys - desired_names:
+ del self.loaded_objects[object_type][key]
+
+ def get_input_value(self, entry, key):
+ val = entry["inputs"][key]
+ return val if isinstance(val, str) else val[0]
+
+ def process_pipe_loader(self, entry, desired_ckpt_names, desired_vae_names, desired_lora_names, desired_lora_settings, num_loras=3, suffix=""):
+ for idx in range(1, num_loras + 1):
+ lora_name_key = f"{suffix}lora{idx}_name"
+ desired_lora_names.add(self.get_input_value(entry, lora_name_key))
+ setting = f'{self.get_input_value(entry, lora_name_key)};{entry["inputs"][f"{suffix}lora{idx}_model_strength"]};{entry["inputs"][f"{suffix}lora{idx}_clip_strength"]}'
+ desired_lora_settings.add(setting)
+
+ desired_ckpt_names.add(self.get_input_value(entry, f"{suffix}ckpt_name"))
+ desired_vae_names.add(self.get_input_value(entry, f"{suffix}vae_name"))
+
+ def update_loaded_objects(self, prompt):
+ desired_ckpt_names = set()
+ desired_unet_names = set()
+ desired_clip_names = set()
+ desired_vae_names = set()
+ desired_lora_names = set()
+ desired_lora_settings = set()
+
+ for entry in prompt.values():
+ class_type = entry["class_type"]
+
+ if class_type in lora_widget:
+ lora_name = self.get_input_value(entry, "lora_name")
+ desired_lora_names.add(lora_name)
+ setting = f'{lora_name};{entry["inputs"]["lora_model_strength"]};{entry["inputs"]["lora_clip_strength"]}'
+ desired_lora_settings.add(setting)
+
+ if class_type in stable_diffusion_loaders:
+ desired_ckpt_names.add(self.get_input_value(entry, "ckpt_name"))
+ desired_vae_names.add(self.get_input_value(entry, "vae_name"))
+
+ elif class_type in stable_cascade_loaders:
+ desired_unet_names.add(self.get_input_value(entry, "stage_c"))
+ desired_unet_names.add(self.get_input_value(entry, "stage_b"))
+ desired_clip_names.add(self.get_input_value(entry, "clip_name"))
+ desired_vae_names.add(self.get_input_value(entry, "stage_a"))
+
+ elif class_type in cascade_vae_node:
+ encode_vae_name = self.get_input_value(entry, "encode_vae_name")
+ decode_vae_name = self.get_input_value(entry, "decode_vae_name")
+ if encode_vae_name and encode_vae_name != 'None':
+ desired_vae_names.add(encode_vae_name)
+ if decode_vae_name and decode_vae_name != 'None':
+ desired_vae_names.add(decode_vae_name)
+
+ elif class_type in model_merge_node:
+ desired_ckpt_names.add(self.get_input_value(entry, "ckpt_name_1"))
+ desired_ckpt_names.add(self.get_input_value(entry, "ckpt_name_2"))
+ vae_use = self.get_input_value(entry, "vae_use")
+ if vae_use != 'Use Model 1' and vae_use != 'Use Model 2':
+ desired_vae_names.add(vae_use)
+
+ object_types = ["ckpt", "unet", "clip", "bvae", "vae", "lora"]
+ for object_type in object_types:
+ if object_type == 'unet':
+ desired_names = desired_unet_names
+ elif object_type in ["ckpt", "clip", "bvae"]:
+ if object_type == 'clip':
+ desired_names = desired_ckpt_names.union(desired_clip_names)
+ else:
+ desired_names = desired_ckpt_names
+ elif object_type == "vae":
+ desired_names = desired_vae_names
+ else:
+ desired_names = desired_lora_names
+ self.clear_unused_objects(desired_names, object_type)
+
+ def add_to_cache(self, obj_type, key, value):
+ """
+ Add an item to the cache with the current timestamp.
+ """
+ timestamped_value = (value, time.time())
+ self.loaded_objects[obj_type][key] = timestamped_value
+
+ def determine_memory_threshold(self, percentage=0.8):
+ """
+ Determines the memory threshold as a percentage of the total available memory.
+ Args:
+ - percentage (float): The fraction of total memory to use as the threshold.
+ Should be a value between 0 and 1. Default is 0.8 (80%).
+ Returns:
+ - memory_threshold (int): Memory threshold in bytes.
+ """
+ total_memory = psutil.virtual_memory().total
+ memory_threshold = total_memory * percentage
+ return memory_threshold
+
+ def get_memory_usage(self):
+ """
+ Returns the memory usage of the current process in bytes.
+ """
+ process = psutil.Process(os.getpid())
+ return process.memory_info().rss
+
+ def eviction_based_on_memory(self):
+ """
+ Evicts objects from cache based on memory usage and priority.
+ """
+ current_memory = self.get_memory_usage()
+ if current_memory < self.memory_threshold:
+ return
+ eviction_order = ["vae", "lora", "bvae", "clip", "ckpt"]
+ for obj_type in eviction_order:
+ if current_memory < self.memory_threshold:
+ break
+ # Sort items based on age (using the timestamp)
+ items = list(self.loaded_objects[obj_type].items())
+ items.sort(key=lambda x: x[1][1]) # Sorting by timestamp
+
+ for item in items:
+ if current_memory < self.memory_threshold:
+ break
+ del self.loaded_objects[obj_type][item[0]]
+ current_memory = self.get_memory_usage()
+
+ def load_checkpoint(self, ckpt_name, config_name=None, load_vision=False):
+ cache_name = ckpt_name
+ if config_name not in [None, "Default"]:
+ cache_name = ckpt_name + "_" + config_name
+ if cache_name in self.loaded_objects["ckpt"]:
+ clip_vision = self.loaded_objects["clip_vision"][cache_name][0] if load_vision else None
+ clip = self.loaded_objects["clip"][cache_name][0]
+ return self.loaded_objects["ckpt"][cache_name][0], clip, self.loaded_objects["bvae"][cache_name][0], clip_vision
+
+ ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
+
+ output_clip = False if load_vision else True
+ output_clipvision = True if load_vision else False
+ if config_name not in [None, "Default"]:
+ config_path = folder_paths.get_full_path("configs", config_name)
+ loaded_ckpt = comfy.sd.load_checkpoint(config_path, ckpt_path, output_vae=True, output_clip=output_clip, embedding_directory=folder_paths.get_folder_paths("embeddings"))
+ else:
+ loaded_ckpt = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=output_clip, output_clipvision=output_clipvision, embedding_directory=folder_paths.get_folder_paths("embeddings"))
+
+ self.add_to_cache("ckpt", cache_name, loaded_ckpt[0])
+ self.add_to_cache("bvae", cache_name, loaded_ckpt[2])
+
+ clip = loaded_ckpt[1]
+ clip_vision = loaded_ckpt[3]
+ if clip:
+ self.add_to_cache("clip", cache_name, clip)
+ if clip_vision:
+ self.add_to_cache("clip_vision", cache_name, clip_vision)
+
+ self.eviction_based_on_memory()
+
+ return loaded_ckpt[0], clip, loaded_ckpt[2], clip_vision
+
+ def load_vae(self, vae_name):
+ if vae_name in self.loaded_objects["vae"]:
+ return self.loaded_objects["vae"][vae_name][0]
+
+ vae_path = folder_paths.get_full_path("vae", vae_name)
+ sd = comfy.utils.load_torch_file(vae_path)
+ loaded_vae = comfy.sd.VAE(sd=sd)
+ self.add_to_cache("vae", vae_name, loaded_vae)
+ self.eviction_based_on_memory()
+
+ return loaded_vae
+
+ def load_unet(self, unet_name):
+ if unet_name in self.loaded_objects["unet"]:
+ return self.loaded_objects["unet"][unet_name][0]
+
+ unet_path = folder_paths.get_full_path("unet", unet_name)
+ model = comfy.sd.load_unet(unet_path)
+ self.add_to_cache("unet", unet_name, model)
+ self.eviction_based_on_memory()
+
+ return model
+
+ def load_clip(self, clip_name, type='stable_diffusion'):
+ if type == 'stable_diffusion':
+ clip_type = comfy.sd.CLIPType.STABLE_DIFFUSION
+ else:
+ clip_type = comfy.sd.CLIPType.STABLE_CASCADE
+ clip_path = folder_paths.get_full_path("clip", clip_name)
+ load_clip = comfy.sd.load_clip(ckpt_paths=[clip_path], embedding_directory=folder_paths.get_folder_paths("embeddings"), clip_type=clip_type)
+ self.add_to_cache("clip", clip_name, load_clip)
+ self.eviction_based_on_memory()
+
+ return load_clip
+
+ def load_lora(self, lora, model=None, clip=None):
+ lora_name = lora["lora_name"]
+ model = model if model is not None else lora["model"]
+ clip = clip if clip is not None else lora["clip"]
+ model_strength = lora["model_strength"]
+ clip_strength = lora["clip_strength"]
+ lbw = lora["lbw"] if "lbw" in lora else None
+ lbw_a = lora["lbw_a"] if "lbw_a" in lora else None
+ lbw_b = lora["lbw_b"] if "lbw_b" in lora else None
+
+ model_hash = str(model)[44:-1]
+ clip_hash = str(clip)[25:-1]
+
+ unique_id = f'{model_hash};{clip_hash};{lora_name};{model_strength};{clip_strength}'
+
+ if unique_id in self.loaded_objects["lora"] and unique_id in self.loaded_objects["lora"][lora_name]:
+ return self.loaded_objects["lora"][unique_id][0]
+
+ lora_path = folder_paths.get_full_path("loras", lora_name)
+ if lora_path:
+ log_node_info("Load LORA",f"{lora_name}: {model_strength}, {clip_strength}, LBW={lbw}, A={lbw_a}, B={lbw_b}")
+ else:
+ log_node_error(f"LORA NOT FOUND", lora_name)
+
+ if lbw:
+ lbw = lora["lbw"]
+ lbw_a = lora["lbw_a"]
+ lbw_b = lora["lbw_b"]
+ if 'LoraLoaderBlockWeight //Inspire' not in NODE_CLASS_MAPPINGS:
+ raise Exception('[InspirePack Not Found] you need to install ComfyUI-Inspire-Pack')
+ cls = NODE_CLASS_MAPPINGS['LoraLoaderBlockWeight //Inspire']
+ model, clip, _ = cls().doit(model, clip, lora_name, model_strength, clip_strength, False, 0,
+ lbw_a, lbw_b, "", lbw)
+ else:
+ _lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
+ model, clip = comfy.sd.load_lora_for_models(model, clip, _lora, model_strength, clip_strength)
+
+ self.add_to_cache("lora", unique_id, (model, clip))
+ self.eviction_based_on_memory()
+
+ return model, clip
\ No newline at end of file
diff --git a/py/libs/sampler.py b/py/libs/sampler.py
new file mode 100644
index 0000000..ac6b997
--- /dev/null
+++ b/py/libs/sampler.py
@@ -0,0 +1,212 @@
+import comfy
+import torch
+import numpy as np
+import latent_preview
+from nodes import MAX_RESOLUTION
+from PIL import Image
+from typing import Dict, List, Optional, Tuple, Union, Any
+
+class easySampler:
+ def __init__(self):
+ self.last_helds: dict[str, list] = {
+ "results": [],
+ "pipe_line": [],
+ }
+
+ @staticmethod
+ def tensor2pil(image: torch.Tensor) -> Image.Image:
+ """Convert a torch tensor to a PIL image."""
+ return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
+
+ @staticmethod
+ def pil2tensor(image: Image.Image) -> torch.Tensor:
+ """Convert a PIL image to a torch tensor."""
+ return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
+
+ @staticmethod
+ def enforce_mul_of_64(d):
+ d = int(d)
+ if d <= 7:
+ d = 8
+ leftover = d % 8 # 8 is the number of pixels per byte
+ if leftover != 0: # if the number of pixels is not a multiple of 8
+ if (leftover < 4): # if the number of pixels is less than 4
+ d -= leftover # remove the leftover pixels
+ else: # if the number of pixels is more than 4
+ d += 8 - leftover # add the leftover pixels
+
+ return int(d)
+
+ @staticmethod
+ def safe_split(to_split: str, delimiter: str) -> List[str]:
+ """Split the input string and return a list of non-empty parts."""
+ parts = to_split.split(delimiter)
+ parts = [part for part in parts if part not in ('', ' ', ' ')]
+
+ while len(parts) < 2:
+ parts.append('None')
+ return parts
+
+ def common_ksampler(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent, denoise=1.0,
+ disable_noise=False, start_step=None, last_step=None, force_full_denoise=False,
+ preview_latent=True, disable_pbar=False):
+ device = comfy.model_management.get_torch_device()
+ latent_image = latent["samples"]
+
+ if disable_noise:
+ noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, device="cpu")
+ else:
+ batch_inds = latent["batch_index"] if "batch_index" in latent else None
+ noise = comfy.sample.prepare_noise(latent_image, seed, batch_inds)
+
+ noise_mask = None
+ if "noise_mask" in latent:
+ noise_mask = latent["noise_mask"]
+
+ preview_format = "JPEG"
+ if preview_format not in ["JPEG", "PNG"]:
+ preview_format = "JPEG"
+
+ previewer = False
+
+ if preview_latent:
+ previewer = latent_preview.get_previewer(device, model.model.latent_format)
+
+ pbar = comfy.utils.ProgressBar(steps)
+
+ def callback(step, x0, x, total_steps):
+ preview_bytes = None
+ if previewer:
+ preview_bytes = previewer.decode_latent_to_preview_image(preview_format, x0)
+ pbar.update_absolute(step + 1, total_steps, preview_bytes)
+
+ samples = comfy.sample.sample(model, noise, steps, cfg, sampler_name, scheduler, positive, negative,
+ latent_image,
+ denoise=denoise, disable_noise=disable_noise, start_step=start_step,
+ last_step=last_step,
+ force_full_denoise=force_full_denoise, noise_mask=noise_mask, callback=callback,
+ disable_pbar=disable_pbar, seed=seed)
+
+ out = latent.copy()
+ out["samples"] = samples
+ return out
+
+ def custom_ksampler(self, model, seed, steps, cfg, _sampler, sigmas, positive, negative, latent,
+ disable_noise=False, preview_latent=True, disable_pbar=False):
+
+ device = comfy.model_management.get_torch_device()
+ latent_image = latent["samples"]
+
+ if disable_noise:
+ noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, device="cpu")
+ else:
+ batch_inds = latent["batch_index"] if "batch_index" in latent else None
+ noise = comfy.sample.prepare_noise(latent_image, seed, batch_inds)
+
+ noise_mask = None
+ if "noise_mask" in latent:
+ noise_mask = latent["noise_mask"]
+
+ preview_format = "JPEG"
+ if preview_format not in ["JPEG", "PNG"]:
+ preview_format = "JPEG"
+
+ previewer = False
+
+ if preview_latent:
+ previewer = latent_preview.get_previewer(device, model.model.latent_format)
+
+ pbar = comfy.utils.ProgressBar(steps)
+
+ def callback(step, x0, x, total_steps):
+ preview_bytes = None
+ if previewer:
+ preview_bytes = previewer.decode_latent_to_preview_image(preview_format, x0)
+ pbar.update_absolute(step + 1, total_steps, preview_bytes)
+
+ samples = comfy.sample.sample_custom(model, noise, cfg, _sampler, sigmas, positive, negative, latent_image,
+ noise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar,
+ seed=seed)
+
+ out = latent.copy()
+ out["samples"] = samples
+ return out
+
+ def get_value_by_id(self, key: str, my_unique_id: Any) -> Optional[Any]:
+ """Retrieve value by its associated ID."""
+ try:
+ for value, id_ in self.last_helds[key]:
+ if id_ == my_unique_id:
+ return value
+ except KeyError:
+ return None
+
+ def update_value_by_id(self, key: str, my_unique_id: Any, new_value: Any) -> Union[bool, None]:
+ """Update the value associated with a given ID. Return True if updated, False if appended, None if key doesn't exist."""
+ try:
+ for i, (value, id_) in enumerate(self.last_helds[key]):
+ if id_ == my_unique_id:
+ self.last_helds[key][i] = (new_value, id_)
+ return True
+ self.last_helds[key].append((new_value, my_unique_id))
+ return False
+ except KeyError:
+ return False
+
+ def upscale(self, samples, upscale_method, scale_by, crop):
+ s = samples.copy()
+ width = self.enforce_mul_of_64(round(samples["samples"].shape[3] * scale_by))
+ height = self.enforce_mul_of_64(round(samples["samples"].shape[2] * scale_by))
+
+ if (width > MAX_RESOLUTION):
+ width = MAX_RESOLUTION
+ if (height > MAX_RESOLUTION):
+ height = MAX_RESOLUTION
+
+ s["samples"] = comfy.utils.common_upscale(samples["samples"], width, height, upscale_method, crop)
+ return (s,)
+
+ def handle_upscale(self, samples: dict, upscale_method: str, factor: float, crop: bool) -> dict:
+ """Upscale the samples if the upscale_method is not set to 'None'."""
+ if upscale_method != "None":
+ samples = self.upscale(samples, upscale_method, factor, crop)[0]
+ return samples
+
+ def init_state(self, my_unique_id: Any, key: str, default: Any) -> Any:
+ """Initialize the state by either fetching the stored value or setting a default."""
+ value = self.get_value_by_id(key, my_unique_id)
+ if value is not None:
+ return value
+ return default
+
+ def get_output(self, pipe: dict,) -> Tuple:
+ """Return a tuple of various elements fetched from the input pipe dictionary."""
+ return (
+ pipe,
+ pipe.get("images"),
+ pipe.get("model"),
+ pipe.get("positive"),
+ pipe.get("negative"),
+ pipe.get("samples"),
+ pipe.get("vae"),
+ pipe.get("clip"),
+ pipe.get("seed"),
+ )
+
+ def get_output_sdxl(self, sdxl_pipe: dict) -> Tuple:
+ """Return a tuple of various elements fetched from the input sdxl_pipe dictionary."""
+ return (
+ sdxl_pipe,
+ sdxl_pipe.get("model"),
+ sdxl_pipe.get("positive"),
+ sdxl_pipe.get("negative"),
+ sdxl_pipe.get("vae"),
+ sdxl_pipe.get("refiner_model"),
+ sdxl_pipe.get("refiner_positive"),
+ sdxl_pipe.get("refiner_negative"),
+ sdxl_pipe.get("refiner_vae"),
+ sdxl_pipe.get("samples"),
+ sdxl_pipe.get("clip"),
+ sdxl_pipe.get("images"),
+ sdxl_pipe.get("seed")
+ )
\ No newline at end of file
diff --git a/py/libs/utils.py b/py/libs/utils.py
new file mode 100644
index 0000000..7cd2165
--- /dev/null
+++ b/py/libs/utils.py
@@ -0,0 +1,93 @@
+
+def find_nearest_steps(clip_id, prompt):
+ """Find the nearest KSampler or preSampling node that references the given id."""
+ def check_link_to_clip(node_id, clip_id, visited=None, node=None):
+ """Check if a given node links directly or indirectly to a loader node."""
+ if visited is None:
+ visited = set()
+
+ if node_id in visited:
+ return False
+ visited.add(node_id)
+ if "pipe" in node["inputs"]:
+ link_ids = node["inputs"]["pipe"]
+ for id in link_ids:
+ if id != 0 and id == str(clip_id):
+ return True
+ return False
+
+ for id in prompt:
+ node = prompt[id]
+ if "Sampler" in node["class_type"] or "sampler" in node["class_type"] or "Sampling" in node["class_type"]:
+ # Check if this KSampler node directly or indirectly references the given CLIPTextEncode node
+ if check_link_to_clip(id, clip_id, None, node):
+ steps = node["inputs"]["steps"] if "steps" in node["inputs"] else 1
+ return steps
+ return 1
+
+def find_wildcards_seed(clip_id, text, prompt):
+ """ Find easy wildcards seed value"""
+ def find_link_clip_id(id, seed, wildcard_id):
+ node = prompt[id]
+ if "positive" in node['inputs']:
+ link_ids = node["inputs"]["positive"]
+ if type(link_ids) == list:
+ for id in link_ids:
+ if id != 0:
+ if id == wildcard_id:
+ wildcard_node = prompt[wildcard_id]
+ seed = wildcard_node["inputs"]["seed_num"] if "seed_num" in wildcard_node["inputs"] else None
+ return seed
+ else:
+ return find_link_clip_id(id, seed, wildcard_id)
+ else:
+ return None
+ else:
+ return None
+ if "__" in text:
+ seed = None
+ for id in prompt:
+ node = prompt[id]
+ if "wildcards" in node["class_type"]:
+ wildcard_id = id
+ return find_link_clip_id(str(clip_id), seed, wildcard_id)
+ return seed
+ else:
+ return None
+
+def is_linked_styles_selector(prompt, my_unique_id, prompt_type='positive'):
+ inputs_values = prompt[my_unique_id]['inputs'][prompt_type] if prompt_type in prompt[my_unique_id][
+ 'inputs'] else None
+ if type(inputs_values) == list and inputs_values != 'undefined' and inputs_values[0]:
+ return True if prompt[inputs_values[0]] and prompt[inputs_values[0]]['class_type'] == 'easy stylesSelector' else False
+ else:
+ return False
+
+def get_local_filepath(url, dirname, local_file_name=None):
+ """Get local file path when is already downloaded or download it"""
+ import os
+ from urllib.parse import urlparse
+ from torch.hub import download_url_to_file
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+ if not local_file_name:
+ parsed_url = urlparse(url)
+ local_file_name = os.path.basename(parsed_url.path)
+ destination = os.path.join(dirname, local_file_name)
+ if not os.path.exists(destination):
+ print(f'downloading {url} to {destination}')
+ download_url_to_file(url, destination)
+ return destination
+
+def easySave(images, filename_prefix, output_type, prompt=None, extra_pnginfo=None):
+ """Save or Preview Image"""
+ from nodes import PreviewImage, SaveImage
+ if output_type == "Hide":
+ return list()
+ if output_type == "Preview":
+ filename_prefix = 'easyPreview'
+ results = PreviewImage().save_images(images, filename_prefix, prompt, extra_pnginfo)
+ return results['ui']['images']
+ else:
+ results = SaveImage().save_images(images, filename_prefix, prompt, extra_pnginfo)
+ return results['ui']['images']
diff --git a/py/libs/xyplot.py b/py/libs/xyplot.py
new file mode 100644
index 0000000..ffb9b47
--- /dev/null
+++ b/py/libs/xyplot.py
@@ -0,0 +1,552 @@
+import os, torch
+from pathlib import Path
+from PIL import Image, ImageDraw, ImageFont
+from .utils import easySave
+from ..config import RESOURCES_DIR
+from ..log import log_node_warn
+from ..adv_encode import advanced_encode
+from .controlnet import easyControlnet
+class easyXYPlot():
+
+ def __init__(self, xyPlotData, save_prefix, image_output, prompt, extra_pnginfo, my_unique_id, sampler, easyCache):
+ self.x_node_type, self.x_type = sampler.safe_split(xyPlotData.get("x_axis"), ': ')
+ self.y_node_type, self.y_type = sampler.safe_split(xyPlotData.get("y_axis"), ': ')
+ self.x_values = xyPlotData.get("x_vals") if self.x_type != "None" else []
+ self.y_values = xyPlotData.get("y_vals") if self.y_type != "None" else []
+
+ self.grid_spacing = xyPlotData.get("grid_spacing")
+ self.latent_id = 0
+ self.output_individuals = xyPlotData.get("output_individuals")
+
+ self.x_label, self.y_label = [], []
+ self.max_width, self.max_height = 0, 0
+ self.latents_plot = []
+ self.image_list = []
+
+ self.num_cols = len(self.x_values) if len(self.x_values) > 0 else 1
+ self.num_rows = len(self.y_values) if len(self.y_values) > 0 else 1
+
+ self.total = self.num_cols * self.num_rows
+ self.num = 0
+
+ self.save_prefix = save_prefix
+ self.image_output = image_output
+ self.prompt = prompt
+ self.extra_pnginfo = extra_pnginfo
+ self.my_unique_id = my_unique_id
+
+ self.sampler = sampler
+ self.easyCache = easyCache
+
+ # Helper Functions
+ @staticmethod
+ def define_variable(plot_image_vars, value_type, value, index):
+
+ plot_image_vars[value_type] = value
+ if value_type in ["seed", "Seeds++ Batch"]:
+ value_label = f"{value}"
+ else:
+ value_label = f"{value_type}: {value}"
+
+ if "ControlNet" in value_type:
+ value_label = f"ControlNet {index + 1}"
+
+ if value_type in ['Lora', 'Checkpoint']:
+ value_label = f"{os.path.basename(os.path.splitext(value.split(',')[0])[0])}"
+
+ if value_type in ["ModelMergeBlocks"]:
+ if ":" in value:
+ line = value.split(':')
+ value_label = f"{line[0]}"
+ elif len(value) > 16:
+ value_label = f"ModelMergeBlocks {index + 1}"
+ else:
+ value_label = f"MMB: {value}"
+
+ if value_type in ["Pos Condition"]:
+ value_label = f"pos cond {index + 1}" if index>0 else f"pos cond"
+ if value_type in ["Neg Condition"]:
+ value_label = f"neg cond {index + 1}" if index>0 else f"neg cond"
+
+ if value_type in ["Positive Prompt S/R"]:
+ value_label = f"pos prompt {index + 1}" if index>0 else f"pos prompt"
+ if value_type in ["Negative Prompt S/R"]:
+ value_label = f"neg prompt {index + 1}" if index>0 else f"neg prompt"
+
+ if value_type in ["steps", "cfg", "denoise", "clip_skip",
+ "lora_model_strength", "lora_clip_strength"]:
+ value_label = f"{value_type}: {value}"
+
+ if value_type == "positive":
+ value_label = f"pos prompt {index + 1}"
+ elif value_type == "negative":
+ value_label = f"neg prompt {index + 1}"
+
+ return plot_image_vars, value_label
+
+ @staticmethod
+ def get_font(font_size):
+ return ImageFont.truetype(str(Path(os.path.join(RESOURCES_DIR, 'OpenSans-Medium.ttf'))), font_size)
+
+ @staticmethod
+ def update_label(label, value, num_items):
+ if len(label) < num_items:
+ return [*label, value]
+ return label
+
+ @staticmethod
+ def rearrange_tensors(latent, num_cols, num_rows):
+ new_latent = []
+ for i in range(num_rows):
+ for j in range(num_cols):
+ index = j * num_rows + i
+ new_latent.append(latent[index])
+ return new_latent
+
+ def calculate_background_dimensions(self):
+ border_size = int((self.max_width // 8) * 1.5) if self.y_type != "None" or self.x_type != "None" else 0
+ bg_width = self.num_cols * (self.max_width + self.grid_spacing) - self.grid_spacing + border_size * (
+ self.y_type != "None")
+ bg_height = self.num_rows * (self.max_height + self.grid_spacing) - self.grid_spacing + border_size * (
+ self.x_type != "None")
+
+ x_offset_initial = border_size if self.y_type != "None" else 0
+ y_offset = border_size if self.x_type != "None" else 0
+
+ return bg_width, bg_height, x_offset_initial, y_offset
+
+ def adjust_font_size(self, text, initial_font_size, label_width):
+ font = self.get_font(initial_font_size)
+ text_width = font.getbbox(text)
+ if text_width and text_width[2]:
+ text_width = text_width[2]
+
+ scaling_factor = 0.9
+ if text_width > (label_width * scaling_factor):
+ return int(initial_font_size * (label_width / text_width) * scaling_factor)
+ else:
+ return initial_font_size
+
+ def textsize(self, d, text, font):
+ _, _, width, height = d.textbbox((0, 0), text=text, font=font)
+ return width, height
+
+ def create_label(self, img, text, initial_font_size, is_x_label=True, max_font_size=70, min_font_size=10):
+ label_width = img.width if is_x_label else img.height
+
+ # Adjust font size
+ font_size = self.adjust_font_size(text, initial_font_size, label_width)
+ font_size = min(max_font_size, font_size) # Ensure font isn't too large
+ font_size = max(min_font_size, font_size) # Ensure font isn't too small
+
+ label_height = int(font_size * 1.5) if is_x_label else font_size
+
+ label_bg = Image.new('RGBA', (label_width, label_height), color=(255, 255, 255, 0))
+ d = ImageDraw.Draw(label_bg)
+
+ font = self.get_font(font_size)
+
+ # Check if text will fit, if not insert ellipsis and reduce text
+ if self.textsize(d, text, font=font)[0] > label_width:
+ while self.textsize(d, text + '...', font=font)[0] > label_width and len(text) > 0:
+ text = text[:-1]
+ text = text + '...'
+
+ # Compute text width and height for multi-line text
+ text_lines = text.split('\n')
+ text_widths, text_heights = zip(*[self.textsize(d, line, font=font) for line in text_lines])
+ max_text_width = max(text_widths)
+ total_text_height = sum(text_heights)
+
+ # Compute position for each line of text
+ lines_positions = []
+ current_y = 0
+ for line, line_width, line_height in zip(text_lines, text_widths, text_heights):
+ text_x = (label_width - line_width) // 2
+ text_y = current_y + (label_height - total_text_height) // 2
+ current_y += line_height
+ lines_positions.append((line, (text_x, text_y)))
+
+ # Draw each line of text
+ for line, (text_x, text_y) in lines_positions:
+ d.text((text_x, text_y), line, fill='black', font=font)
+
+ return label_bg
+
+ def sample_plot_image(self, plot_image_vars, samples, preview_latent, latents_plot, image_list, disable_noise,
+ start_step, last_step, force_full_denoise, x_value=None, y_value=None):
+ model, clip, vae, positive, negative, seed, steps, cfg = None, None, None, None, None, None, None, None
+ sampler_name, scheduler, denoise = None, None, None
+
+ a1111_prompt_style = plot_image_vars['a1111_prompt_style'] if "a1111_prompt_style" in plot_image_vars else False
+ clip = clip if clip is not None else plot_image_vars["clip"]
+ steps = plot_image_vars['steps'] if "steps" in plot_image_vars else 1
+
+ # 高级用法
+ if plot_image_vars["x_node_type"] == "advanced" or plot_image_vars["y_node_type"] == "advanced":
+
+ if self.x_type == "Seeds++ Batch" or self.y_type == "Seeds++ Batch":
+ seed = int(x_value) if self.x_type == "Seeds++ Batch" else int(y_value)
+ if self.x_type == "Steps" or self.y_type == "Steps":
+ steps = int(x_value) if self.x_type == "Steps" else int(y_value)
+ if self.x_type == "StartStep" or self.y_type == "StartStep":
+ start_step = int(x_value) if self.x_type == "StartStep" else int(y_value)
+ if self.x_type == "EndStep" or self.y_type == "EndStep":
+ last_step = int(x_value) if self.x_type == "EndStep" else int(y_value)
+ if self.x_type == "CFG Scale" or self.y_type == "CFG Scale":
+ cfg = float(x_value) if self.x_type == "CFG Scale" else float(y_value)
+ if self.x_type == "Sampler" or self.y_type == "Sampler":
+ sampler_name = x_value if self.x_type == "Sampler" else y_value
+ if self.x_type == "Scheduler" or self.y_type == "Scheduler":
+ scheduler = x_value if self.x_type == "Scheduler" else y_value
+ if self.x_type == "Sampler&Scheduler" or self.y_type == "Sampler&Scheduler":
+ arr = x_value.split(',') if self.x_type == "Sampler&Scheduler" else y_value.split(',')
+ if arr[0] and arr[0]!= 'None':
+ sampler_name = arr[0]
+ if arr[1] and arr[1]!= 'None':
+ scheduler = arr[1]
+ if self.x_type == "Denoise" or self.y_type == "Denoise":
+ denoise = float(x_value) if self.x_type == "Denoise" else float(y_value)
+ if self.x_type == "Pos Condition" or self.y_type == "Pos Condition":
+ positive = plot_image_vars['positive_cond_stack'][int(x_value)] if self.x_type == "Pos Condition" else plot_image_vars['positive_cond_stack'][int(y_value)]
+ if self.x_type == "Neg Condition" or self.y_type == "Neg Condition":
+ negative = plot_image_vars['negative_cond_stack'][int(x_value)] if self.x_type == "Neg Condition" else plot_image_vars['negative_cond_stack'][int(y_value)]
+ # 模型叠加
+ if self.x_type == "ModelMergeBlocks" or self.y_type == "ModelMergeBlocks":
+ ckpt_name_1, ckpt_name_2 = plot_image_vars['models']
+ model1, clip1, vae1, clip_vision = self.easyCache.load_checkpoint(ckpt_name_1)
+ model2, clip2, vae2, clip_vision = self.easyCache.load_checkpoint(ckpt_name_2)
+ xy_values = x_value if self.x_type == "ModelMergeBlocks" else y_value
+ if ":" in xy_values:
+ xy_line = xy_values.split(':')
+ xy_values = xy_line[1]
+
+ xy_arrs = xy_values.split(',')
+ # ModelMergeBlocks
+ if len(xy_arrs) == 3:
+ input, middle, out = xy_arrs
+ kwargs = {
+ "input": input,
+ "middle": middle,
+ "out": out
+ }
+ elif len(xy_arrs) == 30:
+ kwargs = {}
+ kwargs["time_embed."] = xy_arrs[0]
+ kwargs["label_emb."] = xy_arrs[1]
+
+ for i in range(12):
+ kwargs["input_blocks.{}.".format(i)] = xy_arrs[2+i]
+
+ for i in range(3):
+ kwargs["middle_block.{}.".format(i)] = xy_arrs[14+i]
+
+ for i in range(12):
+ kwargs["output_blocks.{}.".format(i)] = xy_arrs[17+i]
+
+ kwargs["out."] = xy_arrs[29]
+ else:
+ raise Exception("ModelMergeBlocks weight length error")
+ default_ratio = next(iter(kwargs.values()))
+
+ m = model1.clone()
+ kp = model2.get_key_patches("diffusion_model.")
+
+ for k in kp:
+ ratio = float(default_ratio)
+ k_unet = k[len("diffusion_model."):]
+
+ last_arg_size = 0
+ for arg in kwargs:
+ if k_unet.startswith(arg) and last_arg_size < len(arg):
+ ratio = float(kwargs[arg])
+ last_arg_size = len(arg)
+
+ m.add_patches({k: kp[k]}, 1.0 - ratio, ratio)
+
+ vae_use = plot_image_vars['vae_use']
+
+ clip = clip2 if vae_use == 'Use Model 2' else clip1
+ if vae_use == 'Use Model 2':
+ vae = vae2
+ elif vae_use == 'Use Model 1':
+ vae = vae1
+ else:
+ vae = self.easyCache.load_vae(vae_use)
+ model = m
+
+ # 如果存在lora_stack叠加lora
+ optional_lora_stack = plot_image_vars['lora_stack']
+ if optional_lora_stack is not None and optional_lora_stack != []:
+ for lora in optional_lora_stack:
+ model, clip = self.easyCache.load_lora(lora)
+
+ # 处理clip
+ clip = clip.clone()
+ if plot_image_vars['clip_skip'] != 0:
+ clip.clip_layer(plot_image_vars['clip_skip'])
+
+ # Lora
+ if self.x_type == "Lora" or self.y_type == "Lora":
+ model = model if model is not None else plot_image_vars["model"]
+ clip = clip if clip is not None else plot_image_vars["clip"]
+
+ xy_values = x_value if self.x_type == "Lora" else y_value
+ lora_name, lora_model_strength, lora_clip_strength = xy_values.split(",")
+ lora_stack = [{"lora_name": lora_name, "model": model, "clip" :clip, "model_strength": float(lora_model_strength), "clip_strength": float(lora_clip_strength)}]
+ if 'lora_stack' in plot_image_vars:
+ lora_stack = lora_stack + plot_image_vars['lora_stack']
+
+ if lora_stack is not None and lora_stack != []:
+ for lora in lora_stack:
+ model, clip = self.easyCache.load_lora(lora)
+
+ # CheckPoint
+ if self.x_type == "Checkpoint" or self.y_type == "Checkpoint":
+ xy_values = x_value if self.x_type == "Checkpoint" else y_value
+ ckpt_name, clip_skip, vae_name = xy_values.split(",")
+ ckpt_name = ckpt_name.replace('*', ',')
+ vae_name = vae_name.replace('*', ',')
+ model, clip, vae, clip_vision = self.easyCache.load_checkpoint(ckpt_name)
+ if vae_name != 'None':
+ vae = self.easyCache.load_vae(vae_name)
+
+ # 如果存在lora_stack叠加lora
+ optional_lora_stack = plot_image_vars['lora_stack']
+ if optional_lora_stack is not None and optional_lora_stack != []:
+ for lora in optional_lora_stack:
+ model, clip = self.easyCache.load_lora(lora)
+
+ # 处理clip
+ clip = clip.clone()
+ if clip_skip != 'None':
+ clip.clip_layer(int(clip_skip))
+ positive = plot_image_vars['positive']
+ negative = plot_image_vars['negative']
+ a1111_prompt_style = plot_image_vars['a1111_prompt_style']
+ steps = plot_image_vars['steps']
+ clip = clip if clip is not None else plot_image_vars["clip"]
+ positive = advanced_encode(clip, positive,
+ plot_image_vars['positive_token_normalization'],
+ plot_image_vars['positive_weight_interpretation'],
+ w_max=1.0,
+ apply_to_pooled="enable",
+ a1111_prompt_style=a1111_prompt_style, steps=steps)
+
+ negative = advanced_encode(clip, negative,
+ plot_image_vars['negative_token_normalization'],
+ plot_image_vars['negative_weight_interpretation'],
+ w_max=1.0,
+ apply_to_pooled="enable",
+ a1111_prompt_style=a1111_prompt_style, steps=steps)
+ if "positive_cond" in plot_image_vars:
+ positive = positive + plot_image_vars["positive_cond"]
+ if "negative_cond" in plot_image_vars:
+ negative = negative + plot_image_vars["negative_cond"]
+
+ # 提示词
+ if "Positive" in self.x_type or "Positive" in self.y_type:
+ if self.x_type == 'Positive Prompt S/R' or self.y_type == 'Positive Prompt S/R':
+ positive = x_value if self.x_type == "Positive Prompt S/R" else y_value
+
+ positive = advanced_encode(clip, positive,
+ plot_image_vars['positive_token_normalization'],
+ plot_image_vars['positive_weight_interpretation'],
+ w_max=1.0,
+ apply_to_pooled="enable", a1111_prompt_style=a1111_prompt_style, steps=steps)
+ if "positive_cond" in plot_image_vars:
+ positive = positive + plot_image_vars["positive_cond"]
+
+ if "Negative" in self.x_type or "Negative" in self.y_type:
+ if self.x_type == 'Negative Prompt S/R' or self.y_type == 'Negative Prompt S/R':
+ negative = x_value if self.x_type == "Negative Prompt S/R" else y_value
+
+ negative = advanced_encode(clip, negative,
+ plot_image_vars['negative_token_normalization'],
+ plot_image_vars['negative_weight_interpretation'],
+ w_max=1.0,
+ apply_to_pooled="enable", a1111_prompt_style=a1111_prompt_style, steps=steps)
+ if "negative_cond" in plot_image_vars:
+ positive = positive + plot_image_vars["negative_cond"]
+
+ # ControlNet
+ if "ControlNet" in self.x_type or "ControlNet" in self.y_type:
+ cnet = plot_image_vars["cnet"] if "cnet" in plot_image_vars else None
+ positive = plot_image_vars["positive_cond"] if "positive" in plot_image_vars else None
+ negative = plot_image_vars["negative_cond"] if "negative" in plot_image_vars else None
+ if cnet:
+ index = x_value if "ControlNet" in self.x_type else y_value
+ controlnet = cnet[index]
+ for index, item in enumerate(controlnet):
+ control_net_name = item[0]
+ image = item[1]
+ strength = item[2]
+ start_percent = item[3]
+ end_percent = item[4]
+ positive, negative = easyControlnet().apply(control_net_name, image, positive, negative, strength, start_percent, end_percent, None, 1)
+
+ # 简单用法
+ if plot_image_vars["x_node_type"] == "loader" or plot_image_vars["y_node_type"] == "loader":
+ model, clip, vae, clip_vision = self.easyCache.load_checkpoint(plot_image_vars['ckpt_name'])
+
+ if plot_image_vars['lora_name'] != "None":
+ lora = {"lora_name": plot_image_vars['lora_name'], "model": model, "clip": clip, "model_strength": plot_image_vars['model_strength'], "clip_strength": plot_image_vars['lora_clip_strength']}
+ model, clip = self.easyCache.load_lora(lora)
+
+ # Check for custom VAE
+ if plot_image_vars['vae_name'] not in ["Baked-VAE", "Baked VAE"]:
+ vae = self.easyCache.load_vae(plot_image_vars['vae_name'])
+
+ # CLIP skip
+ if not clip:
+ raise Exception("No CLIP found")
+ clip = clip.clone()
+ clip.clip_layer(plot_image_vars['clip_skip'])
+
+ positive = self.advanced_encode(clip, plot_image_vars['positive'],
+ plot_image_vars['positive_token_normalization'],
+ plot_image_vars['positive_weight_interpretation'], w_max=1.0,
+ apply_to_pooled="enable",a1111_prompt_style=a1111_prompt_style, steps=steps)
+
+ negative = self.advanced_encode(clip, plot_image_vars['negative'],
+ plot_image_vars['negative_token_normalization'],
+ plot_image_vars['negative_weight_interpretation'], w_max=1.0,
+ apply_to_pooled="enable", a1111_prompt_style=a1111_prompt_style, steps=steps)
+
+ model = model if model is not None else plot_image_vars["model"]
+ vae = vae if vae is not None else plot_image_vars["vae"]
+ positive = positive if positive is not None else plot_image_vars["positive_cond"]
+ negative = negative if negative is not None else plot_image_vars["negative_cond"]
+
+ seed = seed if seed is not None else plot_image_vars["seed"]
+ steps = steps if steps is not None else plot_image_vars["steps"]
+ cfg = cfg if cfg is not None else plot_image_vars["cfg"]
+ sampler_name = sampler_name if sampler_name is not None else plot_image_vars["sampler_name"]
+ scheduler = scheduler if scheduler is not None else plot_image_vars["scheduler"]
+ denoise = denoise if denoise is not None else plot_image_vars["denoise"]
+ # Sample
+ samples = self.sampler.common_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, samples,
+ denoise=denoise, disable_noise=disable_noise, preview_latent=preview_latent,
+ start_step=start_step, last_step=last_step,
+ force_full_denoise=force_full_denoise)
+
+ # Decode images and store
+ latent = samples["samples"]
+
+ # Add the latent tensor to the tensors list
+ latents_plot.append(latent)
+
+ # Decode the image
+ image = vae.decode(latent).cpu()
+
+ if self.output_individuals in [True, "True"]:
+ easySave(image, self.save_prefix, self.image_output)
+
+ # Convert the image from tensor to PIL Image and add it to the list
+ pil_image = self.sampler.tensor2pil(image)
+ image_list.append(pil_image)
+
+ # Update max dimensions
+ self.max_width = max(self.max_width, pil_image.width)
+ self.max_height = max(self.max_height, pil_image.height)
+
+ # Return the touched variables
+ return image_list, self.max_width, self.max_height, latents_plot
+
+ # Process Functions
+ def validate_xy_plot(self):
+ if self.x_type == 'None' and self.y_type == 'None':
+ log_node_warn(f'#{self.my_unique_id}','No Valid Plot Types - Reverting to default sampling...')
+ return False
+ else:
+ return True
+
+ def get_latent(self, samples):
+ # Extract the 'samples' tensor from the dictionary
+ latent_image_tensor = samples["samples"]
+
+ # Split the tensor into individual image tensors
+ image_tensors = torch.split(latent_image_tensor, 1, dim=0)
+
+ # Create a list of dictionaries containing the individual image tensors
+ latent_list = [{'samples': image} for image in image_tensors]
+
+ # Set latent only to the first latent of batch
+ if self.latent_id >= len(latent_list):
+ log_node_warn(f'#{self.my_unique_id}',f'The selected latent_id ({self.latent_id}) is out of range.')
+ log_node_warn(f'#{self.my_unique_id}', f'Automatically setting the latent_id to the last image in the list (index: {len(latent_list) - 1}).')
+
+ self.latent_id = len(latent_list) - 1
+
+ return latent_list[self.latent_id]
+
+ def get_labels_and_sample(self, plot_image_vars, latent_image, preview_latent, start_step, last_step,
+ force_full_denoise, disable_noise):
+ for x_index, x_value in enumerate(self.x_values):
+ plot_image_vars, x_value_label = self.define_variable(plot_image_vars, self.x_type, x_value,
+ x_index)
+ self.x_label = self.update_label(self.x_label, x_value_label, len(self.x_values))
+ if self.y_type != 'None':
+ for y_index, y_value in enumerate(self.y_values):
+ plot_image_vars, y_value_label = self.define_variable(plot_image_vars, self.y_type, y_value,
+ y_index)
+ self.y_label = self.update_label(self.y_label, y_value_label, len(self.y_values))
+ # ttNl(f'{CC.GREY}X: {x_value_label}, Y: {y_value_label}').t(
+ # f'Plot Values {self.num}/{self.total} ->').p()
+
+ self.image_list, self.max_width, self.max_height, self.latents_plot = self.sample_plot_image(
+ plot_image_vars, latent_image, preview_latent, self.latents_plot, self.image_list,
+ disable_noise, start_step, last_step, force_full_denoise, x_value, y_value)
+ self.num += 1
+ else:
+ # ttNl(f'{CC.GREY}X: {x_value_label}').t(f'Plot Values {self.num}/{self.total} ->').p()
+ self.image_list, self.max_width, self.max_height, self.latents_plot = self.sample_plot_image(
+ plot_image_vars, latent_image, preview_latent, self.latents_plot, self.image_list, disable_noise,
+ start_step, last_step, force_full_denoise, x_value)
+ self.num += 1
+
+ # Rearrange latent array to match preview image grid
+ self.latents_plot = self.rearrange_tensors(self.latents_plot, self.num_cols, self.num_rows)
+
+ # Concatenate the tensors along the first dimension (dim=0)
+ self.latents_plot = torch.cat(self.latents_plot, dim=0)
+
+ return self.latents_plot
+
+ def plot_images_and_labels(self):
+ # Calculate the background dimensions
+ bg_width, bg_height, x_offset_initial, y_offset = self.calculate_background_dimensions()
+
+ # Create the white background image
+ background = Image.new('RGBA', (int(bg_width), int(bg_height)), color=(255, 255, 255, 255))
+
+ output_image = []
+ for row_index in range(self.num_rows):
+ x_offset = x_offset_initial
+
+ for col_index in range(self.num_cols):
+ index = col_index * self.num_rows + row_index
+ img = self.image_list[index]
+ output_image.append(self.sampler.pil2tensor(img))
+ background.paste(img, (x_offset, y_offset))
+
+ # Handle X label
+ if row_index == 0 and self.x_type != "None":
+ label_bg = self.create_label(img, self.x_label[col_index], int(48 * img.width / 512))
+ label_y = (y_offset - label_bg.height) // 2
+ background.alpha_composite(label_bg, (x_offset, label_y))
+
+ # Handle Y label
+ if col_index == 0 and self.y_type != "None":
+ label_bg = self.create_label(img, self.y_label[row_index], int(48 * img.height / 512), False)
+ label_bg = label_bg.rotate(90, expand=True)
+
+ label_x = (x_offset - label_bg.width) // 2
+ label_y = y_offset + (img.height - label_bg.height) // 2
+ background.alpha_composite(label_bg, (label_x, label_y))
+
+ x_offset += img.width + self.grid_spacing
+
+ y_offset += img.height + self.grid_spacing
+
+ return (self.sampler.pil2tensor(background), output_image)
\ No newline at end of file
diff --git a/py/lllite.py b/py/lllite.py
index 469c505..5fd686e 100644
--- a/py/lllite.py
+++ b/py/lllite.py
@@ -1,12 +1,7 @@
import math
import torch
-import os
-import folder_paths
import comfy
-def get_file_list(filenames):
- return [file for file in filenames if file != "put_models_here.txt" and "lllite" in file]
-
def extra_options_to_module_prefix(extra_options):
# extra_options = {'transformer_index': 2, 'block_index': 8, 'original_shape': [2, 4, 128, 128], 'block': ('input', 7), 'n_heads': 20, 'dim_head': 64}
@@ -120,7 +115,6 @@ def load_control_net_lllite_patch(path, cond_image, multiplier, num_steps, start
return control_net_lllite_patch(modules)
-
class LLLiteModule(torch.nn.Module):
def __init__(
self,
@@ -240,47 +234,4 @@ class LLLiteModule(torch.nn.Module):
cx = torch.cat([cx, self.down(x)], dim=1 if self.is_conv2d else 2)
cx = self.mid(cx)
cx = self.up(cx)
- return cx * self.multiplier
-
-
-class LLLiteLoader:
- def __init__(self):
- pass
-
- @classmethod
- def INPUT_TYPES(s):
- 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,)
-
-
-NODE_CLASS_MAPPINGS = {"easy LLLiteLoader": LLLiteLoader}
-
-NODE_DISPLAY_NAME_MAPPINGS = {
- "easy LLLiteLoader": "EasyLLLite",
-}
+ return cx * self.multiplier
\ No newline at end of file
diff --git a/py/server.py b/py/server.py
index 3a71cee..2ffadb7 100644
--- a/py/server.py
+++ b/py/server.py
@@ -1,12 +1,7 @@
-import glob
-import os
import random
import server
from enum import Enum
-import folder_paths
-from folder_paths import get_directory_by_type
-from aiohttp import web
-import shutil
+
class SGmode(Enum):
FIX = 1
diff --git a/py/wildcards.py b/py/wildcards.py
index 0c391e7..9cd29f6 100644
--- a/py/wildcards.py
+++ b/py/wildcards.py
@@ -1,11 +1,10 @@
import re
import random
import os
-import nodes
import folder_paths
import yaml
import json
-from pathlib import Path
+from .log import log_node_info
easy_wildcard_dict = {}
@@ -274,22 +273,7 @@ def remove_lora_tags(string):
return result
-
-def resolve_lora_name(lora_name_cache, name):
- if os.path.exists(name):
- return name
- else:
- if len(lora_name_cache) == 0:
- lora_name_cache.extend(folder_paths.get_filename_list("loras"))
-
- for x in lora_name_cache:
- if x.endswith(name):
- return x
-
-
-def process_with_loras(wildcard_opt, model, clip, title="Positive", seed=None, can_load_lora=True, pipe_lora_stack=[]):
- lora_name_cache = []
-
+def process_with_loras(wildcard_opt, model, clip, title="Positive", seed=None, can_load_lora=True, pipe_lora_stack=[], easyCache=None):
pass1 = process(wildcard_opt, seed)
loras = extract_lora_values(pass1)
pass2 = remove_lora_tags(pass1)
@@ -298,39 +282,23 @@ def process_with_loras(wildcard_opt, model, clip, title="Positive", seed=None, c
has_loras = True if loras != [] else False
show_wildcard_prompt = True if has_noodle_key or has_loras else False
- for lora_name, model_weight, clip_weight, lbw, lbw_a, lbw_b in loras:
- if (lora_name.split('.')[-1]) not in folder_paths.supported_pt_extensions:
- lora_name = lora_name+".safetensors"
+ if can_load_lora:
+ for lora_name, model_weight, clip_weight, lbw, lbw_a, lbw_b in loras:
+ if (lora_name.split('.')[-1]) not in folder_paths.supported_pt_extensions:
+ lora_name = lora_name+".safetensors"
+ lora = {
+ "lora_name": lora_name, "model": model, "clip": clip, "model_strength": model_weight,
+ "clip_strength": clip_weight,
+ "lbw_a": lbw_a,
+ "lbw_b": lbw_b,
+ "lbw": lbw
+ }
+ model, clip = easyCache.load_lora(lora)
+ lora["model"] = model
+ lora["clip"] = clip
+ pipe_lora_stack.append(lora)
- lora_name = resolve_lora_name(lora_name_cache, lora_name)
-
- path = folder_paths.get_full_path("loras", lora_name)
-
- if path is not None:
- print(f"LORA: {lora_name}: {model_weight}, {clip_weight}, LBW={lbw}, A={lbw_a}, B={lbw_b}")
-
- def default_lora():
- return nodes.LoraLoader().load_lora(model, clip, lora_name, model_weight, clip_weight)
-
- if lbw is not None:
- cls = nodes.NODE_CLASS_MAPPINGS['LoraLoaderBlockWeight //Inspire']
- if can_load_lora:
- model, clip, _ = cls().doit(model, clip, lora_name, model_weight, clip_weight, False, 0, lbw_a, lbw_b, "", lbw)
- pipe_lora_stack.append({
- "lora_name": lora_name, "model": model, "clip": clip, "lora_model_strength": model_weight,
- "lora_clip_strength": clip_weight,
- "lbw_a": lbw_a,
- "lbw_b": lbw_b,
- "lbw": lbw
- })
- else:
- pipe_lora_stack.append({"lora_name": lora_name, "model": model, "clip": clip, "lora_model_strength": model_weight, "lora_clip_strength": clip_weight})
- if can_load_lora:
- model, clip = default_lora()
- else:
- print(f"LORA NOT FOUND: {lora_name}")
-
- # print(f"{title}: {pass2}")
- # print(f'{title}_decode:', pass1)
+ log_node_info("easy wildcards",f"{title}: {pass2}")
+ log_node_info("easy wildcards",f'{title}_decode: {pass1}')
return model, clip, pass2, pass1, show_wildcard_prompt, pipe_lora_stack
diff --git a/web/js/easy/easyInterface.js b/web/js/easy/easyInterface.js
index d0d8dba..d680303 100644
--- a/web/js/easy/easyInterface.js
+++ b/web/js/easy/easyInterface.js
@@ -106,21 +106,26 @@ try{
ui2.colors.litegraph_base.CLEAR_BACKGROUND_COLOR = '#000'
custom_theme[ui2.id] = ui2
localStorage.setItem('Comfy.Settings.Comfy.CustomColorPalettes', JSON.stringify(custom_theme));
- // localStorage.setItem('Comfy.Settings.Comfy.ColorPalette',`"${custom_theme_name}"`)
}
- const theme_name = localStorage.getItem('Comfy.Settings.Comfy.ColorPalette')
+ let theme_name = localStorage.getItem('Comfy.Settings.Comfy.ColorPalette')
// 兼容 ComfyUI Revision: 1887 [235727fe] 以上版本
if(api.storeSettings){
+ const _settings = await api.getSettings()
+ if(!theme_name && _settings['Comfy.ColorPalette']) {
+ theme_name = _settings['Comfy.ColorPalette']
+ localStorage.setItem('Comfy.Settings.Comfy.ColorPalette',`"${theme_name}"`)
+ }
const settings = {
"Comfy.CustomColorPalettes": localStorage.getItem('Comfy.Settings.Comfy.CustomColorPalettes') ? JSON.parse(localStorage.getItem('Comfy.Settings.Comfy.CustomColorPalettes')) : {},
}
if(['"custom_obsidian"','"custom_obsidian_dark"'].includes(theme_name)) settings["Comfy.ColorPalette"] = custom_theme_name
- const _settings = await api.getSettings()
+
if(!_settings || !_settings["Comfy.CustomColorPalettes"] || !_settings["Comfy.CustomColorPalettes"]["obsidian"] || _settings["Comfy.CustomColorPalettes"]["obsidian"]['version']{
let value = e.target.value
selector.element.children[1].querySelectorAll(".easyuse-prompt-styles-tag").forEach(el => {
- if(el.dataset.name.indexOf(value) != -1 || el.dataset.tag.indexOf(value) != -1 || el.classList.value.indexOf("easyuse-prompt-styles-tag-selected")!=-1){
+ const name = el.dataset.name.toLowerCase()
+ const tag = el.dataset.tag.toLowerCase()
+ const lower_value = value.toLowerCase()
+ if(name.indexOf(lower_value) != -1 || tag.indexOf(lower_value) != -1 || el.classList.value.indexOf("easyuse-prompt-styles-tag-selected")!=-1){
el.classList.remove('hide')
}
else{