fix:the search box of easy styleSelector is modified to be case-insensitive
This commit is contained in:
+16
-4
@@ -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) <br><br>
|
||||
Currently, txt2img and img2img are supported,Lora and Controlnet are comming soon!<br><br>
|
||||
|
||||
Usage:<br>
|
||||
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.<br>
|
||||
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.<br>
|
||||
<br>
|
||||
|
||||
## 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**
|
||||
|
||||
|
||||
@@ -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) <br><br>
|
||||
目前支持文生图与图生图,还未支持Lora和Controlnet,敬请期待!<br>
|
||||
stage_c 与 stage_b 可以使用[checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints)模型或原来的unet模型 <br><br>
|
||||
|
||||
使用方式:<br>
|
||||
1.选择[checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints)模型无需额外加载其余的VAE及clip<br>
|
||||
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)。<br>
|
||||
|
||||
## 更新日志
|
||||
|
||||
**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,敬请期待!<br>
|
||||
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**
|
||||
<details>
|
||||
<summary><b>v1.0.3</b></summary>
|
||||
|
||||
- 增加 `easy stylesSelector` 风格化提示词选择器
|
||||
- 增加队列进度条设置项,默认为未启用状态
|
||||
@@ -104,7 +114,7 @@
|
||||
|
||||
|
||||
- 调整UI主题
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.2</b></summary>
|
||||
|
||||
+1
-2
@@ -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')
|
||||
print('\033[34mComfy-Easy-Use (v1.0.8): \033[92mLoaded\033[0m')
|
||||
+13
-31
@@ -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}]]
|
||||
+1
-4
@@ -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 = {
|
||||
|
||||
}
|
||||
+184
-1444
File diff suppressed because it is too large
Load Diff
+1
-15
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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")
|
||||
)
|
||||
@@ -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']
|
||||
@@ -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)
|
||||
+1
-50
@@ -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
|
||||
+1
-6
@@ -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
|
||||
|
||||
+19
-51
@@ -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
|
||||
|
||||
@@ -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']<ui.version){
|
||||
await api.storeSettings(settings);
|
||||
app.ui.settings.load()
|
||||
}
|
||||
}
|
||||
console.log(theme_name)
|
||||
// 判断主题为黑曜石时改变扩展UI
|
||||
if(['"custom_obsidian"','"custom_obsidian_dark"'].includes(theme_name)){
|
||||
// canvas
|
||||
|
||||
@@ -250,7 +250,10 @@ app.registerExtension({
|
||||
oninput:(e)=>{
|
||||
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{
|
||||
|
||||
Reference in New Issue
Block a user