Upgrade to v1.1.0

This commit is contained in:
yolain
2024-03-04 00:53:29 +08:00
parent ff1add1557
commit 65c54649a0
14 changed files with 1129 additions and 266 deletions
+8 -3
View File
@@ -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
+9 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)",
}
+322
View File
@@ -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
+19
View File
@@ -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
View File
@@ -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
View File
@@ -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"
-231
View File
@@ -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 () {
+4 -2
View File
@@ -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;
+508
View File
@@ -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)
}
}
});
-1
View File
@@ -125,7 +125,6 @@ try{
app.ui.settings.load()
}
}
console.log(theme_name)
// 判断主题为黑曜石时改变扩展UI
if(['"custom_obsidian"','"custom_obsidian_dark"'].includes(theme_name)){
// canvas
+3 -1
View File
@@ -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 () {