Upgrade to v1.1.0
This commit is contained in:
+8
-3
@@ -39,12 +39,17 @@ Usage:<br>
|
||||
|
||||
## Changelog
|
||||
|
||||
**v1.0.9 [2024-3-2]**
|
||||
**v1.1.0 (2024/3/4)**
|
||||
|
||||
- Added `easy preSamplingLayerDiffusion` and `easy kSamplerLayerDiffusion`
|
||||
- Added a convenient menu to right-click on nodes such as Loader, Presampler, Sampler, Controlnet, etc. to quickly replace nodes of the same type
|
||||
- Added `easy instantIDApplyADV` can link positive and negative
|
||||
- Fixed `easy instantIDApply` mask not input right
|
||||
-
|
||||
**v1.0.9 (ff1add1)**
|
||||
|
||||
- Fixed the error when ComfyUI-Impack-Pack and ComfyUI_InstantID were not installed
|
||||
- Fixed `easy pipeIn`
|
||||
-
|
||||
(f9d01ff)
|
||||
- Added `easy instantIDApply` - you need installed [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) fisrt, Workflow[Example](https://github.com/yolain/ComfyUI-Easy-Use/blob/main/README.en.md#InstantID)
|
||||
- Fixed `easy detailerFix` not added to the list of nodes available for saving images formatting extensions
|
||||
- Fixed `easy XYInputs: PromptSR` errors are reported when replacing negative prompts
|
||||
|
||||
@@ -43,13 +43,18 @@ stage_c 与 stage_b 可以使用[checkpoints](https://huggingface.co/stabilityai
|
||||
|
||||
## 更新日志
|
||||
|
||||
**v1.0.9 [2024-3-3]**
|
||||
**v1.1.0 (2024/3/4)**
|
||||
|
||||
- 增加 `easy preSamplingLayerDiffusion` 与 `easy kSamplerLayerDiffusion` (连接 `easy kSampler` 也能通)
|
||||
- 增加 在 加载器、预采样、采样器、Controlnet等节点上右键可快速替换同类型节点的便捷菜单
|
||||
- 增加 `easy instantIDApplyADV` 可连入 positive 与 negative
|
||||
- 修复 `easy instantIDApply` mask 未传入正确值
|
||||
|
||||
**v1.0.9 (ff1add1)**
|
||||
|
||||
- 修复未安装 ComfyUI-Impack-Pack 和 ComfyUI_InstantID 时报错
|
||||
- 修复 `easy pipeIn` - pipe设为可不必选
|
||||
|
||||
(f9d01ff)
|
||||
- 新增 `easy instantIDApply` - 需要先安装 [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID), 工作流参考[示例](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#InstantID)
|
||||
- 增加 `easy instantIDApply` - 需要先安装 [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID), 工作流参考[示例](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#InstantID)
|
||||
- 修复 `easy detailerFix` 未添加到保存图片格式化扩展名可用节点列表
|
||||
- 修复 `easy XYInputs: PromptSR` 在替换负面提示词时报错
|
||||
|
||||
|
||||
+1
-1
@@ -74,4 +74,4 @@ WEB_DIRECTORY = "./web"
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', "WEB_DIRECTORY"]
|
||||
|
||||
|
||||
print('\033[34mComfy-Easy-Use (v1.0.9): \033[92mLoaded\033[0m')
|
||||
print('\033[34mComfy-Easy-Use (v1.1.0): \033[92mLoaded\033[0m')
|
||||
+31
-1
@@ -38,7 +38,7 @@ INPAINT_DIR = os.path.join(folder_paths.models_dir, "inpaint")
|
||||
|
||||
RESOURCES_DIR = os.path.join(Path(__file__).parent.parent, "resources")
|
||||
FOOOCUS_STYLES_DIR = os.path.join(Path(__file__).parent.parent, "styles")
|
||||
|
||||
LAYER_DIFFUSION_DIR = os.path.join(folder_paths.models_dir, "layer_model")
|
||||
|
||||
FOOOCUS_STYLES_SAMPLES = 'https://raw.githubusercontent.com/lllyasviel/Fooocus/main/sdxl_styles/samples/'
|
||||
|
||||
@@ -57,4 +57,34 @@ FOOOCUS_INPAINT_PATCH = {
|
||||
"inpaint (1.32GB)": {
|
||||
"model_url": "https://huggingface.co/lllyasviel/fooocus_inpaint/resolve/main/inpaint.fooocus.patch"
|
||||
},
|
||||
}
|
||||
|
||||
LAYER_DIFFUSION_VAE = {
|
||||
"encode": {
|
||||
"model_url": "https://huggingface.co/LayerDiffusion/layerdiffusion-v1/resolve/main/vae_transparent_encoder.safetensors"
|
||||
},
|
||||
"decode": {
|
||||
"model_url": "https://huggingface.co/LayerDiffusion/layerdiffusion-v1/resolve/main/vae_transparent_decoder.safetensors"
|
||||
}
|
||||
}
|
||||
|
||||
LAYER_DIFFUSION = {
|
||||
"Only Transparent (Attention Injection)": {
|
||||
"model_url": "https://huggingface.co/LayerDiffusion/layerdiffusion-v1/resolve/main/layer_xl_transparent_attn.safetensors"
|
||||
},
|
||||
"Only Transparent (Conv Injection)": {
|
||||
"model_url": "https://huggingface.co/LayerDiffusion/layerdiffusion-v1/resolve/main/layer_xl_transparent_conv.safetensors"
|
||||
},
|
||||
"Foreground to Blending": {
|
||||
"model_url": "https://huggingface.co/LayerDiffusion/layerdiffusion-v1/resolve/main/layer_xl_fg2ble.safetensors"
|
||||
},
|
||||
"Foreground blending to Background": {
|
||||
"model_url": "https://huggingface.co/LayerDiffusion/layerdiffusion-v1/resolve/main/layer_xl_fgble2bg.safetensors"
|
||||
},
|
||||
"Background to Blending": {
|
||||
"model_url": "https://huggingface.co/LayerDiffusion/layerdiffusion-v1/resolve/main/layer_xl_bg2ble.safetensors"
|
||||
},
|
||||
"Background blending to Foreground": {
|
||||
"model_url": "https://huggingface.co/LayerDiffusion/layerdiffusion-v1/resolve/main/layer_xl_bgble2fg.safetensors"
|
||||
},
|
||||
}
|
||||
+222
-20
@@ -2,6 +2,7 @@ import sys, os, re, json, time, math
|
||||
import torch
|
||||
import folder_paths
|
||||
import comfy.utils, comfy.samplers, comfy.controlnet, comfy.model_base, comfy.model_management
|
||||
from comfy.utils import load_torch_file
|
||||
from comfy.sd import CLIP, VAE
|
||||
from comfy.model_patcher import ModelPatcher
|
||||
from comfy_extras.chainner_models import model_loading
|
||||
@@ -11,12 +12,13 @@ from PIL import Image
|
||||
|
||||
from server import PromptServer
|
||||
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 .config import MAX_SEED_NUM, BASE_RESOLUTIONS, RESOURCES_DIR, INPAINT_DIR, FOOOCUS_STYLES_DIR, FOOOCUS_INPAINT_HEAD, FOOOCUS_INPAINT_PATCH, LAYER_DIFFUSION_DIR, LAYER_DIFFUSION_VAE, LAYER_DIFFUSION
|
||||
from .log import log_node_info, log_node_error, log_node_warn
|
||||
from .wildcards import process_with_loras, get_wildcard_list, process
|
||||
from .adv_encode import advanced_encode
|
||||
from .layer_diffusion import LayerMethod, TransparentVAEDecoder
|
||||
|
||||
from .libs.utils import find_nearest_steps, find_wildcards_seed, is_linked_styles_selector, easySave, get_local_filepath, add_folder_path_and_extensions
|
||||
from .libs.utils import find_nearest_steps, find_wildcards_seed, is_linked_styles_selector, easySave, get_local_filepath, to_lora_patch_dict, add_folder_path_and_extensions
|
||||
from .libs.loader import easyLoader
|
||||
from .libs.sampler import easySampler
|
||||
from .libs.xyplot import easyXYPlot
|
||||
@@ -35,6 +37,7 @@ add_folder_path_and_extensions("mmdets", [os.path.join(model_path, "mmdets")], f
|
||||
add_folder_path_and_extensions("sams", [os.path.join(model_path, "sams")], folder_paths.supported_pt_extensions)
|
||||
add_folder_path_and_extensions("onnx", [os.path.join(model_path, "onnx")], {'.onnx'})
|
||||
add_folder_path_and_extensions("instantid", [os.path.join(model_path, "instantid")], {'.bin'})
|
||||
add_folder_path_and_extensions("layer_model", [os.path.join(model_path, "layer_model")], {'.safetensors'})
|
||||
|
||||
# ---------------------------------------------------------------提示词 开始----------------------------------------------------------------------#
|
||||
|
||||
@@ -1609,6 +1612,7 @@ class instantIDApply:
|
||||
"control_net": ("CONTROL_NET",),
|
||||
},
|
||||
"hidden": {
|
||||
"positive": None, "negative": None,
|
||||
"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"
|
||||
},
|
||||
}
|
||||
@@ -1623,11 +1627,11 @@ class instantIDApply:
|
||||
def error(self):
|
||||
raise Exception(f"[ERROR] To use instantIDApply, you need to install 'ComfyUI_InstantID'")
|
||||
|
||||
def apply(self, pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps=None, mask=None, control_net=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
|
||||
def apply(self, pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps=None, mask=None, control_net=None, positive=None, negative=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
|
||||
instantid_model, insightface_model, face_embeds = None, None, None
|
||||
model = pipe['model']
|
||||
positive = pipe['positive']
|
||||
negative = pipe['negative']
|
||||
positive = positive if positive is not None else pipe['positive']
|
||||
negative = negative if negative is not None else pipe['negative']
|
||||
# Load InstantID
|
||||
if "InstantIDModelLoader" in ALL_NODE_CLASS_MAPPINGS:
|
||||
load_instant_cls = ALL_NODE_CLASS_MAPPINGS["InstantIDModelLoader"]
|
||||
@@ -1644,7 +1648,7 @@ class instantIDApply:
|
||||
if "ApplyInstantID" in ALL_NODE_CLASS_MAPPINGS:
|
||||
instantid_apply = ALL_NODE_CLASS_MAPPINGS['ApplyInstantID']
|
||||
control_net = easyControlnet().load_controlnet(control_net_name, control_net, cn_soft_weights)
|
||||
model, positive, negative = instantid_apply().apply_instantid(instantid_model, insightface_model, control_net, image, model, positive, negative, start_at, end_at, weight=weight, ip_weight=None, cn_strength=cn_strength, noise=noise, image_kps=image_kps, mask=None)
|
||||
model, positive, negative = instantid_apply().apply_instantid(instantid_model, insightface_model, control_net, image, model, positive, negative, start_at, end_at, weight=weight, ip_weight=None, cn_strength=cn_strength, noise=noise, image_kps=image_kps, mask=mask)
|
||||
else:
|
||||
self.error()
|
||||
|
||||
@@ -1665,6 +1669,52 @@ class instantIDApply:
|
||||
del pipe
|
||||
|
||||
return (new_pipe, model, positive, negative)
|
||||
|
||||
#Apply InstantID Advanced
|
||||
|
||||
class instantIDApplyAdvanced:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required":{
|
||||
"pipe": ("PIPE_LINE",),
|
||||
"image": ("IMAGE",),
|
||||
"instantid_file": (folder_paths.get_filename_list("instantid"),),
|
||||
"insightface": (["CPU", "CUDA", "ROCM"],),
|
||||
"control_net_name": (folder_paths.get_filename_list("controlnet"),),
|
||||
"cn_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||
"cn_soft_weights": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},),
|
||||
"weight": ("FLOAT", {"default": .8, "min": 0.0, "max": 5.0, "step": 0.01, }),
|
||||
"start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001, }),
|
||||
"end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001, }),
|
||||
"noise": ("FLOAT", {"default": 0.35, "min": 0.0, "max": 1.0, "step": 0.05, }),
|
||||
},
|
||||
"optional": {
|
||||
"image_kps": ("IMAGE",),
|
||||
"mask": ("MASK",),
|
||||
"control_net": ("CONTROL_NET",),
|
||||
"positive": ("CONDITIONING",),
|
||||
"negative": ("CONDITIONING",),
|
||||
},
|
||||
"hidden": {
|
||||
"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("PIPE_LINE", "MODEL", "CONDITIONING", "CONDITIONING")
|
||||
RETURN_NAMES = ("pipe", "model", "positive", "negative")
|
||||
OUTPUT_NODE = True
|
||||
|
||||
FUNCTION = "apply"
|
||||
CATEGORY = "EasyUse/__for_testing"
|
||||
|
||||
def apply(self, pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps=None, mask=None, control_net=None, positive=None, negative=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
|
||||
|
||||
return instantIDApply().apply(pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps, mask, control_net, positive, negative, prompt, extra_pnginfo, my_unique_id)
|
||||
#---------------------------------------------------------------预采样 开始----------------------------------------------------------------------#
|
||||
|
||||
# 预采样设置(基础)
|
||||
@@ -2019,6 +2069,70 @@ class cascadeSettings:
|
||||
|
||||
return {"ui": {"value": [seed_num]}, "result": (new_pipe,)}
|
||||
|
||||
# layerDiffusion预采样参数
|
||||
class layerDiffusionSettings:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required":
|
||||
{
|
||||
"pipe": ("PIPE_LINE",),
|
||||
"method": ([LayerMethod.FG_ONLY_ATTN.value, LayerMethod.FG_ONLY_CONV.value],),
|
||||
"weight": ("FLOAT",{"default": 1.0, "min": -1, "max": 3, "step": 0.05},),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default": "euler_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": MAX_SEED_NUM}),
|
||||
},
|
||||
"optional": {
|
||||
# "image_to_latent": ("IMAGE",),
|
||||
# "latent": ("LATENT",),
|
||||
},
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("PIPE_LINE",)
|
||||
RETURN_NAMES = ("pipe",)
|
||||
OUTPUT_NODE = True
|
||||
|
||||
FUNCTION = "settings"
|
||||
CATEGORY = "EasyUse/PreSampling"
|
||||
|
||||
|
||||
def settings(self, pipe, method, weight, steps, cfg, sampler_name, scheduler, denoise, seed_num, prompt=None, extra_pnginfo=None, my_unique_id=None):
|
||||
|
||||
new_pipe = {
|
||||
"model": pipe['model'],
|
||||
"positive": pipe['positive'],
|
||||
"negative": pipe['negative'],
|
||||
"vae": pipe['vae'],
|
||||
"clip": pipe['clip'],
|
||||
|
||||
"samples": pipe['samples'],
|
||||
"images": pipe['images'],
|
||||
"seed": seed_num,
|
||||
|
||||
"loader_settings": {
|
||||
**pipe["loader_settings"],
|
||||
"steps": steps,
|
||||
"cfg": cfg,
|
||||
"sampler_name": sampler_name,
|
||||
"scheduler": scheduler,
|
||||
"denoise": denoise,
|
||||
"add_noise": "enabled",
|
||||
"layer_diffusion_method": method,
|
||||
"layer_diffusion_weight": weight,
|
||||
}
|
||||
}
|
||||
|
||||
del pipe
|
||||
|
||||
return {"ui": {"value": [seed_num]}, "result": (new_pipe,)}
|
||||
|
||||
# 预采样设置(动态CFG)
|
||||
from .dynthres_core import DynThresh
|
||||
@@ -2164,8 +2278,9 @@ class dynamicThresholdingFull:
|
||||
# 完整采样器
|
||||
class samplerFull:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
def __init__(self) -> None:
|
||||
self.vae_transparent_decoder = None
|
||||
self.vae_transparent_encoder = None
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -2231,6 +2346,17 @@ class samplerFull:
|
||||
if add_noise == "disable":
|
||||
disable_noise = True
|
||||
|
||||
# LayerDiffusion
|
||||
if "layer_diffusion_method" in pipe['loader_settings']:
|
||||
method = LayerMethod(pipe['loader_settings']['layer_diffusion_method'])
|
||||
weight = pipe['loader_settings']['layer_diffusion_weight'] if 'layer_diffusion_weight' in pipe['loader_settings'] else 1.0
|
||||
model_file = get_local_filepath(LAYER_DIFFUSION[method.value]["model_url"], LAYER_DIFFUSION_DIR)
|
||||
layer_lora_state_dict = load_torch_file(model_file)
|
||||
layer_lora_patch_dict = to_lora_patch_dict(layer_lora_state_dict)
|
||||
work_model = samp_model.clone()
|
||||
work_model.add_patches(layer_lora_patch_dict, weight)
|
||||
samp_model = work_model
|
||||
|
||||
def downscale_model_unet(samp_model):
|
||||
if downscale_options is None:
|
||||
return samp_model
|
||||
@@ -2271,6 +2397,10 @@ class samplerFull:
|
||||
image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id,
|
||||
preview_latent, force_full_denoise=force_full_denoise, disable_noise=disable_noise):
|
||||
|
||||
alpha = None
|
||||
layer_diffusion_method = pipe['loader_settings']['layer_diffusion_method'] if 'layer_diffusion_method' in pipe['loader_settings'] else None
|
||||
# LayerDiffusion Decode
|
||||
|
||||
# Downscale Model Unet
|
||||
if samp_model is not None:
|
||||
samp_model = downscale_model_unet(samp_model)
|
||||
@@ -2288,11 +2418,39 @@ class samplerFull:
|
||||
else:
|
||||
samp_images = samp_vae.decode(latent).cpu()
|
||||
|
||||
# LayerDiffusion Decode
|
||||
if layer_diffusion_method is not None:
|
||||
if self.vae_transparent_decoder is None:
|
||||
decoder_file = get_local_filepath(LAYER_DIFFUSION_VAE['decode']["model_url"], LAYER_DIFFUSION_DIR)
|
||||
self.vae_transparent_decoder = TransparentVAEDecoder(
|
||||
load_torch_file(decoder_file),
|
||||
device=comfy.model_management.get_torch_device(),
|
||||
dtype=(torch.float16 if comfy.model_management.should_use_fp16() else torch.float32),
|
||||
)
|
||||
|
||||
pixel = samp_images.movedim(-1, 1) # [B, H, W, C] => [B, C, H, W]
|
||||
pixel_with_alpha = self.vae_transparent_decoder.decode_pixel(pixel, latent)
|
||||
# [B, C, H, W] => [B, H, W, C]
|
||||
pixel_with_alpha = pixel_with_alpha.movedim(1, -1)
|
||||
image = pixel_with_alpha[..., 1:]
|
||||
alpha = pixel_with_alpha[..., 0]
|
||||
|
||||
# mask to image
|
||||
out = image.movedim(-1, 1)
|
||||
if out.shape[1] == 3: # RGB
|
||||
out = torch.cat([out, torch.ones_like(out[:, :1, :, :])], dim=1)
|
||||
for i in range(out.shape[0]):
|
||||
out[i, 3, :, :] = alpha
|
||||
|
||||
new_images = out.movedim(1, -1)
|
||||
else:
|
||||
new_images = samp_images
|
||||
|
||||
# 推理总耗时(包含解码)
|
||||
end_decode_time = int(time.time() * 1000)
|
||||
spent_time = '扩散:' + str((end_time-start_time)/1000)+'秒, 解码:' + str((end_decode_time-end_time)/1000)+'秒'
|
||||
|
||||
results = easySave(samp_images, save_prefix, image_output, prompt, extra_pnginfo)
|
||||
results = easySave(new_images, save_prefix, image_output, prompt, extra_pnginfo)
|
||||
sampler.update_value_by_id("results", my_unique_id, results)
|
||||
|
||||
# Clean loaded_objects
|
||||
@@ -2306,7 +2464,7 @@ class samplerFull:
|
||||
"clip": samp_clip,
|
||||
|
||||
"samples": samp_samples,
|
||||
"images": samp_images,
|
||||
"images": new_images,
|
||||
"seed": samp_seed,
|
||||
|
||||
"loader_settings": {
|
||||
@@ -2317,17 +2475,19 @@ class samplerFull:
|
||||
|
||||
sampler.update_value_by_id("pipe_line", my_unique_id, new_pipe)
|
||||
|
||||
result = (new_pipe, new_images, samp_images, alpha) if layer_diffusion_method is not None else sampler.get_output(new_pipe,)
|
||||
|
||||
del pipe
|
||||
|
||||
if image_output in ("Hide", "Hide/Save"):
|
||||
return {"ui": {},
|
||||
"result": sampler.get_output(new_pipe, )}
|
||||
"result": result}
|
||||
|
||||
if image_output in ("Sender", "Sender/Save"):
|
||||
PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results})
|
||||
|
||||
return {"ui": {"images": results},
|
||||
"result": sampler.get_output(new_pipe, )}
|
||||
"result": result}
|
||||
|
||||
def process_xyPlot(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, samp_negative,
|
||||
steps, cfg, sampler_name, scheduler, denoise,
|
||||
@@ -2472,9 +2632,9 @@ class samplerSimple:
|
||||
|
||||
def run(self, pipe, image_output, link_id, save_prefix, model=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False):
|
||||
|
||||
return samplerFull.run(self, pipe, None, None,None,None,None, image_output, link_id, save_prefix,
|
||||
None, model, None, None, None, None, None, None,
|
||||
tile_size, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise)
|
||||
return samplerFull().run(pipe, None, None, None, None, None, image_output, link_id, save_prefix,
|
||||
None, model, None, None, None, None, None, None,
|
||||
None, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise)
|
||||
|
||||
# 简易采样器 (Tiled)
|
||||
class samplerSimpleTiled:
|
||||
@@ -2507,10 +2667,44 @@ class samplerSimpleTiled:
|
||||
CATEGORY = "EasyUse/Sampler"
|
||||
|
||||
def run(self, pipe, tile_size=512, image_output='preview', link_id=0, save_prefix='ComfyUI', model=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False):
|
||||
return samplerFull.run(self, pipe, None, None,None,None,None, image_output, link_id, save_prefix,
|
||||
return samplerFull().run(pipe, None, None,None,None,None, image_output, link_id, save_prefix,
|
||||
None, model, None, None, None, None, None, None,
|
||||
tile_size, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise)
|
||||
|
||||
# 简易采样器 (LayerDiffusion)
|
||||
class samplerSimpleLayerDiffusion:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required":
|
||||
{"pipe": ("PIPE_LINE",),
|
||||
"image_output": (["Hide", "Preview", "Save", "Hide/Save", "Sender", "Sender/Save"],{"default": "Preview"}),
|
||||
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
|
||||
"save_prefix": ("STRING", {"default": "ComfyUI"})
|
||||
},
|
||||
"optional": {
|
||||
"model": ("MODEL",),
|
||||
},
|
||||
"hidden": {
|
||||
"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID",
|
||||
"embeddingsList": (folder_paths.get_filename_list("embeddings"),)
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("PIPE_LINE", "IMAGE", "IMAGE", "MASK")
|
||||
RETURN_NAMES = ("pipe", "alpha_image", "original_image", "alpha")
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "EasyUse/Sampler"
|
||||
|
||||
def run(self, pipe, image_output='preview', link_id=0, save_prefix='ComfyUI', model=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False):
|
||||
return samplerFull().run(pipe, None, None,None,None,None, image_output, link_id, save_prefix,
|
||||
None, model, None, None, None, None, None, None,
|
||||
None, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise)
|
||||
|
||||
# 简易采样器(收缩Unet)
|
||||
class samplerSimpleDownscaleUnet:
|
||||
|
||||
@@ -2574,7 +2768,7 @@ class samplerSimpleDownscaleUnet:
|
||||
"upscale_method": upscale_method
|
||||
}
|
||||
|
||||
return samplerFull.run(self, pipe, None, None,None,None,None, image_output, link_id, save_prefix,
|
||||
return samplerFull().run(pipe, None, None,None,None,None, image_output, link_id, save_prefix,
|
||||
None, model, None, None, None, None, None, None,
|
||||
tile_size, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise, downscale_options)
|
||||
# 简易采样器 (内补)
|
||||
@@ -2670,7 +2864,8 @@ class samplerSimpleInpainting:
|
||||
else:
|
||||
new_pipe = pipe
|
||||
del pipe
|
||||
return samplerFull.run(self, new_pipe, None, None,None,None,None, image_output, link_id, save_prefix,
|
||||
|
||||
return samplerFull().run(new_pipe, None, None,None,None,None, image_output, link_id, save_prefix,
|
||||
None, model, None, None, None, None, None, None,
|
||||
tile_size, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise)
|
||||
|
||||
@@ -2996,7 +3191,7 @@ class samplerCascadeSimple:
|
||||
|
||||
def run(self, pipe, image_output, link_id, save_prefix, model_c=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False):
|
||||
|
||||
return samplerCascadeFull.run(self, pipe, None, None,None, None,None,None,None, image_output, link_id, save_prefix,
|
||||
return samplerCascadeFull().run(pipe, None, None,None, None,None,None,None, image_output, link_id, save_prefix,
|
||||
None, None, None, model_c, tile_size, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise)
|
||||
|
||||
class unsampler:
|
||||
@@ -4730,10 +4925,12 @@ NODE_CLASS_MAPPINGS = {
|
||||
"easy preSamplingSdTurbo": sdTurboSettings,
|
||||
"easy preSamplingDynamicCFG": dynamicCFGSettings,
|
||||
"easy preSamplingCascade": cascadeSettings,
|
||||
"easy preSamplingLayerDiffusion": layerDiffusionSettings,
|
||||
# kSampler k采样器
|
||||
"easy fullkSampler": samplerFull,
|
||||
"easy kSampler": samplerSimple,
|
||||
"easy kSamplerTiled": samplerSimpleTiled,
|
||||
"easy kSamplerLayerDiffusion": samplerSimpleLayerDiffusion,
|
||||
"easy kSamplerInpainting": samplerSimpleInpainting,
|
||||
"easy kSamplerDownscaleUnet": samplerSimpleDownscaleUnet,
|
||||
"easy kSamplerSDTurbo": samplerSDTurbo,
|
||||
@@ -4775,7 +4972,9 @@ NODE_CLASS_MAPPINGS = {
|
||||
# __for_testing 测试
|
||||
"easy fooocusInpaintLoader": fooocusInpaintLoader,
|
||||
"easy instantIDApply": instantIDApply,
|
||||
"easy instantIDApplyADV": instantIDApplyAdvanced,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
# prompt 提示词
|
||||
"easy positive": "Positive",
|
||||
@@ -4807,10 +5006,12 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"easy preSamplingSdTurbo": "PreSampling (SDTurbo)",
|
||||
"easy preSamplingDynamicCFG": "PreSampling (DynamicCFG)",
|
||||
"easy preSamplingCascade": "PreSampling (Cascade)",
|
||||
"easy preSamplingLayerDiffusion": "PreSampling (LayerDiffusion)",
|
||||
# kSampler k采样器
|
||||
"easy kSampler": "EasyKSampler",
|
||||
"easy fullkSampler": "EasyKSampler (Full)",
|
||||
"easy kSamplerTiled": "EasyKSampler (Tiled Decode)",
|
||||
"easy kSamplerLayerDiffusion": "EasyKSampler (LayerDiffusion)",
|
||||
"easy kSamplerInpainting": "EasyKSampler (Inpainting)",
|
||||
"easy kSamplerDownscaleUnet": "EasyKsampler (Downscale Unet)",
|
||||
"easy kSamplerSDTurbo": "EasyKSampler (SDTurbo)",
|
||||
@@ -4851,5 +5052,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"dynamicThresholdingFull": "DynamicThresholdingFull",
|
||||
# __for_testing 测试
|
||||
"easy fooocusInpaintLoader": "Load Fooocus Inpaint",
|
||||
"easy instantIDApply": "Easy Apply InstantID"
|
||||
"easy instantIDApply": "Easy Apply InstantID",
|
||||
"easy instantIDApplyADV": "Easy Apply InstantID (Advanced)",
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
import torch.nn as nn
|
||||
import torch
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from enum import Enum
|
||||
from tqdm import tqdm
|
||||
from typing import Optional, Tuple
|
||||
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
||||
from diffusers.models.modeling_utils import ModelMixin
|
||||
from diffusers.models.unet_2d_blocks import UNetMidBlock2D, get_down_block, get_up_block
|
||||
|
||||
|
||||
def zero_module(module):
|
||||
"""
|
||||
Zero out the parameters of a module and return it.
|
||||
"""
|
||||
for p in module.parameters():
|
||||
p.detach().zero_()
|
||||
return module
|
||||
|
||||
|
||||
class LayerMethod(Enum):
|
||||
FG_ONLY_ATTN = "Only Transparent (Attention Injection)"
|
||||
FG_ONLY_CONV = "Only Transparent (Conv Injection)"
|
||||
FG_TO_BLEND = "Foreground to Blending"
|
||||
FG_BLEND_TO_BG = "Foreground Blending to Background"
|
||||
BG_TO_BLEND = "Background to Blending"
|
||||
BG_BLEND_TO_FG = "Background Blending to Foreground"
|
||||
|
||||
class LatentTransparencyOffsetEncoder(torch.nn.Module):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.blocks = torch.nn.Sequential(
|
||||
torch.nn.Conv2d(4, 32, kernel_size=3, padding=1, stride=1),
|
||||
nn.SiLU(),
|
||||
torch.nn.Conv2d(32, 32, kernel_size=3, padding=1, stride=1),
|
||||
nn.SiLU(),
|
||||
torch.nn.Conv2d(32, 64, kernel_size=3, padding=1, stride=2),
|
||||
nn.SiLU(),
|
||||
torch.nn.Conv2d(64, 64, kernel_size=3, padding=1, stride=1),
|
||||
nn.SiLU(),
|
||||
torch.nn.Conv2d(64, 128, kernel_size=3, padding=1, stride=2),
|
||||
nn.SiLU(),
|
||||
torch.nn.Conv2d(128, 128, kernel_size=3, padding=1, stride=1),
|
||||
nn.SiLU(),
|
||||
torch.nn.Conv2d(128, 256, kernel_size=3, padding=1, stride=2),
|
||||
nn.SiLU(),
|
||||
torch.nn.Conv2d(256, 256, kernel_size=3, padding=1, stride=1),
|
||||
nn.SiLU(),
|
||||
zero_module(torch.nn.Conv2d(256, 4, kernel_size=3, padding=1, stride=1)),
|
||||
)
|
||||
|
||||
def __call__(self, x):
|
||||
return self.blocks(x)
|
||||
|
||||
|
||||
# 1024 * 1024 * 3 -> 16 * 16 * 512 -> 1024 * 1024 * 3
|
||||
class UNet1024(ModelMixin, ConfigMixin):
|
||||
@register_to_config
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int = 3,
|
||||
out_channels: int = 3,
|
||||
down_block_types: Tuple[str] = (
|
||||
"DownBlock2D",
|
||||
"DownBlock2D",
|
||||
"DownBlock2D",
|
||||
"DownBlock2D",
|
||||
"AttnDownBlock2D",
|
||||
"AttnDownBlock2D",
|
||||
"AttnDownBlock2D",
|
||||
),
|
||||
up_block_types: Tuple[str] = (
|
||||
"AttnUpBlock2D",
|
||||
"AttnUpBlock2D",
|
||||
"AttnUpBlock2D",
|
||||
"UpBlock2D",
|
||||
"UpBlock2D",
|
||||
"UpBlock2D",
|
||||
"UpBlock2D",
|
||||
),
|
||||
block_out_channels: Tuple[int] = (32, 32, 64, 128, 256, 512, 512),
|
||||
layers_per_block: int = 2,
|
||||
mid_block_scale_factor: float = 1,
|
||||
downsample_padding: int = 1,
|
||||
downsample_type: str = "conv",
|
||||
upsample_type: str = "conv",
|
||||
dropout: float = 0.0,
|
||||
act_fn: str = "silu",
|
||||
attention_head_dim: Optional[int] = 8,
|
||||
norm_num_groups: int = 4,
|
||||
norm_eps: float = 1e-5,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
# input
|
||||
self.conv_in = nn.Conv2d(
|
||||
in_channels, block_out_channels[0], kernel_size=3, padding=(1, 1)
|
||||
)
|
||||
self.latent_conv_in = zero_module(
|
||||
nn.Conv2d(4, block_out_channels[2], kernel_size=1)
|
||||
)
|
||||
|
||||
self.down_blocks = nn.ModuleList([])
|
||||
self.mid_block = None
|
||||
self.up_blocks = nn.ModuleList([])
|
||||
|
||||
# down
|
||||
output_channel = block_out_channels[0]
|
||||
for i, down_block_type in enumerate(down_block_types):
|
||||
input_channel = output_channel
|
||||
output_channel = block_out_channels[i]
|
||||
is_final_block = i == len(block_out_channels) - 1
|
||||
|
||||
down_block = get_down_block(
|
||||
down_block_type,
|
||||
num_layers=layers_per_block,
|
||||
in_channels=input_channel,
|
||||
out_channels=output_channel,
|
||||
temb_channels=None,
|
||||
add_downsample=not is_final_block,
|
||||
resnet_eps=norm_eps,
|
||||
resnet_act_fn=act_fn,
|
||||
resnet_groups=norm_num_groups,
|
||||
attention_head_dim=(
|
||||
attention_head_dim
|
||||
if attention_head_dim is not None
|
||||
else output_channel
|
||||
),
|
||||
downsample_padding=downsample_padding,
|
||||
resnet_time_scale_shift="default",
|
||||
downsample_type=downsample_type,
|
||||
dropout=dropout,
|
||||
)
|
||||
self.down_blocks.append(down_block)
|
||||
|
||||
# mid
|
||||
self.mid_block = UNetMidBlock2D(
|
||||
in_channels=block_out_channels[-1],
|
||||
temb_channels=None,
|
||||
dropout=dropout,
|
||||
resnet_eps=norm_eps,
|
||||
resnet_act_fn=act_fn,
|
||||
output_scale_factor=mid_block_scale_factor,
|
||||
resnet_time_scale_shift="default",
|
||||
attention_head_dim=(
|
||||
attention_head_dim
|
||||
if attention_head_dim is not None
|
||||
else block_out_channels[-1]
|
||||
),
|
||||
resnet_groups=norm_num_groups,
|
||||
attn_groups=None,
|
||||
add_attention=True,
|
||||
)
|
||||
|
||||
# up
|
||||
reversed_block_out_channels = list(reversed(block_out_channels))
|
||||
output_channel = reversed_block_out_channels[0]
|
||||
for i, up_block_type in enumerate(up_block_types):
|
||||
prev_output_channel = output_channel
|
||||
output_channel = reversed_block_out_channels[i]
|
||||
input_channel = reversed_block_out_channels[
|
||||
min(i + 1, len(block_out_channels) - 1)
|
||||
]
|
||||
|
||||
is_final_block = i == len(block_out_channels) - 1
|
||||
|
||||
up_block = get_up_block(
|
||||
up_block_type,
|
||||
num_layers=layers_per_block + 1,
|
||||
in_channels=input_channel,
|
||||
out_channels=output_channel,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=None,
|
||||
add_upsample=not is_final_block,
|
||||
resnet_eps=norm_eps,
|
||||
resnet_act_fn=act_fn,
|
||||
resnet_groups=norm_num_groups,
|
||||
attention_head_dim=(
|
||||
attention_head_dim
|
||||
if attention_head_dim is not None
|
||||
else output_channel
|
||||
),
|
||||
resnet_time_scale_shift="default",
|
||||
upsample_type=upsample_type,
|
||||
dropout=dropout,
|
||||
)
|
||||
self.up_blocks.append(up_block)
|
||||
prev_output_channel = output_channel
|
||||
|
||||
# out
|
||||
self.conv_norm_out = nn.GroupNorm(
|
||||
num_channels=block_out_channels[0], num_groups=norm_num_groups, eps=norm_eps
|
||||
)
|
||||
self.conv_act = nn.SiLU()
|
||||
self.conv_out = nn.Conv2d(
|
||||
block_out_channels[0], out_channels, kernel_size=3, padding=1
|
||||
)
|
||||
|
||||
def forward(self, x, latent):
|
||||
sample_latent = self.latent_conv_in(latent)
|
||||
sample = self.conv_in(x)
|
||||
emb = None
|
||||
|
||||
down_block_res_samples = (sample,)
|
||||
for i, downsample_block in enumerate(self.down_blocks):
|
||||
if i == 3:
|
||||
sample = sample + sample_latent
|
||||
|
||||
sample, res_samples = downsample_block(hidden_states=sample, temb=emb)
|
||||
down_block_res_samples += res_samples
|
||||
|
||||
sample = self.mid_block(sample, emb)
|
||||
|
||||
for upsample_block in self.up_blocks:
|
||||
res_samples = down_block_res_samples[-len(upsample_block.resnets) :]
|
||||
down_block_res_samples = down_block_res_samples[
|
||||
: -len(upsample_block.resnets)
|
||||
]
|
||||
sample = upsample_block(sample, res_samples, emb)
|
||||
|
||||
sample = self.conv_norm_out(sample)
|
||||
sample = self.conv_act(sample)
|
||||
sample = self.conv_out(sample)
|
||||
return sample
|
||||
|
||||
|
||||
def checkerboard(shape):
|
||||
return np.indices(shape).sum(axis=0) % 2
|
||||
|
||||
|
||||
def fill_checkerboard_bg(y: torch.Tensor) -> torch.Tensor:
|
||||
alpha = y[..., :1]
|
||||
fg = y[..., 1:]
|
||||
B, H, W, C = fg.shape
|
||||
cb = checkerboard(shape=(H // 64, W // 64))
|
||||
cb = cv2.resize(cb, (W, H), interpolation=cv2.INTER_NEAREST)
|
||||
cb = (0.5 + (cb - 0.5) * 0.1)[None, ..., None]
|
||||
cb = torch.from_numpy(cb).to(fg)
|
||||
vis = fg * alpha + cb * (1 - alpha)
|
||||
return vis
|
||||
|
||||
|
||||
class TransparentVAEDecoder:
|
||||
def __init__(self, sd, device, dtype):
|
||||
self.load_device = device
|
||||
self.dtype = dtype
|
||||
|
||||
model = UNet1024(in_channels=3, out_channels=4)
|
||||
model.load_state_dict(sd, strict=True)
|
||||
model.to(self.load_device, dtype=self.dtype)
|
||||
model.eval()
|
||||
self.model = model
|
||||
|
||||
@torch.no_grad()
|
||||
def estimate_single_pass(self, pixel, latent):
|
||||
y = self.model(pixel, latent)
|
||||
return y
|
||||
|
||||
@torch.no_grad()
|
||||
def estimate_augmented(self, pixel, latent):
|
||||
args = [
|
||||
[False, 0],
|
||||
[False, 1],
|
||||
[False, 2],
|
||||
[False, 3],
|
||||
[True, 0],
|
||||
[True, 1],
|
||||
[True, 2],
|
||||
[True, 3],
|
||||
]
|
||||
|
||||
result = []
|
||||
|
||||
for flip, rok in tqdm(args):
|
||||
feed_pixel = pixel.clone()
|
||||
feed_latent = latent.clone()
|
||||
|
||||
if flip:
|
||||
feed_pixel = torch.flip(feed_pixel, dims=(3,))
|
||||
feed_latent = torch.flip(feed_latent, dims=(3,))
|
||||
|
||||
feed_pixel = torch.rot90(feed_pixel, k=rok, dims=(2, 3))
|
||||
feed_latent = torch.rot90(feed_latent, k=rok, dims=(2, 3))
|
||||
|
||||
eps = self.estimate_single_pass(feed_pixel, feed_latent).clip(0, 1)
|
||||
eps = torch.rot90(eps, k=-rok, dims=(2, 3))
|
||||
|
||||
if flip:
|
||||
eps = torch.flip(eps, dims=(3,))
|
||||
|
||||
result += [eps]
|
||||
|
||||
result = torch.stack(result, dim=0)
|
||||
median = torch.median(result, dim=0).values
|
||||
return median
|
||||
|
||||
@torch.no_grad()
|
||||
def decode_pixel(self, pixel, latent):
|
||||
# pixel.shape = [B, C=3, H, W]
|
||||
assert pixel.shape[1] == 3
|
||||
pixel = pixel.to(device=self.load_device, dtype=self.dtype)
|
||||
latent = latent.to(device=self.load_device, dtype=self.dtype)
|
||||
# y.shape = [B, C=4, H, W]
|
||||
y = self.estimate_augmented(pixel, latent)
|
||||
y = y.clip(0, 1)
|
||||
assert y.shape[1] == 4
|
||||
return y
|
||||
|
||||
class TransparentVAEEncoder:
|
||||
def __init__(self, sd, device, dtype):
|
||||
self.load_device = device
|
||||
self.dtype = dtype
|
||||
|
||||
model = LatentTransparencyOffsetEncoder()
|
||||
model.load_state_dict(sd, strict=True)
|
||||
model.to(device=self.load_device, dtype=self.dtype)
|
||||
model.eval()
|
||||
|
||||
self.model = model
|
||||
|
||||
@@ -107,6 +107,25 @@ def get_local_filepath(url, dirname, local_file_name=None):
|
||||
download_url_to_file(url, destination)
|
||||
return destination
|
||||
|
||||
def to_lora_patch_dict(state_dict: dict) -> dict:
|
||||
""" Convert raw lora state_dict to patch_dict that can be applied on
|
||||
modelpatcher."""
|
||||
patch_dict = {}
|
||||
for k, w in state_dict.items():
|
||||
model_key, patch_type, weight_index = k.split('::')
|
||||
if model_key not in patch_dict:
|
||||
patch_dict[model_key] = {}
|
||||
if patch_type not in patch_dict[model_key]:
|
||||
patch_dict[model_key][patch_type] = [None] * 16
|
||||
patch_dict[model_key][patch_type][int(weight_index)] = w
|
||||
|
||||
patch_flat = {}
|
||||
for model_key, v in patch_dict.items():
|
||||
for patch_type, weight_list in v.items():
|
||||
patch_flat[model_key] = (patch_type, weight_list)
|
||||
|
||||
return patch_flat
|
||||
|
||||
def easySave(images, filename_prefix, output_type, prompt=None, extra_pnginfo=None):
|
||||
"""Save or Preview Image"""
|
||||
from nodes import PreviewImage, SaveImage
|
||||
|
||||
+1
-1
@@ -127,7 +127,7 @@ def prompt_seed_update(json_data):
|
||||
if 'class_type' not in v:
|
||||
continue
|
||||
cls = v['class_type']
|
||||
if cls in ["easy wildcards","easy preSampling","easy preSamplingAdvanced","easy preSamplingSdTurbo","easy preSamplingDynamicCFG","easy preSamplingCascade","easy fullCascadeKSampler","easy fullkSampler","easy seed","easy latentNoisy"]:
|
||||
if cls in ["easy wildcards","easy preSampling","easy preSamplingAdvanced","easy preSamplingSdTurbo","easy preSamplingDynamicCFG","easy preSamplingLayerDiffusion","easy preSamplingCascade","easy fullCascadeKSampler","easy fullkSampler","easy seed","easy latentNoisy"]:
|
||||
extra_data = next((x for x in workflow["nodes"] if str(x["id"]) == k), None)
|
||||
if extra_data is not None:
|
||||
inputs = extra_data.get('inputs')
|
||||
|
||||
+1
-1
@@ -282,7 +282,7 @@ 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
|
||||
|
||||
if can_load_lora:
|
||||
if can_load_lora and has_loras:
|
||||
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"
|
||||
|
||||
@@ -3,242 +3,11 @@ import { ComfyWidgets } from "/scripts/widgets.js";
|
||||
import { $el } from "/scripts/ui.js";
|
||||
import { api } from "/scripts/api.js";
|
||||
|
||||
const BETTER_COMBOS_NODES = ["easy a1111Loader"]
|
||||
const CONVERTED_TYPE = "converted-widget";
|
||||
const GET_CONFIG = Symbol();
|
||||
|
||||
function hideWidget(node, widget, suffix = "") {
|
||||
widget.origType = widget.type;
|
||||
widget.origComputeSize = widget.computeSize;
|
||||
widget.origSerializeValue = widget.serializeValue;
|
||||
widget.computeSize = () => [0, -4]; // -4 is due to the gap litegraph adds between widgets automatically
|
||||
widget.type = CONVERTED_TYPE + suffix;
|
||||
widget.serializeValue = () => {
|
||||
// Prevent serializing the widget if we have no input linked
|
||||
if (!node.inputs) {
|
||||
return undefined;
|
||||
}
|
||||
let node_input = node.inputs.find((i) => i.widget?.name === widget.name);
|
||||
|
||||
if (!node_input || !node_input.link) {
|
||||
return undefined;
|
||||
}
|
||||
return widget.origSerializeValue ? widget.origSerializeValue() : widget.value;
|
||||
};
|
||||
|
||||
// Hide any linked widgets, e.g. seed+seedControl
|
||||
if (widget.linkedWidgets) {
|
||||
for (const w of widget.linkedWidgets) {
|
||||
hideWidget(node, w, ":" + widget.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
function deepEqual (obj1, obj2) {
|
||||
if (typeof obj1 !== typeof obj2) {
|
||||
return false
|
||||
}
|
||||
if (typeof obj1 !== 'object' || obj1 === null || obj2 === null) {
|
||||
return obj1 === obj2
|
||||
}
|
||||
const keys1 = Object.keys(obj1)
|
||||
const keys2 = Object.keys(obj2)
|
||||
if (keys1.length !== keys2.length) {
|
||||
return false
|
||||
}
|
||||
for (let key of keys1) {
|
||||
if (!deepEqual(obj1[key], obj2[key])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
function convertToInput(node, widget, config) {
|
||||
console.log('config:', config)
|
||||
hideWidget(node, widget);
|
||||
|
||||
const { type } = getWidgetType(config);
|
||||
|
||||
// Add input and store widget config for creating on primitive node
|
||||
const sz = node.size;
|
||||
node.addInput(widget.name, type, {
|
||||
widget: { name: widget.name, [GET_CONFIG]: () => config },
|
||||
});
|
||||
|
||||
for (const widget of node.widgets) {
|
||||
widget.last_y += LiteGraph.NODE_SLOT_HEIGHT;
|
||||
}
|
||||
|
||||
// Restore original size but grow if needed
|
||||
node.setSize([Math.max(sz[0], node.size[0]), Math.max(sz[1], node.size[1])]);
|
||||
}
|
||||
|
||||
function getWidgetType(config) {
|
||||
// Special handling for COMBO so we restrict links based on the entries
|
||||
let type = config[0];
|
||||
if (type instanceof Array) {
|
||||
type = "COMBO";
|
||||
}
|
||||
return { type };
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "comfy.easyUse",
|
||||
init() {
|
||||
// 刷新节点
|
||||
const easyReloadNode = function (node) {
|
||||
const nodeType = node.constructor.type;
|
||||
const origVals = node.properties.origVals || {};
|
||||
|
||||
const nodeTitle = origVals.title || node.title;
|
||||
const nodeColor = origVals.color || node.color;
|
||||
const bgColor = origVals.bgcolor || node.bgcolor;
|
||||
const oldNode = node
|
||||
const options = {
|
||||
'size': [...node.size],
|
||||
'color': nodeColor,
|
||||
'bgcolor': bgColor,
|
||||
'pos': [...node.pos]
|
||||
}
|
||||
|
||||
let inputLinks = []
|
||||
let outputLinks = []
|
||||
if(node.inputs){
|
||||
for (const input of node.inputs) {
|
||||
if (input.link) {
|
||||
const input_name = input.name
|
||||
const input_slot = node.findInputSlot(input_name)
|
||||
const input_node = node.getInputNode(input_slot)
|
||||
const input_link = node.getInputLink(input_slot)
|
||||
|
||||
inputLinks.push([input_link.origin_slot, input_node, input_name])
|
||||
}
|
||||
}
|
||||
}
|
||||
if(node.outputs) {
|
||||
for (const output of node.outputs) {
|
||||
if (output.links) {
|
||||
const output_name = output.name
|
||||
|
||||
for (const linkID of output.links) {
|
||||
const output_link = graph.links[linkID]
|
||||
const output_node = graph._nodes_by_id[output_link.target_id]
|
||||
outputLinks.push([output_name, output_node, output_link.target_slot])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.graph.remove(node)
|
||||
const newNode = app.graph.add(LiteGraph.createNode(nodeType, nodeTitle, options));
|
||||
|
||||
function handleLinks() {
|
||||
// re-convert inputs
|
||||
for (let w of oldNode.widgets) {
|
||||
if (w.type === 'converted-widget') {
|
||||
const WidgetToConvert = newNode.widgets.find((nw) => nw.name === w.name);
|
||||
for (let i of oldNode.inputs) {
|
||||
if (i.name === w.name) {
|
||||
convertToInput(newNode, WidgetToConvert, i.widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// replace input and output links
|
||||
for (let input of inputLinks) {
|
||||
const [output_slot, output_node, input_name] = input;
|
||||
output_node.connect(output_slot, newNode.id, input_name)
|
||||
}
|
||||
for (let output of outputLinks) {
|
||||
const [output_name, input_node, input_slot] = output;
|
||||
newNode.connect(output_name, input_node, input_slot)
|
||||
}
|
||||
}
|
||||
|
||||
// fix widget values
|
||||
let values = oldNode.widgets_values;
|
||||
if (!values) {
|
||||
newNode.widgets.forEach((newWidget, index) => {
|
||||
const oldWidget = oldNode.widgets[index];
|
||||
if (newWidget.name === oldWidget.name && newWidget.type === oldWidget.type) {
|
||||
newWidget.value = oldWidget.value;
|
||||
}
|
||||
});
|
||||
handleLinks();
|
||||
return;
|
||||
}
|
||||
let pass = false
|
||||
const isIterateForwards = values.length <= newNode.widgets.length;
|
||||
let vi = isIterateForwards ? 0 : values.length - 1;
|
||||
function evalWidgetValues(testValue, newWidg) {
|
||||
if (testValue === true || testValue === false) {
|
||||
if (newWidg.options?.on && newWidg.options?.off) {
|
||||
return { value: testValue, pass: true };
|
||||
}
|
||||
} else if (typeof testValue === "number") {
|
||||
if (newWidg.options?.min <= testValue && testValue <= newWidg.options?.max) {
|
||||
return { value: testValue, pass: true };
|
||||
}
|
||||
} else if (newWidg.options?.values?.includes(testValue)) {
|
||||
return { value: testValue, pass: true };
|
||||
} else if (newWidg.inputEl && typeof testValue === "string") {
|
||||
return { value: testValue, pass: true };
|
||||
}
|
||||
return { value: newWidg.value, pass: false };
|
||||
}
|
||||
const updateValue = (wi) => {
|
||||
const oldWidget = oldNode.widgets[wi];
|
||||
let newWidget = newNode.widgets[wi];
|
||||
if (newWidget.name === oldWidget.name && newWidget.type === oldWidget.type) {
|
||||
while ((isIterateForwards ? vi < values.length : vi >= 0) && !pass) {
|
||||
let { value, pass } = evalWidgetValues(values[vi], newWidget);
|
||||
if (pass && value !== null) {
|
||||
newWidget.value = value;
|
||||
break;
|
||||
}
|
||||
vi += isIterateForwards ? 1 : -1;
|
||||
}
|
||||
vi++
|
||||
if (!isIterateForwards) {
|
||||
vi = values.length - (newNode.widgets.length - 1 - wi);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (isIterateForwards) {
|
||||
for (let wi = 0; wi < newNode.widgets.length; wi++) {
|
||||
updateValue(wi);
|
||||
}
|
||||
} else {
|
||||
for (let wi = newNode.widgets.length - 1; wi >= 0; wi--) {
|
||||
updateValue(wi);
|
||||
}
|
||||
}
|
||||
handleLinks();
|
||||
};
|
||||
|
||||
// Nodes Menu
|
||||
const getNodeMenuOptions = LGraphCanvas.prototype.getNodeMenuOptions;
|
||||
LGraphCanvas.prototype.getNodeMenuOptions = function (node) {
|
||||
const options = getNodeMenuOptions.apply(this, arguments);
|
||||
node.setDirtyCanvas(true, true);
|
||||
|
||||
options.splice(options.length - 1, 0,
|
||||
{
|
||||
content: "🔃Reload Node (EasyUse)",
|
||||
callback: () => {
|
||||
var graphcanvas = LGraphCanvas.active_canvas;
|
||||
if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1) {
|
||||
easyReloadNode(node);
|
||||
} else {
|
||||
for (var i in graphcanvas.selected_nodes) {
|
||||
easyReloadNode(graphcanvas.selected_nodes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
return options;
|
||||
};
|
||||
|
||||
// Canvas Menu
|
||||
const getCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions;
|
||||
LGraphCanvas.prototype.getCanvasMenuOptions = function () {
|
||||
|
||||
@@ -19,7 +19,7 @@ function toggleWidget(node, widget, show = false, suffix = "") {
|
||||
}
|
||||
const origSize = node.size;
|
||||
|
||||
widget.type = show ? origProps[widget.name].origType : "esayHidden" + suffix;
|
||||
widget.type = show ? origProps[widget.name].origType : "easyHidden" + suffix;
|
||||
widget.computeSize = show ? origProps[widget.name].origComputeSize : () => [0, -4];
|
||||
|
||||
widget.linkedWidgets?.forEach(w => toggleWidget(node, w, ":" + widget.name, show));
|
||||
@@ -438,10 +438,12 @@ app.registerExtension({
|
||||
case "easy preSamplingAdvanced":
|
||||
case "easy preSamplingSdTurbo":
|
||||
case "easy preSamplingCascade":
|
||||
case "easy preSamplingLayerDiffusion":
|
||||
case "easy fullkSampler":
|
||||
case "easy kSampler":
|
||||
case "easy kSamplerSDTurbo":
|
||||
case "easy kSamplerTiled":
|
||||
case "easy kSamplerLayerDiffusion":
|
||||
case "easy kSamplerInpainting":
|
||||
case "easy kSamplerDownscaleUnet":
|
||||
case "easy fullCascadeKSampler":
|
||||
@@ -753,7 +755,7 @@ app.registerExtension({
|
||||
};
|
||||
}
|
||||
|
||||
if (["easy seed", "easy latentNoisy", "easy wildcards", "easy preSampling", "easy preSamplingAdvanced", "easy preSamplingSdTurbo", "easy preSamplingCascade", "easy preSamplingDynamicCFG", "easy fullkSampler", "easy fullCascadeKSampler"].includes(nodeData.name)) {
|
||||
if (["easy seed", "easy latentNoisy", "easy wildcards", "easy preSampling", "easy preSamplingAdvanced", "easy preSamplingSdTurbo", "easy preSamplingCascade", "easy preSamplingDynamicCFG", "easy preSamplingLayerDiffusion", "easy fullkSampler", "easy fullCascadeKSampler"].includes(nodeData.name)) {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = async function () {
|
||||
onNodeCreated ? onNodeCreated.apply(this, []) : undefined;
|
||||
|
||||
@@ -0,0 +1,508 @@
|
||||
import {app} from "/scripts/app.js";
|
||||
|
||||
const loaders = ['easy fullLoader', 'easy a1111Loader', 'easy comfyLoader']
|
||||
const preSampling = ['easy preSampling', 'easy preSamplingAdvanced', 'easy preSamplingDynamicCFG', 'easy preSamplingLayerDiffusion', 'easy fullkSampler']
|
||||
const kSampler = ['easy kSampler', 'easy kSamplerTiled', 'easy kSamplerInpainting', 'easy kSamplerDownscaleUnet', 'easy kSamplerLayerDiffusion']
|
||||
const controlnet = ['easy controlnetLoader', 'easy controlnetLoaderADV', 'easy instantIDApply', 'easy instantIDApplyADV']
|
||||
const positive_prompt = ['easy positive', 'easy wildcards']
|
||||
const widgetMapping = {
|
||||
"positive_prompt":{
|
||||
"text": "positive",
|
||||
"positive": "text"
|
||||
},
|
||||
"loaders":{
|
||||
"ckpt_name": "ckpt_name",
|
||||
"vae_name": "vae_name",
|
||||
"clip_skip": "clip_skip",
|
||||
"lora_name": "lora_name",
|
||||
"resolution": "resolution",
|
||||
"empty_latent_width": "empty_latent_width",
|
||||
"empty_latent_height": "empty_latent_height",
|
||||
"positive": "positive",
|
||||
"negative": "negative",
|
||||
"batch_size": "batch_size",
|
||||
"a1111_prompt_style": "a1111_prompt_style"
|
||||
},
|
||||
"preSampling":{
|
||||
"steps": "steps",
|
||||
"cfg": "cfg",
|
||||
"sampler_name": "sampler_name",
|
||||
"scheduler": "scheduler",
|
||||
"denoise": "denoise",
|
||||
"seed_num": "seed_num"
|
||||
},
|
||||
"kSampler":{
|
||||
"image_output": "image_output",
|
||||
"save_prefix": "save_prefix",
|
||||
"link_id": "link_id"
|
||||
},
|
||||
"controlnet":{
|
||||
"control_net_name":"control_net_name",
|
||||
"strength": ["strength", "cn_strength"],
|
||||
"scale_soft_weights": ["scale_soft_weights","cn_soft_weights"],
|
||||
"cn_strength": ["strength", "cn_strength"],
|
||||
"cn_soft_weights": ["scale_soft_weights","cn_soft_weights"],
|
||||
}
|
||||
}
|
||||
const inputMapping = {
|
||||
"loaders":{
|
||||
"optional_lora_stack": "optional_lora_stack",
|
||||
"positive": "positive",
|
||||
"negative": "negative"
|
||||
},
|
||||
"preSampling":{
|
||||
"pipe": "pipe",
|
||||
"image_to_latent": "image_to_latent",
|
||||
"latent": "latent"
|
||||
},
|
||||
"kSampler":{
|
||||
"pipe": "pipe",
|
||||
"model": "model"
|
||||
},
|
||||
"controlnet":{
|
||||
"pipe": "pipe",
|
||||
"image": "image",
|
||||
"image_kps": "image_kps",
|
||||
"control_net": "control_net",
|
||||
"positive": "positive",
|
||||
"negative": "negative",
|
||||
"mask": "mask"
|
||||
},
|
||||
"positive_prompt":{
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const outputMapping = {
|
||||
"loaders":{
|
||||
"pipe": "pipe",
|
||||
"model": "model",
|
||||
"vae": "vae",
|
||||
"clip": null,
|
||||
"positive": null,
|
||||
"negative": null,
|
||||
"latent": null,
|
||||
},
|
||||
"preSampling":{
|
||||
"pipe":"pipe"
|
||||
},
|
||||
"kSampler":{
|
||||
"pipe": "pipe",
|
||||
"image": "image"
|
||||
},
|
||||
"controlnet":{
|
||||
"pipe": "pipe",
|
||||
"positive": "positive",
|
||||
"negative": "negative"
|
||||
},
|
||||
"positive_prompt":{
|
||||
"text": "positive",
|
||||
"positive": "text"
|
||||
},
|
||||
};
|
||||
|
||||
// 替换节点
|
||||
function replaceNode(oldNode, newNodeName, type) {
|
||||
const newNode = LiteGraph.createNode(newNodeName);
|
||||
if (!newNode) {
|
||||
return;
|
||||
}
|
||||
app.graph.add(newNode);
|
||||
|
||||
newNode.pos = oldNode.pos.slice();
|
||||
newNode.size = oldNode.size.slice();
|
||||
|
||||
oldNode.widgets.forEach(widget => {
|
||||
if(widgetMapping[type][widget.name]){
|
||||
const newName = widgetMapping[type][widget.name];
|
||||
if (newName) {
|
||||
const newWidget = findWidgetByName(newNode, newName);
|
||||
if (newWidget) {
|
||||
newWidget.value = widget.value;
|
||||
if(widget.name == 'seed_num'){
|
||||
newWidget.linkedWidgets[0].value = widget.linkedWidgets[0].value
|
||||
}
|
||||
if(widget.type == 'converted-widget'){
|
||||
convertToInput(newNode, newWidget, widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if(oldNode.inputs){
|
||||
oldNode.inputs.forEach((input, index) => {
|
||||
if (input && input.link && inputMapping[type][input.name]) {
|
||||
const newInputName = inputMapping[type][input.name];
|
||||
// If the new node does not have this output, skip
|
||||
if (newInputName === null) {
|
||||
return;
|
||||
}
|
||||
const newInputIndex = newNode.findInputSlot(newInputName);
|
||||
if (newInputIndex !== -1) {
|
||||
const originLinkInfo = oldNode.graph.links[input.link];
|
||||
if (originLinkInfo) {
|
||||
const originNode = oldNode.graph.getNodeById(originLinkInfo.origin_id);
|
||||
if (originNode) {
|
||||
originNode.connect(originLinkInfo.origin_slot, newNode, newInputIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(oldNode.outputs){
|
||||
oldNode.outputs.forEach((output, index) => {
|
||||
if (output && output.links && outputMapping[type] && outputMapping[type][output.name]) {
|
||||
const newOutputName = outputMapping[type][output.name];
|
||||
// If the new node does not have this output, skip
|
||||
if (newOutputName === null) {
|
||||
return;
|
||||
}
|
||||
const newOutputIndex = newNode.findOutputSlot(newOutputName);
|
||||
if (newOutputIndex !== -1) {
|
||||
output.links.forEach(link => {
|
||||
const targetLinkInfo = oldNode.graph.links[link];
|
||||
if (targetLinkInfo) {
|
||||
const targetNode = oldNode.graph.getNodeById(targetLinkInfo.target_id);
|
||||
if (targetNode) {
|
||||
newNode.connect(newOutputIndex, targetNode, targetLinkInfo.target_slot);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Remove old node
|
||||
app.graph.remove(oldNode);
|
||||
|
||||
// Remove others
|
||||
if(newNode.type == 'easy fullkSampler'){
|
||||
const link_output_id = newNode.outputs[0].links
|
||||
if(link_output_id && link_output_id[0]){
|
||||
const nodes = app.graph._nodes
|
||||
const node = nodes.find(cate=> cate.inputs && cate.inputs[0] && cate.inputs[0]['link'] == link_output_id[0])
|
||||
if(node){
|
||||
app.graph.remove(node);
|
||||
}
|
||||
}
|
||||
}else if(preSampling.includes(newNode.type)){
|
||||
const link_output_id = newNode.outputs[0].links
|
||||
if(!link_output_id || !link_output_id[0]){
|
||||
const ksampler = LiteGraph.createNode('easy kSampler');
|
||||
app.graph.add(ksampler);
|
||||
ksampler.pos = newNode.pos.slice();
|
||||
ksampler.pos[0] = ksampler.pos[0] + newNode.size[0] + 20;
|
||||
const newInputIndex = newNode.findInputSlot('pipe');
|
||||
if (newInputIndex !== -1) {
|
||||
if (newNode) {
|
||||
newNode.connect(0, ksampler, newInputIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// autoHeight
|
||||
newNode.setSize([newNode.size[0], newNode.computeSize()[1]]);
|
||||
}
|
||||
|
||||
export function findWidgetByName(node, widgetName) {
|
||||
return node.widgets.find(widget => typeof widgetName == 'object' ? widgetName.includes(widget.name) : widget.name === widgetName);
|
||||
}
|
||||
function replaceNodeMenuCallback(currentNode, targetNodeName, type) {
|
||||
return function() {
|
||||
replaceNode(currentNode, targetNodeName, type);
|
||||
};
|
||||
}
|
||||
const addMenuHandler = (nodeType, cb)=> {
|
||||
const getOpts = nodeType.prototype.getExtraMenuOptions;
|
||||
nodeType.prototype.getExtraMenuOptions = function () {
|
||||
const r = getOpts.apply(this, arguments);
|
||||
cb.apply(this, arguments);
|
||||
return r;
|
||||
};
|
||||
}
|
||||
const addMenu = (content, type, nodes_include, nodeType, has_submenu=true) => {
|
||||
addMenuHandler(nodeType, function (_, options) {
|
||||
options.unshift({
|
||||
content: content,
|
||||
has_submenu: has_submenu,
|
||||
callback: (value, options, e, menu, node) => showSwapMenu(value, options, e, menu, node, type, nodes_include)
|
||||
})
|
||||
})
|
||||
}
|
||||
const showSwapMenu = (value, options, e, menu, node, type, nodes_include) => {
|
||||
const swapOptions = [];
|
||||
nodes_include.map(cate=>{
|
||||
if (node.type !== cate) {
|
||||
swapOptions.push({
|
||||
content: `${cate}`,
|
||||
callback: replaceNodeMenuCallback(node, cate, type)
|
||||
});
|
||||
}
|
||||
})
|
||||
new LiteGraph.ContextMenu(swapOptions, {
|
||||
event: e,
|
||||
callback: null,
|
||||
parentMenu: menu,
|
||||
node: node
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// 重载节点
|
||||
const CONVERTED_TYPE = "converted-widget";
|
||||
const GET_CONFIG = Symbol();
|
||||
|
||||
function hideWidget(node, widget, suffix = "") {
|
||||
widget.origType = widget.type;
|
||||
widget.origComputeSize = widget.computeSize;
|
||||
widget.origSerializeValue = widget.serializeValue;
|
||||
widget.computeSize = () => [0, -4]; // -4 is due to the gap litegraph adds between widgets automatically
|
||||
widget.type = CONVERTED_TYPE + suffix;
|
||||
widget.serializeValue = () => {
|
||||
// Prevent serializing the widget if we have no input linked
|
||||
if (!node.inputs) {
|
||||
return undefined;
|
||||
}
|
||||
let node_input = node.inputs.find((i) => i.widget?.name === widget.name);
|
||||
|
||||
if (!node_input || !node_input.link) {
|
||||
return undefined;
|
||||
}
|
||||
return widget.origSerializeValue ? widget.origSerializeValue() : widget.value;
|
||||
};
|
||||
|
||||
// Hide any linked widgets, e.g. seed+seedControl
|
||||
if (widget.linkedWidgets) {
|
||||
for (const w of widget.linkedWidgets) {
|
||||
hideWidget(node, w, ":" + widget.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
function deepEqual (obj1, obj2) {
|
||||
if (typeof obj1 !== typeof obj2) {
|
||||
return false
|
||||
}
|
||||
if (typeof obj1 !== 'object' || obj1 === null || obj2 === null) {
|
||||
return obj1 === obj2
|
||||
}
|
||||
const keys1 = Object.keys(obj1)
|
||||
const keys2 = Object.keys(obj2)
|
||||
if (keys1.length !== keys2.length) {
|
||||
return false
|
||||
}
|
||||
for (let key of keys1) {
|
||||
if (!deepEqual(obj1[key], obj2[key])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
function convertToInput(node, widget, config) {
|
||||
console.log('config:', config)
|
||||
hideWidget(node, widget);
|
||||
|
||||
const { type } = getWidgetType(config);
|
||||
|
||||
// Add input and store widget config for creating on primitive node
|
||||
const sz = node.size;
|
||||
node.addInput(widget.name, type, {
|
||||
widget: { name: widget.name, [GET_CONFIG]: () => config },
|
||||
});
|
||||
|
||||
for (const widget of node.widgets) {
|
||||
widget.last_y += LiteGraph.NODE_SLOT_HEIGHT;
|
||||
}
|
||||
|
||||
// Restore original size but grow if needed
|
||||
node.setSize([Math.max(sz[0], node.size[0]), Math.max(sz[1], node.size[1])]);
|
||||
}
|
||||
|
||||
function getWidgetType(config) {
|
||||
// Special handling for COMBO so we restrict links based on the entries
|
||||
let type = config[0];
|
||||
if (type instanceof Array) {
|
||||
type = "COMBO";
|
||||
}
|
||||
return { type };
|
||||
}
|
||||
|
||||
const reloadNode = function (node) {
|
||||
const nodeType = node.constructor.type;
|
||||
const origVals = node.properties.origVals || {};
|
||||
|
||||
const nodeTitle = origVals.title || node.title;
|
||||
const nodeColor = origVals.color || node.color;
|
||||
const bgColor = origVals.bgcolor || node.bgcolor;
|
||||
const oldNode = node
|
||||
const options = {
|
||||
'size': [...node.size],
|
||||
'color': nodeColor,
|
||||
'bgcolor': bgColor,
|
||||
'pos': [...node.pos]
|
||||
}
|
||||
|
||||
let inputLinks = []
|
||||
let outputLinks = []
|
||||
if(node.inputs){
|
||||
for (const input of node.inputs) {
|
||||
if (input.link) {
|
||||
const input_name = input.name
|
||||
const input_slot = node.findInputSlot(input_name)
|
||||
const input_node = node.getInputNode(input_slot)
|
||||
const input_link = node.getInputLink(input_slot)
|
||||
|
||||
inputLinks.push([input_link.origin_slot, input_node, input_name])
|
||||
}
|
||||
}
|
||||
}
|
||||
if(node.outputs) {
|
||||
for (const output of node.outputs) {
|
||||
if (output.links) {
|
||||
const output_name = output.name
|
||||
|
||||
for (const linkID of output.links) {
|
||||
const output_link = graph.links[linkID]
|
||||
const output_node = graph._nodes_by_id[output_link.target_id]
|
||||
outputLinks.push([output_name, output_node, output_link.target_slot])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.graph.remove(node)
|
||||
const newNode = app.graph.add(LiteGraph.createNode(nodeType, nodeTitle, options));
|
||||
|
||||
function handleLinks() {
|
||||
// re-convert inputs
|
||||
for (let w of oldNode.widgets) {
|
||||
if (w.type === 'converted-widget') {
|
||||
const WidgetToConvert = newNode.widgets.find((nw) => nw.name === w.name);
|
||||
for (let i of oldNode.inputs) {
|
||||
if (i.name === w.name) {
|
||||
convertToInput(newNode, WidgetToConvert, i.widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// replace input and output links
|
||||
for (let input of inputLinks) {
|
||||
const [output_slot, output_node, input_name] = input;
|
||||
output_node.connect(output_slot, newNode.id, input_name)
|
||||
}
|
||||
for (let output of outputLinks) {
|
||||
const [output_name, input_node, input_slot] = output;
|
||||
newNode.connect(output_name, input_node, input_slot)
|
||||
}
|
||||
}
|
||||
|
||||
// fix widget values
|
||||
let values = oldNode.widgets_values;
|
||||
if (!values) {
|
||||
newNode.widgets.forEach((newWidget, index) => {
|
||||
const oldWidget = oldNode.widgets[index];
|
||||
if (newWidget.name === oldWidget.name && newWidget.type === oldWidget.type) {
|
||||
newWidget.value = oldWidget.value;
|
||||
}
|
||||
});
|
||||
handleLinks();
|
||||
return;
|
||||
}
|
||||
let pass = false
|
||||
const isIterateForwards = values.length <= newNode.widgets.length;
|
||||
let vi = isIterateForwards ? 0 : values.length - 1;
|
||||
function evalWidgetValues(testValue, newWidg) {
|
||||
if (testValue === true || testValue === false) {
|
||||
if (newWidg.options?.on && newWidg.options?.off) {
|
||||
return { value: testValue, pass: true };
|
||||
}
|
||||
} else if (typeof testValue === "number") {
|
||||
if (newWidg.options?.min <= testValue && testValue <= newWidg.options?.max) {
|
||||
return { value: testValue, pass: true };
|
||||
}
|
||||
} else if (newWidg.options?.values?.includes(testValue)) {
|
||||
return { value: testValue, pass: true };
|
||||
} else if (newWidg.inputEl && typeof testValue === "string") {
|
||||
return { value: testValue, pass: true };
|
||||
}
|
||||
return { value: newWidg.value, pass: false };
|
||||
}
|
||||
const updateValue = (wi) => {
|
||||
const oldWidget = oldNode.widgets[wi];
|
||||
let newWidget = newNode.widgets[wi];
|
||||
if (newWidget.name === oldWidget.name && newWidget.type === oldWidget.type) {
|
||||
while ((isIterateForwards ? vi < values.length : vi >= 0) && !pass) {
|
||||
let { value, pass } = evalWidgetValues(values[vi], newWidget);
|
||||
if (pass && value !== null) {
|
||||
newWidget.value = value;
|
||||
break;
|
||||
}
|
||||
vi += isIterateForwards ? 1 : -1;
|
||||
}
|
||||
vi++
|
||||
if (!isIterateForwards) {
|
||||
vi = values.length - (newNode.widgets.length - 1 - wi);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (isIterateForwards) {
|
||||
for (let wi = 0; wi < newNode.widgets.length; wi++) {
|
||||
updateValue(wi);
|
||||
}
|
||||
} else {
|
||||
for (let wi = newNode.widgets.length - 1; wi >= 0; wi--) {
|
||||
updateValue(wi);
|
||||
}
|
||||
}
|
||||
handleLinks();
|
||||
};
|
||||
|
||||
|
||||
app.registerExtension({
|
||||
name: "comfy.easyUse.extraMenu",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
// 刷新节点
|
||||
addMenuHandler(nodeType, function (_, options) {
|
||||
options.unshift({
|
||||
content: "🔃 Reload Node",
|
||||
callback: (value, options, e, menu, node) => {
|
||||
let graphcanvas = LGraphCanvas.active_canvas;
|
||||
if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1) {
|
||||
reloadNode(node);
|
||||
} else {
|
||||
for (let i in graphcanvas.selected_nodes) {
|
||||
reloadNode(graphcanvas.selected_nodes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
// Swap提示词
|
||||
if (positive_prompt.includes(nodeData.name)) {
|
||||
addMenu("↪️ Swap EasyPrompt", 'positive_prompt', positive_prompt, nodeType)
|
||||
}
|
||||
// Swap加载器
|
||||
if (loaders.includes(nodeData.name)) {
|
||||
addMenu("↪️ Swap EasyLoader", 'loaders', loaders, nodeType)
|
||||
}
|
||||
// Swap预采样器
|
||||
if (preSampling.includes(nodeData.name)) {
|
||||
addMenu("↪️ Swap EasyPreSampling", 'preSampling', preSampling, nodeType)
|
||||
}
|
||||
// Swap kSampler
|
||||
if (kSampler.includes(nodeData.name)) {
|
||||
addMenu("↪️ Swap EasyKSampler", 'preSampling', kSampler, nodeType)
|
||||
}
|
||||
// Swap ControlNet
|
||||
if (controlnet.includes(nodeData.name)) {
|
||||
addMenu("↪️ Swap EasyControlnet", 'controlnet', controlnet, nodeType)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -125,7 +125,6 @@ try{
|
||||
app.ui.settings.load()
|
||||
}
|
||||
}
|
||||
console.log(theme_name)
|
||||
// 判断主题为黑曜石时改变扩展UI
|
||||
if(['"custom_obsidian"','"custom_obsidian_dark"'].includes(theme_name)){
|
||||
// canvas
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { app } from "/scripts/app.js";
|
||||
import { applyTextReplacements } from "/scripts/utils.js";
|
||||
|
||||
const extraNodes = ["easy imageSave", "easy fullkSampler", "easy kSampler", "easy kSamplerTiled","easy kSamplerInpainting", "easy kSamplerDownscaleUnet", "easy kSamplerSDTurbo","easy detailerFix"]
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.Easy.SaveImageExtraOutput",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (["easy imageSave", "easy fullkSampler", "easy kSampler", "easy kSamplerTiled","easy kSamplerInpainting", "easy kSamplerDownscaleUnet", "easy kSamplerSDTurbo","easy detailerFix"].includes(nodeData.name)) {
|
||||
if (extraNodes.includes(nodeData.name)) {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
// When the SaveImage node is created we want to override the serialization of the output name widget to run our S&R
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
|
||||
Reference in New Issue
Block a user