Compare commits

...
30 Commits
Author SHA1 Message Date
yolain 7cbc2a2a2b modify the pyproject.toml file 2024-05-23 13:33:03 +08:00
yolain 63811206c9 Merge pull request #182 from haohaocreates/publish
Add Github Action for Publishing to Comfy Registry
2024-05-23 13:30:16 +08:00
yolain 48a4b5bfc6 Merge pull request #183 from haohaocreates/pyproject
Add pyproject.toml for Custom Node Registry
2024-05-23 13:30:00 +08:00
yolain 7980f4eef4 fix:compatible with new brushnet versions #181 2024-05-23 12:33:45 +08:00
haohaocreates b19c8b07ea chore(pyproject): Add pyproject.toml for Custom Node Registry 2024-05-22 18:08:19 -04:00
haohaocreates 3f506d265c chore(publish): Add Github Action for Publishing to Comfy Registry 2024-05-22 18:08:16 -04:00
yolain d8af918e7d fix:the modal for selecting an image can not be closed #180 2024-05-21 15:54:02 +08:00
yolain 33566e8474 fix:run error when input optional image in easy fullkSampler #178 2024-05-21 15:41:39 +08:00
yolain ea0350c2dc fix:preview image offset in easy stylesSelector when zooming the page #176 2024-05-18 15:35:28 +08:00
yolain 24526623fb fix:iclight cache bug #173 2024-05-17 17:00:39 +08:00
yolain 7715ebfd06 Merge pull request #170 from chenpx976/main
feat: optimize GPU memory management and model unloading
2024-05-16 12:52:09 +08:00
color 2ba814c131 feat: optimize GPU memory management and model unloading 2024-05-16 12:35:40 +08:00
yolain d5ad332666 fix:icLightModel add to cache 2024-05-16 11:57:19 +08:00
yolain fee7e7bf73 fix:some models were not successfully written to easyCache,resulting in slow secondary diffusion 2024-05-16 01:01:03 +08:00
yolain 01f17ff02b remove:unnecessary import modules 2024-05-15 17:07:14 +08:00
yolain da7120219a fix:diffusers version>0.26.0 use different module #70 2024-05-15 10:50:19 +08:00
yolain d616d18069 fix:compatible with cg-image-picker #169 2024-05-15 10:04:00 +08:00
yolain dbf76f288c Update README.md 2024-05-14 16:49:16 +08:00
yolain 05124006ba fix:set_clip_options no longer compare version #165 2024-05-13 14:23:55 +08:00
yolain 4586af311c fix:fooocus+dd wrong 2024-05-13 14:19:33 +08:00
yolain 1cea58c7cf add:remove_bg in easy icLightApply 2024-05-11 02:13:55 +08:00
yolain a84f7c4a58 fix:brushnet error in easy presamplingInpainting 2024-05-11 00:42:14 +08:00
yolain 1d9bf86560 add:easy icLightApply 2024-05-10 17:26:04 +08:00
yolain 8d352b85bc add:easy imageSplitGrid 2024-05-09 16:08:03 +08:00
yolain 403562575c add:easy imageCropFromMask & imageUncropFromBBOX 2024-05-06 12:52:37 +08:00
yolain dbe2cd6569 fix:compatibility with comfyui-brushnet new commit 2024-05-05 11:30:41 +08:00
yolain b3b0a961c5 fix:raise excenption when comfyui-brushnet is not installed 2024-05-04 19:26:18 +08:00
yolain 9dfb8b9c15 fix:layerdiffuse everything config #158 2024-05-04 19:19:01 +08:00
yolain b4ea58946c fix:supported brushnet for ays 2024-05-04 01:01:35 +08:00
yolain 513fc4b67e support for brushnet model loading 2024-05-03 18:24:47 +08:00
29 changed files with 1385 additions and 308 deletions
+21
View File
@@ -0,0 +1,21 @@
name: Publish to Comfy registry
on:
workflow_dispatch:
push:
branches:
- main
paths:
- "pyproject.toml"
jobs:
publish-node:
name: Publish Custom Node to registry
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Publish Custom Node
uses: Comfy-Org/publish-node-action@main
with:
## Add your own personal access token to your Github Repository secrets and reference it here.
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
+10
View File
@@ -33,6 +33,16 @@
-
## Changelog
**v1.1.7**
- Added `easy prompt` - Subject and light presets, maybe adjusted later
- Added `easy icLightApply` - Light and shadow migration, Code based on [ComfyUI-IC-Light](https://github.com/huchenlei/ComfyUI-IC-Light)
- Added `easy imageSplitGrid`
- `easy kSamplerInpainting` added options such as different diffusion and brushnet in **additional** widget
- Support for brushnet model loading - [ComfyUI-BrushNet](https://github.com/nullquant/ComfyUI-BrushNet)
- Added `easy applyFooocusInpaint` - Replace FooocusInpaintLoader
- Removed `easy fooocusInpaintLoader`
**v1.1.6**
- Added **alignYourSteps** to **schedulder** widget in all `easy preSampling` and `easy fullkSampler`
+23 -39
View File
@@ -12,7 +12,7 @@
**ComfyUI-Easy-Use** 是一个化繁为简的节点整合包, 在 [tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) 的基础上进行延展,并针对了诸多主流的节点包做了整合与优化,以达到更快更方便使用ComfyUI的目的,在保证自由度的同时还原了本属于Stable Diffusion的极致畅快出图体验。
<img src="https://raw.githubusercontent.com/yolain/yolain-comfyui-workflow/main/Docs/workflow_node_compare.png">
[![ComfyUI-Yolain-Workflows](https://github.com/yolain/ComfyUI-Easy-Use/assets/73304135/9a3f54bc-a677-4bf1-a196-8845dd57c942)](https://github.com/yolain/ComfyUI-Yolain-Workflows)
## 特色介绍
@@ -23,10 +23,10 @@
- 可多选的风格化提示词选择器,默认是Fooocus的样式json,可自定义json放在styles底下,samples文件夹里可放预览图(名称和name一致,图片文件名如有空格需转为下划线'_')
- 加载器可开启A1111提示词风格模式,可重现与webui生成近乎相同的图像,需先安装 [ComfyUI_smZNodes](https://github.com/shiimizu/ComfyUI_smZNodes)
- 可使用`easy latentNoisy`或`easy preSamplingNoiseIn`节点实现对潜空间的噪声注入
- 简化 SD1.x、SD2.x、SDXL、SVD、Zero123等流程 [示例参考](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#StableDiffusion)
- 简化 Stable Cascade [示例参考](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#StableCascade)
- 简化 Layer Diffuse [示例参考](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#LayerDiffusion), 首次使用您可能需要运行 `pip install -r requirements.txt` 安装所需依赖
- 简化 InstantID [示例参考](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#InstantID), 需先保证自定义节点包中安装了 [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID)
- 简化 SD1.x、SD2.x、SDXL、SVD、Zero123等流程
- 简化 Stable Cascade [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#1-13-stable-cascade)
- 简化 Layer Diffuse [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#2-3-layerdiffusion)
- 简化 InstantID [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#2-2-instantid), 需先保证自定义节点包中安装了 [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID)
- 简化 IPAdapter, 需先保证自定义节点包中安装最新版v2的 [ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus)
- 扩展 XYplot 的可用性
- 整合了Fooocus Inpaint功能
@@ -35,9 +35,22 @@
- 支持BriaAI的RMBG-1.4模型的背景去除节点,[技术参考](https://huggingface.co/briaai/RMBG-1.4)
- 支持 强制清理comfyUI模型显存占用
- 支持Stable Diffusion 3 多账号API节点
- 支持IC-Light的应用 [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#2-5-ic-light) | [代码整合来源](https://github.com/huchenlei/ComfyUI-IC-Light) | [技术参考](https://github.com/lllyasviel/IC-Light)
## 更新日志
**v1.1.7**
- 修复 一些模型(如controlnet模型等)未成功写入缓存,导致修改前置节点束参数(如提示词)需要二次载入模型的问题
- 增加 `easy prompt` - 主体和光影预置项,后期可能会调整
- 增加 `easy icLightApply` - 重绘光影, 从[ComfyUI-IC-Light](https://github.com/huchenlei/ComfyUI-IC-Light)优化
- 增加 `easy imageSplitGrid` - 图像网格拆分
- `easy kSamplerInpainting` 的 **additional** 属性增加差异扩散和brushnet等相关选项
- 增加 brushnet模型加载的支持 - [ComfyUI-BrushNet](https://github.com/nullquant/ComfyUI-BrushNet)
- 增加 `easy applyFooocusInpaint` - Fooocus内补节点 替代原有的 FooocusInpaintLoader
- 移除 `easy fooocusInpaintLoader` - 容易bug,不再使用
- 修改 easy kSampler等采样器中并联的model 不再替换输出中pipe里的model
**v1.1.6**
- 增加步调齐整适配 - 在所有的预采样和全采样器节点中的 调度器(schedulder) 增加了 **alignYourSteps** 选项
@@ -124,7 +137,7 @@
- 修复未安装 ComfyUI-Impack-Pack 和 ComfyUI_InstantID 时报错
- 修复 `easy pipeIn` - pipe设为可不必选
- 增加 `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-Yolain-Workflows?tab=readme-ov-file#2-2-instantid)
- 修复 `easy detailerFix` 未添加到保存图片格式化扩展名可用节点列表
- 修复 `easy XYInputs: PromptSR` 在替换负面提示词时报错
</details>
@@ -328,39 +341,8 @@
| easy preSamplingLayerDiffusion | [ComfyUI-layerdiffusion](https://github.com/huchenlei/ComfyUI-layerdiffusion) | LayeredDiffusionApply等 |
| easy dynamiCrafterLoader | [ComfyUI-layerdiffusion](https://github.com/ExponentialML/ComfyUI_Native_DynamiCrafter) | Apply Dynamicrafter |
| easy imageChooser | [cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) | Preview Chooser |
| easy styleAlignedBatchAlign | [style_aligned_comfy](https://github.com/chrisgoringe/cg-image-picker) | styleAlignedBatchAlign |
## 示例
导入后请自行更换您目录里的大模型
### StableDiffusion
#### 文生图
<img src="https://raw.githubusercontent.com/yolain/yolain-comfyui-workflow/main/Workflows/Simple/text_to_image.png">
#### 图生图+controlnet
<img src="https://raw.githubusercontent.com/yolain/yolain-comfyui-workflow/main/Workflows/Simple/image_to_image_controlnet.png">
#### InstantID
<img src="https://raw.githubusercontent.com/yolain/yolain-comfyui-workflow/main/Workflows/Simple/instantID.png">
### LayerDiffusion
#### SD15
<img src="https://raw.githubusercontent.com/yolain/yolain-comfyui-workflow/main/Workflows/Simple/layer_diffusion_sd15.png">
#### SDXL
<img src="https://raw.githubusercontent.com/yolain/yolain-comfyui-workflow/main/Workflows/Simple/layer_diffusion_example.png">
### StableCascade
#### 文生图
<img src="https://raw.githubusercontent.com/yolain/yolain-comfyui-workflow/main/Workflows/StableCascade/text_to_image.png">
#### 图生图
<img src="https://raw.githubusercontent.com/yolain/yolain-comfyui-workflow/main/Workflows/StableCascade/image_to_image.png">
| easy styleAlignedBatchAlign | [style_aligned_comfy](https://github.com/chrisgoringe/cg-image-picker) | styleAlignedBatchAlign |
| easy icLightApply | [ComfyUI-IC-Light](https://github.com/huchenlei/ComfyUI-IC-Light) | ICLightApply等 |
## Credits
@@ -387,3 +369,5 @@
[ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) - pyssss 小蛇🐍脚本
[cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) - 图片选择器
[ComfyUI-BrushNet](https://github.com/nullquant/ComfyUI-BrushNet) - BrushNet 内补节点
+1 -1
View File
@@ -1,4 +1,4 @@
__version__ = "1.1.6"
__version__ = "1.1.7"
import os
import glob
+33
View File
@@ -0,0 +1,33 @@
import folder_paths
import os
def add_folder_path_and_extensions(folder_name, full_folder_paths, extensions):
for full_folder_path in full_folder_paths:
folder_paths.add_model_folder_path(folder_name, full_folder_path)
if folder_name in folder_paths.folder_names_and_paths:
current_paths, current_extensions = folder_paths.folder_names_and_paths[folder_name]
updated_extensions = current_extensions | extensions
folder_paths.folder_names_and_paths[folder_name] = (current_paths, updated_extensions)
else:
folder_paths.folder_names_and_paths[folder_name] = (full_folder_paths, extensions)
image_suffixs = set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".tiff", ".svg", ".ico", ".apng", ".tif", ".hdr", ".exr"])
model_path = folder_paths.models_dir
add_folder_path_and_extensions("ultralytics_bbox", [os.path.join(model_path, "ultralytics", "bbox")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("ultralytics_segm", [os.path.join(model_path, "ultralytics", "segm")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("ultralytics", [os.path.join(model_path, "ultralytics")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("mmdets_bbox", [os.path.join(model_path, "mmdets", "bbox")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("mmdets_segm", [os.path.join(model_path, "mmdets", "segm")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("mmdets", [os.path.join(model_path, "mmdets")], folder_paths.supported_pt_extensions)
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")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("layer_model", [os.path.join(model_path, "layer_model")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("rembg", [os.path.join(model_path, "rembg")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("ipadapter", [os.path.join(model_path, "ipadapter")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("dynamicrafter_models", [os.path.join(model_path, "dynamicrafter_models")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("mediapipe", [os.path.join(model_path, "mediapipe")], set(['.tflite','.pth']))
add_folder_path_and_extensions("inpaint", [os.path.join(model_path, "inpaint")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("checkpoints_thumb", [os.path.join(model_path, "checkpoints")], image_suffixs)
add_folder_path_and_extensions("loras_thumb", [os.path.join(model_path, "loras")], image_suffixs)
+8 -8
View File
@@ -237,17 +237,17 @@ def encode_token_weights_g(model, token_weight_pairs):
def encode_token_weights_l(model, token_weight_pairs):
l_out, _ = model.clip_l.encode_token_weights(token_weight_pairs)
return l_out, None
l_out, pooled = model.clip_l.encode_token_weights(token_weight_pairs)
return l_out, pooled
def encode_token_weights(model, token_weight_pairs, encode_func):
if model.layer_idx is not None:
# 2016 [c2cb8e88] 及以上版本去除了sdxl clip的clip_layer方法
if compare_revision(2016):
model.cond_stage_model.set_clip_options({'layer': model.layer_idx})
else:
model.cond_stage_model.clip_layer(model.layer_idx)
# if compare_revision(2016):
model.cond_stage_model.set_clip_options({'layer': model.layer_idx})
# else:
# model.cond_stage_model.clip_layer(model.layer_idx)
model_management.load_model_gpu(model.patcher)
return encode_func(model.cond_stage_model, token_weight_pairs)
@@ -316,8 +316,8 @@ def advanced_encode(clip, text, token_normalization, weight_interpretation, w_ma
embeddings_final, pooled = advanced_encode_from_tokens(tokenized['l'],
token_normalization,
weight_interpretation,
lambda x: (clip.encode_from_tokens({'l': x}), None),
w_max=w_max)
lambda x: encode_token_weights(clip, x, encode_token_weights_l),
w_max=w_max,return_pooled=True,)
cond = [[embeddings_final, {"pooled_output": pooled}]]
if conditioning is not None:
+41 -2
View File
@@ -38,7 +38,7 @@ MAX_SEED_NUM = 1125899906842624
RESOURCES_DIR = os.path.join(Path(__file__).parent.parent, "resources")
# fooocus
# inpaint
INPAINT_DIR = os.path.join(folder_paths.models_dir, "inpaint")
FOOOCUS_STYLES_DIR = os.path.join(Path(__file__).parent.parent, "styles")
FOOOCUS_STYLES_SAMPLES = 'https://raw.githubusercontent.com/lllyasviel/Fooocus/main/sdxl_styles/samples/'
@@ -58,6 +58,24 @@ FOOOCUS_INPAINT_PATCH = {
"model_url": "https://huggingface.co/lllyasviel/fooocus_inpaint/resolve/main/inpaint.fooocus.patch"
},
}
BRUSHNET_MODELS = {
"random_mask": {
"sd1": {
"model_url": "https://huggingface.co/Kijai/BrushNet-fp16/resolve/main/brushnet_random_mask_fp16.safetensors"
},
"sdxl": {
"model_url": "https://huggingface.co/yolain/brushnet/resolve/main/brushnet_random_mask_sdxl.safetensors"
}
},
"segmentation_mask": {
"sd1": {
"model_url": "https://huggingface.co/Kijai/BrushNet-fp16/resolve/main/brushnet_segmentation_mask_fp16.safetensors"
},
"sdxl": {
"model_url": "https://huggingface.co/yolain/brushnet/resolve/main/brushnet_segmentation_mask_sdxl.safetensors"
}
}
}
# layerDiffuse
LAYER_DIFFUSION_DIR = os.path.join(folder_paths.models_dir, "layer_model")
@@ -94,7 +112,7 @@ LAYER_DIFFUSION = {
}
},
"Everything": {
"sd15": {
"sd1": {
"model_url": "https://huggingface.co/LayerDiffusion/layerdiffusion-v1/resolve/main/layer_sd15_joint.safetensors"
},
"sdxl": {
@@ -135,6 +153,27 @@ LAYER_DIFFUSION = {
},
}
# IC Light
IC_LIGHT_MODELS = {
"Foreground": {
"sd1": {
"model_url": "https://huggingface.co/huchenlei/IC-Light-ldm/resolve/main/iclight_sd15_fc_unet_ldm.safetensors"
},
"sdxl": {
"model_url": None
}
},
"Foreground&Background": {
"sd1": {
"model_url": "https://huggingface.co/huchenlei/IC-Light-ldm/resolve/main/iclight_sd15_fbc_unet_ldm.safetensors"
},
"sdxl": {
"model_url": None
}
}
}
# REMBG
REMBG_DIR = os.path.join(folder_paths.models_dir, "rembg")
REMBG_MODELS = {
+316 -123
View File
@@ -1,6 +1,7 @@
import sys, os, re, json, time, math, copy
import sys, os, re, json, time
import torch
import folder_paths
import numpy as np
import comfy.utils, comfy.sample, comfy.samplers, comfy.controlnet, comfy.model_base, comfy.model_management
try:
import comfy.sampler_helpers
@@ -9,20 +10,21 @@ except:
from comfy.sd import CLIP, VAE
from comfy.model_patcher import ModelPatcher
from comfy_extras.chainner_models import model_loading
from comfy_extras.nodes_mask import LatentCompositeMasked
from comfy_extras.nodes_mask import LatentCompositeMasked, GrowMask
from comfy_extras.nodes_compositing import JoinImageWithAlpha
from comfy.clip_vision import load as load_clip_vision
from urllib.request import urlopen
from PIL import Image
from server import PromptServer
from nodes import MAX_RESOLUTION, LatentFromBatch, RepeatLatentBatch, NODE_CLASS_MAPPINGS as ALL_NODE_CLASS_MAPPINGS, ConditioningSetMask, ConditioningConcat, CLIPTextEncode, VAEEncodeForInpaint, InpaintModelConditioning
from .config import MAX_SEED_NUM, BASE_RESOLUTIONS, RESOURCES_DIR, INPAINT_DIR, FOOOCUS_STYLES_DIR, FOOOCUS_INPAINT_HEAD, FOOOCUS_INPAINT_PATCH, IPADAPTER_DIR, IPADAPTER_MODELS, DYNAMICRAFTER_DIR, DYNAMICRAFTER_MODELS
from .config import MAX_SEED_NUM, BASE_RESOLUTIONS, RESOURCES_DIR, INPAINT_DIR, FOOOCUS_STYLES_DIR, FOOOCUS_INPAINT_HEAD, FOOOCUS_INPAINT_PATCH, BRUSHNET_MODELS, IPADAPTER_DIR, IPADAPTER_MODELS, DYNAMICRAFTER_DIR, DYNAMICRAFTER_MODELS, IC_LIGHT_MODELS
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_diffuse.func import LayerDiffuse, LayerMethod
from .libs.utils import find_wildcards_seed, is_linked_styles_selector, easySave, get_local_filepath, add_folder_path_and_extensions, AlwaysEqualProxy, get_sd_version
from .libs.utils import find_wildcards_seed, is_linked_styles_selector, easySave, get_local_filepath, AlwaysEqualProxy, get_sd_version
from .libs.loader import easyLoader
from .libs.sampler import easySampler, alignYourStepsScheduler
from .libs.xyplot import easyXYPlot
@@ -33,29 +35,6 @@ from .libs.easing import EasingBase
sampler = easySampler()
easyCache = easyLoader()
default_calculate_weight = copy.copy(ModelPatcher.calculate_weight)
image_suffixs = set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".tiff", ".svg", ".ico", ".apng", ".tif", ".hdr", ".exr"])
model_path = folder_paths.models_dir
add_folder_path_and_extensions("ultralytics_bbox", [os.path.join(model_path, "ultralytics", "bbox")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("ultralytics_segm", [os.path.join(model_path, "ultralytics", "segm")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("ultralytics", [os.path.join(model_path, "ultralytics")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("mmdets_bbox", [os.path.join(model_path, "mmdets", "bbox")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("mmdets_segm", [os.path.join(model_path, "mmdets", "segm")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("mmdets", [os.path.join(model_path, "mmdets")], folder_paths.supported_pt_extensions)
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")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("layer_model", [os.path.join(model_path, "layer_model")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("rembg", [os.path.join(model_path, "rembg")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("ipadapter", [os.path.join(model_path, "ipadapter")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("dynamicrafter_models", [os.path.join(model_path, "dynamicrafter_models")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("mediapipe", [os.path.join(model_path, "mediapipe")], set(['.tflite','.pth']))
add_folder_path_and_extensions("checkpoints_thumb", [os.path.join(model_path, "checkpoints")], image_suffixs)
add_folder_path_and_extensions("loras_thumb", [os.path.join(model_path, "loras")], image_suffixs)
# ---------------------------------------------------------------提示词 开始----------------------------------------------------------------------#
# 正面提示词
@@ -233,11 +212,64 @@ class stylesPromptSelector:
positive_prompt = positive + ', '
# 去重
positive_prompt = self.replace_repeat(positive_prompt) if positive_prompt else ''
negative_prompt = self.replace_repeat(negative_prompt) if negative_prompt else ''
# positive_prompt = self.replace_repeat(positive_prompt) if positive_prompt else ''
# negative_prompt = self.replace_repeat(negative_prompt) if negative_prompt else ''
return (positive_prompt, negative_prompt)
#prompt
class prompt:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"prompt": ("STRING", {"default": "", "multiline": True, "placeholder": "Prompt"}),
"main": ([
'none',
'beautiful woman, detailed face',
'handsome man, detailed face',
'pretty girl',
'handsome boy',
'dog',
'cat',
'Buddha',
'toy'
], {"default": "none"}),
"lighting": ([
'none',
'sunshine from window',
'neon light, city',
'sunset over sea',
'golden time',
'sci-fi RGB glowing, cyberpunk',
'natural lighting',
'warm atmosphere, at home, bedroom',
'magic lit',
'evil, gothic, Yharnam',
'light and shadow',
'shadow from window',
'soft studio lighting',
'home atmosphere, cozy bedroom illumination',
'neon, Wong Kar-wai, warm',
'cinemative lighting',
'neo punk lighting, cyberpunk',
],{"default":'none'})
}}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("prompt",)
FUNCTION = "doit"
CATEGORY = "EasyUse/Prompt"
def doit(self, prompt, main, lighting):
if lighting != 'none' and main != 'none':
prompt = main + ',' + lighting + ',' + prompt
elif lighting != 'none' and main == 'none':
prompt = prompt + ',' + lighting
elif main != 'none':
prompt = main + ',' + prompt
return prompt,
#promptList
class promptList:
@classmethod
@@ -1910,7 +1942,7 @@ class loraStackLoader:
loras.append((lora_name, model_strength, clip_strength))
return (loras,)
class controlnetNameStack:
class controlnetStack:
def get_file_list(filenames):
return [file for file in filenames if file != "put_models_here.txt" and "lllite" not in file]
@@ -1927,7 +1959,7 @@ class controlnetNameStack:
"start_percent_1": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"end_percent_1": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"switch_2": (["Off", "On"],),
"controlnet_2": (s.controlnets,),
"`controlnet`_2": (s.controlnets,),
"controlnet_strength_2": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"start_percent_2": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"end_percent_2": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
@@ -1971,7 +2003,7 @@ class controlnetSimple:
def controlnetApply(self, pipe, image, control_net_name, control_net=None, strength=1, scale_soft_weights=1):
positive, negative = easyControlnet().apply(control_net_name, image, pipe["positive"], pipe["negative"], strength, 0, 1, control_net, scale_soft_weights)
positive, negative = easyControlnet().apply(control_net_name, image, pipe["positive"], pipe["negative"], strength, 0, 1, control_net, scale_soft_weights, None, easyCache)
new_pipe = {
"model": pipe['model'],
@@ -2023,7 +2055,7 @@ class controlnetAdvanced:
def controlnetApply(self, pipe, image, control_net_name, control_net=None, strength=1, start_percent=0, end_percent=1, scale_soft_weights=1):
positive, negative = easyControlnet().apply(control_net_name, image, pipe["positive"], pipe["negative"],
strength, start_percent, end_percent, control_net, scale_soft_weights)
strength, start_percent, end_percent, control_net, scale_soft_weights, None, easyCache)
new_pipe = {
"model": pipe['model'],
@@ -2087,22 +2119,26 @@ class LLLiteLoader:
# FooocusInpaint
from .fooocus import InpaintHead, InpaintWorker
inpaint_head_model = None
class fooocusInpaintLoader:
class applyFooocusInpaint:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model": ("MODEL",),
"latent": ("LATENT",),
"head": (list(FOOOCUS_INPAINT_HEAD.keys()),),
"patch": (list(FOOOCUS_INPAINT_PATCH.keys()),),
}
},
}
RETURN_TYPES = ("INPAINT_PATCH",)
RETURN_NAMES = ("patch",)
RETURN_TYPES = ("MODEL",)
RETURN_NAMES = ("model",)
CATEGORY = "EasyUse/Inpaint"
FUNCTION = "apply"
def apply(self, head, patch):
def apply(self, model, latent, head, patch):
global inpaint_head_model
head_file = get_local_filepath(FOOOCUS_INPAINT_HEAD[head]["model_url"], INPAINT_DIR)
@@ -2114,9 +2150,17 @@ class fooocusInpaintLoader:
patch_file = get_local_filepath(FOOOCUS_INPAINT_PATCH[patch]["model_url"], INPAINT_DIR)
inpaint_lora = comfy.utils.load_torch_file(patch_file, safe_load=True)
return ((inpaint_head_model, inpaint_lora),)
patch = (inpaint_head_model, inpaint_lora)
worker = InpaintWorker(node_name="easy kSamplerInpainting")
cloned = model.clone()
m, = worker.patch(cloned, latent, patch)
return (m,)
#---------------------------------------------------------------适配器 开始----------------------------------------------------------------------#
# 风格对齐
from .libs.styleAlign import styleAlignBatch, SHARE_NORM_OPTIONS, SHARE_ATTN_OPTIONS
class styleAlignedBatchAlign:
@@ -2138,6 +2182,92 @@ class styleAlignedBatchAlign:
def align(self, model, share_norm, share_attn, scale):
return (styleAlignBatch(model, share_norm, share_attn, scale),)
# 光照对齐
from .ic_light.func import ICLight, VAEEncodeArgMax
class icLightApply:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"mode": (list(IC_LIGHT_MODELS.keys()),),
"model": ("MODEL",),
"image": ("IMAGE",),
"vae": ("VAE",),
"lighting": (['None', 'Left Light', 'Right Light', 'Top Light', 'Bottom Light', 'Circle Light'],{"default": "None"}),
"source": (['Use Background Image', 'Use Flipped Background Image', 'Left Light', 'Right Light', 'Top Light', 'Bottom Light', 'Ambient'],{"default": "Use Background Image"}),
"remove_bg": ("BOOLEAN", {"default": True}),
},
}
RETURN_TYPES = ("MODEL", "IMAGE")
RETURN_NAMES = ("model", "lighting_image")
FUNCTION = "apply"
OUTPUT_NODE = True
CATEGORY = "EasyUse/Adapter"
def batch(self, image1, image2):
if image1.shape[1:] != image2.shape[1:]:
image2 = comfy.utils.common_upscale(image2.movedim(-1, 1), image1.shape[2], image1.shape[1], "bilinear",
"center").movedim(1, -1)
s = torch.cat((image1, image2), dim=0)
return s
def removebg(self, image):
if "easy imageRemBg" not in ALL_NODE_CLASS_MAPPINGS:
raise Exception("Please re-install ComfyUI-Easy-Use")
cls = ALL_NODE_CLASS_MAPPINGS['easy imageRemBg']
results = cls().remove('RMBG-1.4', image, 'Hide', 'ComfyUI')
if "result" in results:
image, _ = results['result']
return image
def apply(self, mode, model, image, vae, lighting, source, remove_bg):
model_type = get_sd_version(model)
if model_type == 'sdxl':
raise Exception("IC Light model is not supported for SDXL now")
batch_size, height, width, channel = image.shape
if channel == 3:
# remove bg
if mode == 'Foreground' or batch_size == 1:
if remove_bg:
image = self.removebg(image)
else:
mask = torch.full((1, height, width), 1.0, dtype=torch.float32, device="cpu")
image, = JoinImageWithAlpha().join_image_with_alpha(image, mask)
iclight = ICLight()
if mode == 'Foreground':
lighting_image = iclight.generate_lighting_image(image, lighting)
else:
lighting_image = iclight.generate_source_image(image, source)
if source != 'Use Background Image':
_, height, width, _ = lighting_image.shape
mask = torch.full((1, height, width), 1.0, dtype=torch.float32, device="cpu")
lighting_image, = JoinImageWithAlpha().join_image_with_alpha(lighting_image, mask)
if batch_size < 2:
image = self.batch(image, lighting_image)
else:
original_image = [img.unsqueeze(0) for img in image]
original_image = self.removebg(original_image[0])
image = self.batch(original_image, lighting_image)
latent, = VAEEncodeArgMax().encode(vae, image)
key = 'iclight_' + mode + '_' + model_type
model_path = get_local_filepath(IC_LIGHT_MODELS[mode]['sd1']["model_url"],
os.path.join(folder_paths.models_dir, "unet"))
ic_model = None
if key in backend_cache.cache:
log_node_info("easy icLightApply", f"Using icLightModel {mode+'_'+model_type} Cached")
_, ic_model = backend_cache.cache[key][1]
m, _ = iclight.apply(model_path, model, latent, ic_model)
else:
m, ic_model = iclight.apply(model_path, model, latent, ic_model)
backend_cache.update_cache(key, 'iclight', (False, ic_model))
return (m, lighting_image)
def insightface_loader(provider):
try:
from insightface.app import FaceAnalysis
@@ -2396,7 +2526,7 @@ class ipadapterApply(ipadapter):
"weight_faceidv2": ("FLOAT", { "default": 1.0, "min": -1, "max": 5.0, "step": 0.05 }),
"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}),
"cache_mode": (["insightface only", "clip_vision only", "ipadapter only", "all", "none"], {"default": "insightface only"},),
"cache_mode": (["insightface only", "clip_vision only", "ipadapter only", "all", "none"], {"default": "all"},),
"use_tiled": ("BOOLEAN", {"default": False},),
},
@@ -2458,7 +2588,7 @@ class ipadapterApplyAdvanced(ipadapter):
"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}),
"embeds_scaling": (['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],),
"cache_mode": (["insightface only", "clip_vision only","ipadapter only", "all", "none"], {"default": "insightface only"},),
"cache_mode": (["insightface only", "clip_vision only","ipadapter only", "all", "none"], {"default": "all"},),
"use_tiled": ("BOOLEAN", {"default": False},),
"use_batch": ("BOOLEAN", {"default": False},),
"sharpening": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.05}),
@@ -2527,7 +2657,7 @@ class ipadapterStyleComposition(ipadapter):
"end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
"embeds_scaling": (['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],),
"cache_mode": (["insightface only", "clip_vision only", "ipadapter only", "all", "none"],
{"default": "insightface only"},),
{"default": "all"},),
},
"optional": {
"image_composition": ("IMAGE",),
@@ -2849,7 +2979,8 @@ class instantID:
# Apply InstantID
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)
if control_net is None:
control_net = easyCache.load_controlnet(control_net_name, 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=mask)
else:
self.error()
@@ -3005,9 +3136,14 @@ class samplerSettings:
vae = pipe["vae"]
batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1
if image_to_latent is not None:
samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])}
samples = RepeatLatentBatch().repeat(samples, batch_size)[0]
images = image_to_latent
_, height, width, _ = image_to_latent.shape
if height == 1 and width == 1:
samples = pipe["samples"]
images = pipe["images"]
else:
samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])}
samples = RepeatLatentBatch().repeat(samples, batch_size)[0]
images = image_to_latent
elif latent is not None:
samples = latent
images = pipe["images"]
@@ -3081,9 +3217,14 @@ class samplerSettingsAdvanced:
vae = pipe["vae"]
batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1
if image_to_latent is not None:
samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])}
samples = RepeatLatentBatch().repeat(samples, batch_size)[0]
images = image_to_latent
_, height, width, _ = image_to_latent.shape
if height == 1 and width == 1:
samples = pipe["samples"]
images = pipe["images"]
else:
samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])}
samples = RepeatLatentBatch().repeat(samples, batch_size)[0]
images = image_to_latent
elif latent is not None:
samples = latent
images = pipe["images"]
@@ -3186,7 +3327,6 @@ class samplerSettingsNoiseIn:
def expand_mask(self, mask, expand, tapered_corners):
try:
import numpy as np
import scipy
c = 0 if tapered_corners else 1
@@ -3398,13 +3538,18 @@ class samplerCustomSettings:
batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1
_guider, sigmas = None, None
if image_to_latent is not None:
if guider == "IP2P+DualCFG":
positive, negative, latent = self.ip2p(pipe['positive'], pipe['negative'], vae, image_to_latent)
samples = latent
_, height, width, _ = image_to_latent.shape
if height == 1 and width == 1:
samples = pipe["samples"]
images = pipe["images"]
else:
samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])}
samples = RepeatLatentBatch().repeat(samples, batch_size)[0]
images = image_to_latent
if guider == "IP2P+DualCFG":
positive, negative, latent = self.ip2p(pipe['positive'], pipe['negative'], vae, image_to_latent)
samples = latent
else:
samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])}
samples = RepeatLatentBatch().repeat(samples, batch_size)[0]
images = image_to_latent
elif latent is not None:
if guider == "IP2P+DualCFG":
positive, negative, latent = self.ip2p(pipe['positive'], pipe['negative'], latent=latent)
@@ -4052,12 +4197,9 @@ class samplerFull(LayerDiffuse):
FUNCTION = "run"
CATEGORY = "EasyUse/Sampler"
def run(self, pipe, steps, cfg, sampler_name, scheduler, denoise, image_output, link_id, save_prefix, seed=None, model=None, positive=None, negative=None, latent=None, vae=None, clip=None, xyPlot=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False, downscale_options=None):
def run(self, pipe, steps, cfg, sampler_name, scheduler, denoise, image_output, link_id, save_prefix, seed=None, model=None, positive=None, negative=None, latent=None, vae=None, clip=None, xyPlot=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False, downscale_options=None, image=None):
# Clean loaded_objects
easyCache.update_loaded_objects(prompt)
samp_model = model if model is not None else pipe["model"]
samp_model = model.clone() if model is not None else pipe["model"].clone()
samp_positive = positive if positive is not None else pipe["positive"]
samp_negative = negative if negative is not None else pipe["negative"]
samp_samples = latent if latent is not None else pipe["samples"]
@@ -4078,6 +4220,9 @@ class samplerFull(LayerDiffuse):
add_noise = pipe['loader_settings']['add_noise'] if 'add_noise' in pipe['loader_settings'] else 'enabled'
force_full_denoise = pipe['loader_settings']['force_full_denoise'] if 'force_full_denoise' in pipe['loader_settings'] else True
if image is not None and latent is None:
samp_samples = {"samples": samp_vae.encode(image[:, :, :, :3])}
disable_noise = False
if add_noise == "disable":
disable_noise = True
@@ -4177,13 +4322,9 @@ class samplerFull(LayerDiffuse):
spent_time = 'Diffusion:' + str((end_time-start_time)/1000)+'″, VAEDecode:' + str((end_decode_time-end_time)/1000)+'″ '
results = easySave(new_images, save_prefix, image_output, prompt, extra_pnginfo)
sampler.update_value_by_id("results", my_unique_id, results)
# Clean loaded_objects
easyCache.update_loaded_objects(prompt)
new_pipe = {
"model": samp_model,
**pipe,
"positive": samp_positive,
"negative": samp_negative,
"vae": samp_vae,
@@ -4202,8 +4343,6 @@ class samplerFull(LayerDiffuse):
}
}
sampler.update_value_by_id("pipe_line", my_unique_id, new_pipe)
del pipe
if image_output == 'Preview&Choose':
@@ -4241,7 +4380,9 @@ class samplerFull(LayerDiffuse):
if image_output in ("Sender", "Sender&Save"):
PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results})
ModelPatcher.calculate_weight = default_calculate_weight
if hasattr(ModelPatcher, "original_calculate_weight"):
ModelPatcher.calculate_weight = ModelPatcher.original_calculate_weight
return {"ui": {"images": results},
"result": sampler.get_output(new_pipe,)}
@@ -4329,13 +4470,9 @@ class samplerFull(LayerDiffuse):
output_images, samp_model)
results = easySave(images, save_prefix, image_output, prompt, extra_pnginfo)
sampler.update_value_by_id("results", my_unique_id, results)
# Clean loaded_objects
easyCache.update_loaded_objects(prompt)
new_pipe = {
"model": samp_model,
**pipe,
"positive": samp_positive,
"negative": samp_negative,
"vae": samp_vae,
@@ -4351,14 +4488,14 @@ class samplerFull(LayerDiffuse):
"loader_settings": pipe["loader_settings"],
}
sampler.update_value_by_id("pipe_line", my_unique_id, new_pipe)
del pipe
if hasattr(ModelPatcher, "original_calculate_weight"):
ModelPatcher.calculate_weight = ModelPatcher.original_calculate_weight
if image_output in ("Hide", "Hide&Save"):
return sampler.get_output(new_pipe)
ModelPatcher.calculate_weight = default_calculate_weight
return {"ui": {"images": results}, "result": (sampler.get_output(new_pipe))}
preview_latent = True
@@ -4442,6 +4579,7 @@ 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(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)
@@ -4477,6 +4615,7 @@ class samplerSimpleLayerDiffusion:
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):
result = 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)
@@ -4563,12 +4702,11 @@ class samplerSimpleInpainting:
"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"}),
"additional": (["None", "Differential Diffusion", "Only InpaintModelConditioning"],{"default": "None"})
"additional": (["None", "InpaintModelCond", "Differential Diffusion", "Fooocus Inpaint", "Fooocus Inpaint + DD", "Brushnet Random", "Brushnet Random + DD", "Brushnet Segmentation", "Brushnet Segmentation + DD"],{"default": "None"})
},
"optional": {
"model": ("MODEL",),
"mask": ("MASK",),
"patch": ("INPAINT_PATCH",),
},
"hidden":
{"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID",
@@ -4582,57 +4720,107 @@ class samplerSimpleInpainting:
FUNCTION = "run"
CATEGORY = "EasyUse/Sampler"
def run(self, pipe, grow_mask_by, image_output, link_id, save_prefix, additional, model=None, mask=None, patch=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False):
fooocus_model = None
model = model if model is not None else pipe['model']
def dd(self, model, positive, negative, pixels, vae, mask):
positive, negative, latent = InpaintModelConditioning().encode(positive, negative, pixels, vae, mask)
cls = ALL_NODE_CLASS_MAPPINGS['DifferentialDiffusion']
if cls is not None:
model, = cls().apply(model)
else:
raise Exception("Differential Diffusion not found,please update comfyui")
return positive, negative, latent, model
def get_brushnet_model(self, type, model):
model_type = 'sdxl' if isinstance(model.model.model_config, comfy.supported_models.SDXL) else 'sd1'
if type == 'random':
brush_model = BRUSHNET_MODELS['random_mask'][model_type]['model_url']
if model_type == 'sdxl':
pattern = 'brushnet.random.mask.sdxl.*\.(safetensors|bin)$'
else:
pattern = 'brushnet.random.mask.*\.(safetensors|bin)$'
elif type == 'segmentation':
brush_model = BRUSHNET_MODELS['segmentation_mask'][model_type]['model_url']
if model_type == 'sdxl':
pattern = 'brushnet.segmentation.mask.sdxl.*\.(safetensors|bin)$'
else:
pattern = 'brushnet.segmentation.mask.*\.(safetensors|bin)$'
brushfile = [e for e in folder_paths.get_filename_list('inpaint') if re.search(pattern, e, re.IGNORECASE)]
brushname = brushfile[0] if brushfile else None
if not brushname:
from urllib.parse import urlparse
get_local_filepath(brush_model, INPAINT_DIR)
parsed_url = urlparse(brush_model)
brushname = os.path.basename(parsed_url.path)
return brushname
def apply_brushnet(self, brushname, model, vae, image, mask, positive, negative, scale=1.0, start_at=0, end_at=10000):
if "BrushNetLoader" not in ALL_NODE_CLASS_MAPPINGS:
raise Exception("BrushNetLoader not found,please install ComfyUI-BrushNet")
cls = ALL_NODE_CLASS_MAPPINGS['BrushNetLoader']
brushnet, = cls().brushnet_loading(brushname, 'float16')
cls = ALL_NODE_CLASS_MAPPINGS['BrushNet']
m, positive, negative, latent = cls().model_update(model=model, vae=vae, image=image, mask=mask, brushnet=brushnet, positive=positive, negative=negative, scale=scale, start_at=start_at, end_at=end_at)
return m, positive, negative, latent
def run(self, pipe, grow_mask_by, image_output, link_id, save_prefix, additional, model=None, mask=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False):
_model = model if model is not None else pipe['model']
latent = pipe['samples'] if 'samples' in pipe else None
positive = pipe['positive']
negative = pipe['negative']
pixels = pipe["images"] if pipe and "images" in pipe else None
images = pipe["images"] if pipe and "images" in pipe else None
vae = pipe["vae"] if pipe and "vae" in pipe else None
if 'noise_mask' in latent and mask is None:
mask = latent['noise_mask']
else:
if pixels is None:
elif mask is not None:
if images is None:
raise Exception("No Images found")
if vae is None:
raise Exception("No VAE found")
latent, = VAEEncodeForInpaint().encode(vae, pixels, mask, grow_mask_by)
mask = latent['noise_mask']
if mask is not None:
if additional != "None":
match additional:
case 'Differential Diffusion':
positive, negative, latent, _model = self.dd(_model, positive, negative, images, vae, mask)
case 'InpaintModelCond':
if mask is not None:
mask, = GrowMask().expand_mask(mask, grow_mask_by, False)
positive, negative, latent = InpaintModelConditioning().encode(positive, negative, images, vae, mask)
case 'Fooocus Inpaint':
head = list(FOOOCUS_INPAINT_HEAD.keys())[0]
patch = list(FOOOCUS_INPAINT_PATCH.keys())[0]
if mask is not None:
latent, = VAEEncodeForInpaint().encode(vae, images, mask, grow_mask_by)
_model, = applyFooocusInpaint().apply(_model, latent, head, patch)
case 'Fooocus Inpaint + DD':
head = list(FOOOCUS_INPAINT_HEAD.keys())[0]
patch = list(FOOOCUS_INPAINT_PATCH.keys())[0]
if mask is not None:
latent, = VAEEncodeForInpaint().encode(vae, images, mask, grow_mask_by)
_model, = applyFooocusInpaint().apply(_model, latent, head, patch)
positive, negative, latent, _model = self.dd(_model, positive, negative, images, vae, mask)
case 'Brushnet Random':
mask, = GrowMask().expand_mask(mask, grow_mask_by, False)
brush_name = self.get_brushnet_model('random', _model)
_model, positive, negative, latent = self.apply_brushnet(brush_name, _model, vae, images, mask, positive, negative)
case 'Brushnet Random + DD':
mask, = GrowMask().expand_mask(mask, grow_mask_by, False)
brush_name = self.get_brushnet_model('random', _model)
_model, positive, negative, latent = self.apply_brushnet(brush_name, _model, vae, images, mask, positive, negative)
positive, negative, latent, _model = self.dd(_model, positive, negative, images, vae, mask)
case 'Brushnet Segmentation':
mask, = GrowMask().expand_mask(mask, grow_mask_by, False)
brush_name = self.get_brushnet_model('segmentation', _model)
_model, positive, negative, latent = self.apply_brushnet(brush_name, _model, vae, images, mask, positive, negative)
case 'Brushnet Segmentation + DD':
mask, = GrowMask().expand_mask(mask, grow_mask_by, False)
brush_name = self.get_brushnet_model('segmentation', _model)
_model, positive, negative, latent = self.apply_brushnet(brush_name, _model, vae, images, mask, positive, negative)
positive, negative, latent, _model = self.dd(_model, positive, negative, images, vae, mask)
case _:
latent, = VAEEncodeForInpaint().encode(vae, images, mask, grow_mask_by)
positive, negative, latent = InpaintModelConditioning().encode(positive, negative, pixels, vae, mask)
if additional == "Differential Diffusion":
cls = ALL_NODE_CLASS_MAPPINGS['DifferentialDiffusion']
if cls is not None:
model, = cls().apply(model)
else:
raise Exception("Differential Diffusion not found,please update comfyui")
# when patch was linked
fooocus_model = None
if patch is not None:
worker = InpaintWorker(node_name="easy kSamplerInpainting")
fooocus_model, = worker.patch(model, latent, patch)
new_pipe = {
**pipe,
"model": fooocus_model if fooocus_model else model,
"positive": positive,
"negative": negative,
"vae": vae,
"samples": latent,
"loader_settings": pipe["loader_settings"],
}
else:
new_pipe = pipe
del pipe
results = samplerFull().run(new_pipe, None, None,None,None,None, image_output, link_id, save_prefix,
None, None, None, None, None, None, None, None,
results = samplerFull().run(pipe, None, None,None,None,None, image_output, link_id, save_prefix,
None, _model, positive, negative, latent, vae, None, None,
tile_size, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise)
result = results['result']
@@ -5596,8 +5784,7 @@ class detailerFix:
del pipe
if image_output in ("Hide", "Hide&Save"):
return {"ui": {},
"result": (new_pipe, result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, result_mask, result_cnet_images )}
return (new_pipe, result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, result_mask, result_cnet_images)
if image_output in ("Sender", "Sender&Save"):
PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results})
@@ -7033,6 +7220,7 @@ NODE_CLASS_MAPPINGS = {
"easy positive": positivePrompt,
"easy negative": negativePrompt,
"easy wildcards": wildcardsPrompt,
"easy prompt": prompt,
"easy promptList": promptList,
"easy promptLine": promptLine,
"easy promptConcat": promptConcat,
@@ -7063,8 +7251,10 @@ NODE_CLASS_MAPPINGS = {
"easy instantIDApply": instantIDApply,
"easy instantIDApplyADV": instantIDApplyAdvanced,
"easy styleAlignedBatchAlign": styleAlignedBatchAlign,
"easy icLightApply": icLightApply,
# Inpaint 内补
"easy fooocusInpaintLoader": fooocusInpaintLoader,
# "easy fooocusInpaintLoader": fooocusInpaintLoader,
"easy applyFooocusInpaint": applyFooocusInpaint,
# latent 潜空间
"easy latentNoisy": latentNoisy,
"easy latentCompositeMaskedWithCond": latentCompositeMaskedWithCond,
@@ -7139,6 +7329,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"easy positive": "Positive",
"easy negative": "Negative",
"easy wildcards": "Wildcards",
"easy prompt": "Prompt",
"easy promptList": "PromptList",
"easy promptLine": "PromptLine",
"easy promptConcat": "PromptConcat",
@@ -7169,8 +7360,10 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"easy instantIDApply": "Easy Apply InstantID",
"easy instantIDApplyADV": "Easy Apply InstantID (Advanced)",
"easy styleAlignedBatchAlign": "Easy Apply StyleAlign",
"easy icLightApply": "Easy Apply ICLight",
# Inpaint 内补
"easy fooocusInpaintLoader": "Load Fooocus Inpaint",
# "easy fooocusInpaintLoader": "Load Fooocus Inpaint(Removed)",
"easy applyFooocusInpaint": "Apply Fooocus Inpaint",
# latent 潜空间
"easy latentNoisy": "LatentNoisy",
"easy latentCompositeMaskedWithCond": "LatentCompositeMaskedWithCond",
+2
View File
@@ -19,6 +19,8 @@ class InpaintWorker:
def __init__(self, node_name):
self.node_name = node_name if node_name is not None else ""
self.original_calculate_weight = ModelPatcher.calculate_weight
if not hasattr(ModelPatcher, "original_calculate_weight"):
ModelPatcher.original_calculate_weight = self.original_calculate_weight
self.injected_model_patcher_calculate_weight = False
def load_fooocus_patch(self, lora: dict, to_load: dict):
+185
View File
@@ -0,0 +1,185 @@
import torch
import numpy as np
from typing import Tuple, TypedDict, Callable
import comfy.model_management
from comfy.sd import load_unet
from comfy.ldm.models.autoencoder import AutoencoderKL
from comfy.model_base import BaseModel
from PIL import Image
from nodes import VAEEncode
from ..layer_diffuse.model import ModelPatcher, calculate_weight_adjust_channel
from ..libs.image import np2tensor, pil2tensor
class UnetParams(TypedDict):
input: torch.Tensor
timestep: torch.Tensor
c: dict
cond_or_uncond: torch.Tensor
class VAEEncodeArgMax(VAEEncode):
def encode(self, vae, pixels):
assert isinstance(
vae.first_stage_model, AutoencoderKL
), "ArgMax only supported for AutoencoderKL"
original_sample_mode = vae.first_stage_model.regularization.sample
vae.first_stage_model.regularization.sample = False
ret = super().encode(vae, pixels)
vae.first_stage_model.regularization.sample = original_sample_mode
return ret
class ICLight:
@staticmethod
def apply_c_concat(params: UnetParams, concat_conds) -> UnetParams:
"""Apply c_concat on unet call."""
sample = params["input"]
params["c"]["c_concat"] = torch.cat(
(
[concat_conds.to(sample.device)]
* (sample.shape[0] // concat_conds.shape[0])
),
dim=0,
)
return params
@staticmethod
def create_custom_conv(
original_conv: torch.nn.Module,
dtype: torch.dtype,
device=torch.device,
) -> torch.nn.Module:
with torch.no_grad():
new_conv_in = torch.nn.Conv2d(
8,
original_conv.out_channels,
original_conv.kernel_size,
original_conv.stride,
original_conv.padding,
)
new_conv_in.weight.zero_()
new_conv_in.weight[:, :4, :, :].copy_(original_conv.weight)
new_conv_in.bias = original_conv.bias
return new_conv_in.to(dtype=dtype, device=device)
def generate_lighting_image(self, original_image, direction):
_, image_height, image_width, _ = original_image.shape
match direction:
case 'Left Light':
gradient = np.linspace(255, 0, image_width)
image = np.tile(gradient, (image_height, 1))
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
return np2tensor(input_bg)
case 'Right Light':
gradient = np.linspace(0, 255, image_width)
image = np.tile(gradient, (image_height, 1))
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
return np2tensor(input_bg)
case 'Top Light':
gradient = np.linspace(255, 0, image_height)[:, None]
image = np.tile(gradient, (1, image_width))
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
return np2tensor(input_bg)
case 'Bottom Light':
gradient = np.linspace(0, 255, image_height)[:, None]
image = np.tile(gradient, (1, image_width))
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
return np2tensor(input_bg)
case 'Circle Light':
x = np.linspace(-1, 1, image_width)
y = np.linspace(-1, 1, image_height)
x, y = np.meshgrid(x, y)
r = np.sqrt(x ** 2 + y ** 2)
r = r / r.max()
color1 = np.array([0, 0, 0])[np.newaxis, np.newaxis, :]
color2 = np.array([255, 255, 255])[np.newaxis, np.newaxis, :]
gradient = (color1 * r[..., np.newaxis] + color2 * (1 - r)[..., np.newaxis]).astype(np.uint8)
image = pil2tensor(Image.fromarray(gradient))
return image
case _:
image = pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0)))
return image
def generate_source_image(self, original_image, source):
batch_size, image_height, image_width, _ = original_image.shape
match source:
case 'Use Flipped Background Image':
if batch_size < 2:
raise ValueError('Must be at least 2 image to use flipped background image.')
original_image = [img.unsqueeze(0) for img in original_image]
image = torch.flip(original_image[1], [2])
return image
case 'Ambient':
input_bg = np.zeros(shape=(image_height, image_width, 3), dtype=np.uint8) + 64
return np2tensor(input_bg)
case 'Left Light':
gradient = np.linspace(224, 32, image_width)
image = np.tile(gradient, (image_height, 1))
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
return np2tensor(input_bg)
case 'Right Light':
gradient = np.linspace(32, 224, image_width)
image = np.tile(gradient, (image_height, 1))
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
return np2tensor(input_bg)
case 'Top Light':
gradient = np.linspace(224, 32, image_height)[:, None]
image = np.tile(gradient, (1, image_width))
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
return np2tensor(input_bg)
case 'Bottom Light':
gradient = np.linspace(32, 224, image_height)[:, None]
image = np.tile(gradient, (1, image_width))
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
return np2tensor(input_bg)
case _:
image = pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0)))
return image
def apply(self, ic_model_path, model: ModelPatcher, c_concat: dict, ic_model=None) -> Tuple[ModelPatcher]:
try:
ModelPatcher.calculate_weight = calculate_weight_adjust_channel(ModelPatcher.calculate_weight)
except:
pass
device = comfy.model_management.get_torch_device()
dtype = comfy.model_management.unet_dtype()
work_model = model.clone()
# Apply scale factor.
base_model: BaseModel = work_model.model
scale_factor = base_model.model_config.latent_format.scale_factor
# [B, 4, H, W]
concat_conds: torch.Tensor = c_concat["samples"] * scale_factor
# [1, 4 * B, H, W]
concat_conds = torch.cat([c[None, ...] for c in concat_conds], dim=1)
def unet_dummy_apply(unet_apply: Callable, params: UnetParams):
"""A dummy unet apply wrapper serving as the endpoint of wrapper
chain."""
return unet_apply(x=params["input"], t=params["timestep"], **params["c"])
existing_wrapper = work_model.model_options.get(
"model_function_wrapper", unet_dummy_apply
)
def wrapper_func(unet_apply: Callable, params: UnetParams):
return existing_wrapper(unet_apply, params=self.apply_c_concat(params, concat_conds))
work_model.set_model_unet_function_wrapper(wrapper_func)
if not ic_model:
ic_model = load_unet(ic_model_path)
ic_model_state_dict = ic_model.model.diffusion_model.state_dict()
work_model.add_patches(
patches={
("diffusion_model." + key): (value.to(dtype=dtype, device=device),)
for key, value in ic_model_state_dict.items()
}
)
return (work_model, ic_model)
+370 -11
View File
@@ -1,4 +1,4 @@
from PIL import Image
from PIL import Image, ImageDraw, ImageFilter
import os
import hashlib
import folder_paths
@@ -8,9 +8,10 @@ import comfy.model_management
from comfy_extras.nodes_compositing import JoinImageWithAlpha
from server import PromptServer
from nodes import MAX_RESOLUTION
from torchvision.transforms import Resize, CenterCrop, InterpolationMode
from torchvision.transforms.functional import to_pil_image
from .log import log_node_info
from .libs.image import pil2tensor, tensor2pil, ResizeMode, get_new_bounds, RGB2RGBA, image2mask, blendImage
from .libs.image import pil2tensor, tensor2pil, ResizeMode, get_new_bounds, RGB2RGBA, image2mask, mask2image, blendImage
from .libs.colorfix import adain_color_fix, wavelet_color_fix
from .libs.chooser import ChooserMessage, ChooserCancelled
from .config import REMBG_DIR, REMBG_MODELS, HUMANPARSING_MODELS, MEDIAPIPE_MODELS, MEDIAPIPE_DIR
@@ -539,6 +540,41 @@ class imageSplitList:
new_images[1].append(img)
return new_images
class imageSplitGrid:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"images": ("IMAGE",),
"row": ("INT", {"default": 1,"min": 1,"max": 10,"step": 1,}),
"column": ("INT", {"default": 1,"min": 1,"max": 10,"step": 1,}),
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("images",)
FUNCTION = "doit"
CATEGORY = "EasyUse/Image"
def crop(self, image, width, height, x, y):
x = min(x, image.shape[2] - 1)
y = min(y, image.shape[1] - 1)
to_x = width + x
to_y = height + y
img = image[:, y:to_y, x:to_x, :]
return img
def doit(self, images, row, column):
_, height, width, _ = images.shape
sub_width = width // column
sub_height = height // row
new_images = []
for i in range(row):
for j in range(column):
new_images.append(self.crop(images, sub_width, sub_height, j * sub_width, i * sub_height))
return (torch.cat(new_images, dim=0),)
class imagesSplitImage:
@classmethod
def INPUT_TYPES(s):
@@ -557,6 +593,34 @@ class imagesSplitImage:
new_images = torch.chunk(images, len(images), dim=0)
return new_images
class imageConcat:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"image1": ("IMAGE",),
"image2": ("IMAGE",),
"direction": (['right','down','left','up',],{"default": 'right'}),
"match_image_size": ("BOOLEAN", {"default": False}),
}}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "concat"
CATEGORY = "EasyUse/Image"
def concat(self, image1, image2, direction, match_image_size):
if match_image_size:
image2 = torch.nn.functional.interpolate(image2, size=(image1.shape[2], image1.shape[3]), mode="bilinear")
if direction == 'right':
row = torch.cat((image1, image2), dim=2)
elif direction == 'down':
row = torch.cat((image1, image2), dim=1)
elif direction == 'left':
row = torch.cat((image2, image1), dim=2)
elif direction == 'up':
row = torch.cat((image2, image1), dim=1)
return (row,)
# 图片背景移除
from .briaai.rembg import BriaRMBG, preprocess_image, postprocess_image
from .libs.utils import get_local_filepath, easySave, install_package
@@ -632,12 +696,12 @@ class imageChooser(PreviewImage):
def INPUT_TYPES(self):
return {
"required":{
"mode": (['Always Pause', 'Keep Last Selection'], {"default": "Always Pause"}),
},
"optional": {
"images": ("IMAGE",),
},
"hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"},
"hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO"},
}
RETURN_TYPES = ("IMAGE",)
@@ -660,8 +724,7 @@ class imageChooser(PreviewImage):
else:
return None
def chooser(self, prompt=None, my_unique_id=None, **kwargs):
def chooser(self, prompt=None, my_unique_id=None, extra_pnginfo=None, **kwargs):
id = my_unique_id[0]
if id not in ChooserMessage.stash:
ChooserMessage.stash[id] = {}
@@ -684,9 +747,19 @@ class imageChooser(PreviewImage):
images = result['ui']['images']
PromptServer.instance.send_sync("easyuse-image-choose", {"id": id, "urls": images})
# 获取上次选择
mode = kwargs.pop('mode', 'Always Pause')
last_choosen = None
if mode == 'Keep Last Selection':
if id and extra_pnginfo[0] and "workflow" in extra_pnginfo[0]:
workflow = extra_pnginfo[0]["workflow"]
node = next((x for x in workflow["nodes"] if str(x["id"]) == id), None)
if node:
last_choosen = node['properties']['values']
# wait for selection
try:
selections = ChooserMessage.waitForMessage(id, asList=True)
selections = ChooserMessage.waitForMessage(id, asList=True) if last_choosen is None or len(last_choosen)<1 else last_choosen
choosen = [x for x in selections if x >= 0] if len(selections)>1 else [0]
except ChooserCancelled:
raise comfy.model_management.InterruptProcessingException()
@@ -794,6 +867,7 @@ class humanSegmentation:
"image": ("IMAGE",),
"method": (["selfie_multiclass_256x256", "human_parsing_lip"],),
"confidence": ("FLOAT", {"default": 0.4, "min": 0.05, "max": 0.95, "step": 0.01},),
"crop_multi": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.001},),
},
"hidden": {
"prompt": "PROMPT",
@@ -801,8 +875,8 @@ class humanSegmentation:
}
}
RETURN_TYPES = ("IMAGE", "MASK",)
RETURN_NAMES = ("image", "mask",)
RETURN_TYPES = ("IMAGE", "MASK", "BBOX")
RETURN_NAMES = ("image", "mask", "bbox")
FUNCTION = "parsing"
CATEGORY = "EasyUse/Segmentation"
@@ -819,7 +893,7 @@ class humanSegmentation:
numpy_image = cv2.cvtColor(numpy_image, cv2.COLOR_BGR2RGB)
return mp.Image(image_format=image_format, data=numpy_image)
def parsing(self, image, confidence, method, prompt=None, my_unique_id=None):
def parsing(self, image, confidence, method, crop_multi, prompt=None, my_unique_id=None):
mask_components = []
if my_unique_id in prompt:
if prompt[my_unique_id]["inputs"]['mask_components']:
@@ -919,7 +993,253 @@ class humanSegmentation:
output_image, = JoinImageWithAlpha().join_image_with_alpha(image, alpha)
return (output_image, mask)
# use crop
bbox = [[0, 0, 0, 0]]
if crop_multi > 0.0:
output_image, mask, bbox = imageCropFromMask().crop(output_image, mask, crop_multi, crop_multi, 1.0)
return (output_image, mask, bbox)
class imageCropFromMask:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
"mask": ("MASK",),
"image_crop_multi": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}),
"mask_crop_multi": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}),
"bbox_smooth_alpha": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
},
}
RETURN_TYPES = ("IMAGE", "MASK", "BBOX",)
RETURN_NAMES = ("crop_image", "crop_mask", "bbox",)
FUNCTION = "crop"
CATEGORY = "EasyUse/Image"
def smooth_bbox_size(self, prev_bbox_size, curr_bbox_size, alpha):
if alpha == 0:
return prev_bbox_size
return round(alpha * curr_bbox_size + (1 - alpha) * prev_bbox_size)
def smooth_center(self, prev_center, curr_center, alpha=0.5):
if alpha == 0:
return prev_center
return (
round(alpha * curr_center[0] + (1 - alpha) * prev_center[0]),
round(alpha * curr_center[1] + (1 - alpha) * prev_center[1])
)
def image2mask(self, image):
return image[:, :, :, 0]
def mask2image(self, mask):
return mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3)
def cropimage(self, original_images, masks, crop_size_mult, bbox_smooth_alpha):
bounding_boxes = []
cropped_images = []
self.max_bbox_width = 0
self.max_bbox_height = 0
# First, calculate the maximum bounding box size across all masks
curr_max_bbox_width = 0
curr_max_bbox_height = 0
for mask in masks:
_mask = tensor2pil(mask)
non_zero_indices = np.nonzero(np.array(_mask))
min_x, max_x = np.min(non_zero_indices[1]), np.max(non_zero_indices[1])
min_y, max_y = np.min(non_zero_indices[0]), np.max(non_zero_indices[0])
width = max_x - min_x
height = max_y - min_y
curr_max_bbox_width = max(curr_max_bbox_width, width)
curr_max_bbox_height = max(curr_max_bbox_height, height)
# Smooth the changes in the bounding box size
self.max_bbox_width = self.smooth_bbox_size(self.max_bbox_width, curr_max_bbox_width, bbox_smooth_alpha)
self.max_bbox_height = self.smooth_bbox_size(self.max_bbox_height, curr_max_bbox_height, bbox_smooth_alpha)
# Apply the crop size multiplier
self.max_bbox_width = round(self.max_bbox_width * crop_size_mult)
self.max_bbox_height = round(self.max_bbox_height * crop_size_mult)
bbox_aspect_ratio = self.max_bbox_width / self.max_bbox_height
# Then, for each mask and corresponding image...
for i, (mask, img) in enumerate(zip(masks, original_images)):
_mask = tensor2pil(mask)
non_zero_indices = np.nonzero(np.array(_mask))
min_x, max_x = np.min(non_zero_indices[1]), np.max(non_zero_indices[1])
min_y, max_y = np.min(non_zero_indices[0]), np.max(non_zero_indices[0])
# Calculate center of bounding box
center_x = np.mean(non_zero_indices[1])
center_y = np.mean(non_zero_indices[0])
curr_center = (round(center_x), round(center_y))
# If this is the first frame, initialize prev_center with curr_center
if not hasattr(self, 'prev_center'):
self.prev_center = curr_center
# Smooth the changes in the center coordinates from the second frame onwards
if i > 0:
center = self.smooth_center(self.prev_center, curr_center, bbox_smooth_alpha)
else:
center = curr_center
# Update prev_center for the next frame
self.prev_center = center
# Create bounding box using max_bbox_width and max_bbox_height
half_box_width = round(self.max_bbox_width / 2)
half_box_height = round(self.max_bbox_height / 2)
min_x = max(0, center[0] - half_box_width)
max_x = min(img.shape[1], center[0] + half_box_width)
min_y = max(0, center[1] - half_box_height)
max_y = min(img.shape[0], center[1] + half_box_height)
# Append bounding box coordinates
bounding_boxes.append((min_x, min_y, max_x - min_x, max_y - min_y))
# Crop the image from the bounding box
cropped_img = img[min_y:max_y, min_x:max_x, :]
# Calculate the new dimensions while maintaining the aspect ratio
new_height = min(cropped_img.shape[0], self.max_bbox_height)
new_width = round(new_height * bbox_aspect_ratio)
# Resize the image
resize_transform = Resize((new_height, new_width))
resized_img = resize_transform(cropped_img.permute(2, 0, 1))
# Perform the center crop to the desired size
crop_transform = CenterCrop((self.max_bbox_height, self.max_bbox_width)) # swap the order here if necessary
cropped_resized_img = crop_transform(resized_img)
cropped_images.append(cropped_resized_img.permute(1, 2, 0))
return cropped_images, bounding_boxes
def crop(self, image, mask, image_crop_multi, mask_crop_multi, bbox_smooth_alpha):
cropped_images, bounding_boxes = self.cropimage(image, mask, image_crop_multi, bbox_smooth_alpha)
cropped_mask_image, _ = self.cropimage(self.mask2image(mask), mask, mask_crop_multi, bbox_smooth_alpha)
cropped_image_out = torch.stack(cropped_images, dim=0)
cropped_mask_out = torch.stack(cropped_mask_image, dim=0)
return (cropped_image_out, cropped_mask_out[:, :, :, 0], bounding_boxes)
class imageUncropFromBBOX:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"original_image": ("IMAGE",),
"crop_image": ("IMAGE",),
"bbox": ("BBOX",),
"border_blending": ("FLOAT", {"default": 0.25, "min": 0.0, "max": 1.0, "step": 0.01},),
"use_square_mask": ("BOOLEAN", {"default": True}),
},
"optional":{
"optional_mask": ("MASK",)
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "uncrop"
CATEGORY = "EasyUse/Image"
def bbox_check(self, bbox, target_size=None):
if not target_size:
return bbox
new_bbox = (
bbox[0],
bbox[1],
min(target_size[0] - bbox[0], bbox[2]),
min(target_size[1] - bbox[1], bbox[3]),
)
return new_bbox
def bbox_to_region(self, bbox, target_size=None):
bbox = self.bbox_check(bbox, target_size)
return (bbox[0], bbox[1], bbox[0] + bbox[2], bbox[1] + bbox[3])
def uncrop(self, original_image, crop_image, bbox, border_blending, use_square_mask, optional_mask=None):
def inset_border(image, border_width=20, border_color=(0)):
width, height = image.size
bordered_image = Image.new(image.mode, (width, height), border_color)
bordered_image.paste(image, (0, 0))
draw = ImageDraw.Draw(bordered_image)
draw.rectangle((0, 0, width - 1, height - 1), outline=border_color, width=border_width)
return bordered_image
if len(original_image) != len(crop_image):
raise ValueError(
f"The number of original_images ({len(original_image)}) and cropped_images ({len(crop_image)}) should be the same")
# Ensure there are enough bboxes, but drop the excess if there are more bboxes than images
if len(bbox) > len(original_image):
print(f"Warning: Dropping excess bounding boxes. Expected {len(original_image)}, but got {len(bbox)}")
bbox = bbox[:len(original_image)]
elif len(bbox) < len(original_image):
raise ValueError("There should be at least as many bboxes as there are original and cropped images")
out_images = []
for i in range(len(original_image)):
img = tensor2pil(original_image[i])
crop = tensor2pil(crop_image[i])
_bbox = bbox[i]
bb_x, bb_y, bb_width, bb_height = _bbox
paste_region = self.bbox_to_region((bb_x, bb_y, bb_width, bb_height), img.size)
# rescale the crop image to fit the paste_region
crop = crop.resize((round(paste_region[2] - paste_region[0]), round(paste_region[3] - paste_region[1])))
crop_img = crop.convert("RGB")
# border blending
if border_blending > 1.0:
border_blending = 1.0
elif border_blending < 0.0:
border_blending = 0.0
blend_ratio = (max(crop_img.size) / 2) * float(border_blending)
blend = img.convert("RGBA")
if use_square_mask:
mask = Image.new("L", img.size, 0)
mask_block = Image.new("L", (paste_region[2] - paste_region[0], paste_region[3] - paste_region[1]), 255)
mask_block = inset_border(mask_block, round(blend_ratio / 2), (0))
mask.paste(mask_block, paste_region)
else:
if optional_mask is None:
raise ValueError("optional_mask is required when use_square_mask is False")
original_mask = tensor2pil(optional_mask)
original_mask = original_mask.resize((paste_region[2] - paste_region[0], paste_region[3] - paste_region[1]))
mask = Image.new("L", img.size, 0)
mask.paste(original_mask, paste_region)
mask = mask.filter(ImageFilter.BoxBlur(radius=blend_ratio / 4))
mask = mask.filter(ImageFilter.GaussianBlur(radius=blend_ratio / 4))
blend.paste(crop_img, paste_region)
blend.putalpha(mask)
img = Image.alpha_composite(img.convert("RGBA"), blend)
out_images.append(img.convert("RGB"))
output_images = torch.cat([pil2tensor(img) for img in out_images], dim=0)
return (output_images,)
import cv2
import base64
@@ -1001,6 +1321,35 @@ class imageToBase64:
base64_str = base64.b64encode(image_bytes).decode("utf-8")
return {"result": (base64_str,)}
class removeLocalImage:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"file_name": ("STRING",{"default":""}),
},
}
RETURN_TYPES = ()
OUTPUT_NODE = True
FUNCTION = "remove"
CATEGORY = "EasyUse/Image"
def remove(self, file_name):
hasFile = False
for file in os.listdir(folder_paths.input_directory):
name_without_extension, file_extension = os.path.splitext(file)
if name_without_extension == file_name or file == file_name:
os.remove(os.path.join(folder_paths.input_directory, file))
hasFile = True
break
if hasFile:
PromptServer.instance.send_sync("easyuse-toast", {"content": "Removed SuccessFully", "type":'success'})
else:
PromptServer.instance.send_sync("easyuse-toast", {"content": "Removed Failed", "type": 'error'})
return ()
# 姿势编辑器
class poseEditor:
@classmethod
@@ -1055,8 +1404,12 @@ NODE_CLASS_MAPPINGS = {
"easy imageScaleDownToSize": imageScaleDownToSize,
"easy imageRatio": imageRatio,
"easy imageToMask": imageToMask,
"easy imageConcat": imageConcat,
"easy imageSplitList": imageSplitList,
"easy imageSplitGrid": imageSplitGrid,
"easy imagesSplitImage": imagesSplitImage,
"easy imageCropFromMask": imageCropFromMask,
"easy imageUncropFromBBOX": imageUncropFromBBOX,
"easy imageSave": imageSaveSimple,
"easy imageRemBg": imageRemBg,
"easy imageChooser": imageChooser,
@@ -1066,6 +1419,7 @@ NODE_CLASS_MAPPINGS = {
"easy imageToBase64": imageToBase64,
"easy joinImageBatch": JoinImageBatch,
"easy humanSegmentation": humanSegmentation,
"easy removeLocalImage": removeLocalImage,
"easy poseEditor": poseEditor
}
@@ -1082,8 +1436,12 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"easy imageRatio": "ImageRatio",
"easy imageToMask": "ImageToMask",
"easy imageHSVMask": "ImageHSVMask",
"easy imageConcat": "imageConcat",
"easy imageSplitList": "imageSplitList",
"easy imageSplitGrid": "imageSplitGrid",
"easy imagesSplitImage": "imagesSplitImage",
"easy imageCropFromMask": "imageCropFromMask",
"easy imageUncropFromBBOX": "imageUncropFromBBOX",
"easy imageSave": "SaveImage (Simple)",
"easy imageRemBg": "Image Remove Bg",
"easy imageChooser": "Image Chooser",
@@ -1093,5 +1451,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"easy loadImageBase64": "Load Image (Base64)",
"easy imageToBase64": "Image To Base64",
"easy humanSegmentation": "Human Segmentation",
"easy removeLocalImage": "Remove Local Image",
"easy poseEditor": "PoseEditor",
}
+1 -1
View File
@@ -17,7 +17,7 @@ try:
from diffusers.models.modeling_utils import ModelMixin
from diffusers import __version__
if __version__:
if version.parse(__version__) < version.parse("0.27.0"):
if version.parse(__version__) < version.parse("0.26.0"):
from diffusers.models.unet_2d_blocks import UNetMidBlock2D, get_down_block, get_up_block
else:
from diffusers.models.unets.unet_2d_blocks import UNetMidBlock2D, get_down_block, get_up_block
+3 -17
View File
@@ -7,26 +7,12 @@ class easyControlnet:
def __init__(self):
pass
def load_controlnet(self, control_net_name, control_net, scale_soft_weights):
if control_net is None:
if scale_soft_weights < 1:
if "ScaledSoftControlNetWeights" in NODE_CLASS_MAPPINGS:
soft_weight_cls = NODE_CLASS_MAPPINGS['ScaledSoftControlNetWeights']
(weights, timestep_keyframe) = soft_weight_cls().load_weights(scale_soft_weights, False)
cn_adv_cls = NODE_CLASS_MAPPINGS['ControlNetLoaderAdvanced']
control_net, = cn_adv_cls().load_controlnet(control_net_name, timestep_keyframe)
else:
raise Exception(f"[Advanced-ControlNet Not Found] you need to install 'COMFYUI-Advanced-ControlNet'")
else:
controlnet_path = folder_paths.get_full_path("controlnet", control_net_name)
control_net = comfy.controlnet.load_controlnet(controlnet_path)
return control_net
def apply(self, control_net_name, image, positive, negative, strength, start_percent=0, end_percent=1, control_net=None, scale_soft_weights=1, mask=None):
def apply(self, control_net_name, image, positive, negative, strength, start_percent=0, end_percent=1, control_net=None, scale_soft_weights=1, mask=None, easyCache=None):
if strength == 0:
return (positive, negative)
control_net = self.load_controlnet(control_net_name, control_net, scale_soft_weights)
if control_net is None:
control_net = easyCache.load_controlnet(control_net_name, scale_soft_weights)
if mask is not None:
mask = mask.to(self.device)
+20
View File
@@ -5,6 +5,7 @@ import numpy as np
from enum import Enum
from PIL import Image
from io import BytesIO
from typing import List, Union
import folder_paths
from .utils import install_package
@@ -15,6 +16,17 @@ def pil2tensor(image):
# Tensor to PIL
def tensor2pil(image):
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
# np to Tensor
def np2tensor(img_np: Union[np.ndarray, List[np.ndarray]]) -> torch.Tensor:
if isinstance(img_np, list):
return torch.cat([np2tensor(img) for img in img_np], dim=0)
return torch.from_numpy(img_np.astype(np.float32) / 255.0).unsqueeze(0)
# Tensor to np
def tensor2np(tensor: torch.Tensor) -> List[np.ndarray]:
if len(tensor.shape) == 3: # Single image
return np.clip(255.0 * tensor.cpu().numpy(), 0, 255).astype(np.uint8)
else: # Batch of images
return [np.clip(255.0 * t.cpu().numpy(), 0, 255).astype(np.uint8) for t in tensor]
def pil2byte(pil_image, format='PNG'):
byte_arr = BytesIO()
@@ -48,6 +60,14 @@ def image2mask(image: Image) -> torch.Tensor:
ret_mask = torch.tensor([pil2tensor(_image)[0, :, :, 3].tolist()])
return ret_mask
def mask2image(mask: torch.Tensor) -> Image:
masks = tensor2np(mask)
for m in masks:
_mask = Image.fromarray(m).convert("L")
_image = Image.new("RGBA", _mask.size, color='white')
_image = Image.composite(
_image, Image.new("RGBA", _mask.size, color='black'), _mask)
return _image
# 图像融合
class blendImage:
+52 -9
View File
@@ -1,16 +1,19 @@
import time, os, psutil
import comfy.utils
import comfy.sd
import comfy.controlnet
import folder_paths
from nodes import NODE_CLASS_MAPPINGS
from collections import defaultdict
from ..log import log_node_info, log_node_error
stable_diffusion_loaders = ["easy a1111Loader", "easy comfyLoader", "easy zero123Loader", "easy svdLoader"]
stable_diffusion_loaders = ["easy fullLoader", "easy a1111Loader", "easy comfyLoader", "easy zero123Loader", "easy svdLoader"]
stable_cascade_loaders = ["easy cascadeLoader"]
controlnet_loaders = ["easy controlnetLoader", "easy controlnetLoaderADV"]
instant_loaders = ["easy instantIDApply", "easy instantIDApplyADV"]
cascade_vae_node = ["easy preSamplingCascade", "easy fullCascadeKSampler"]
model_merge_node = ["easy XYInputs: ModelMergeBlocks"]
lora_widget = ["easy a1111Loader", "easy comfyLoader"]
lora_widget = ["easy fullLoader", "easy a1111Loader", "easy comfyLoader"]
class easyLoader:
def __init__(self):
@@ -22,8 +25,9 @@ class easyLoader:
"bvae": defaultdict(tuple),
"vae": defaultdict(object),
"lora": defaultdict(dict), # {lora_name: {UID: (model_lora, clip_lora)}}
"controlnet": defaultdict(dict),
}
self.memory_threshold = self.determine_memory_threshold(0.7)
self.memory_threshold = self.determine_memory_threshold(0.9)
self.lora_name_cache = []
def clean_values(self, values: str):
@@ -50,9 +54,17 @@ class easyLoader:
for key in keys - desired_names:
del self.loaded_objects[object_type][key]
def get_input_value(self, entry, key):
def get_input_value(self, entry, key, prompt=None):
val = entry["inputs"][key]
return val if isinstance(val, str) else val[0]
if isinstance(val, str):
return val
elif isinstance(val, list):
if prompt is not None and val[0]:
return prompt[val[0]]['inputs'][key]
else:
return val[0]
else:
return str(val)
def process_pipe_loader(self, entry, desired_ckpt_names, desired_vae_names, desired_lora_names, desired_lora_settings, num_loras=3, suffix=""):
for idx in range(1, num_loras + 1):
@@ -71,10 +83,10 @@ class easyLoader:
desired_vae_names = set()
desired_lora_names = set()
desired_lora_settings = set()
desired_controlnet_names = set()
for entry in prompt.values():
class_type = entry["class_type"]
if class_type in lora_widget:
lora_name = self.get_input_value(entry, "lora_name")
desired_lora_names.add(lora_name)
@@ -82,7 +94,7 @@ class easyLoader:
desired_lora_settings.add(setting)
if class_type in stable_diffusion_loaders:
desired_ckpt_names.add(self.get_input_value(entry, "ckpt_name"))
desired_ckpt_names.add(self.get_input_value(entry, "ckpt_name", prompt))
desired_vae_names.add(self.get_input_value(entry, "vae_name"))
elif class_type in stable_cascade_loaders:
@@ -99,6 +111,16 @@ class easyLoader:
if decode_vae_name and decode_vae_name != 'None':
desired_vae_names.add(decode_vae_name)
elif class_type in controlnet_loaders:
control_net_name = self.get_input_value(entry, "control_net_name", prompt)
scale_soft_weights = self.get_input_value(entry, "scale_soft_weights")
desired_controlnet_names.add(f'{control_net_name};{scale_soft_weights}')
elif class_type in instant_loaders:
control_net_name = self.get_input_value(entry, "control_net_name", prompt)
scale_soft_weights = self.get_input_value(entry, "cn_soft_weights")
desired_controlnet_names.add(f'{control_net_name};{scale_soft_weights}')
elif class_type in model_merge_node:
desired_ckpt_names.add(self.get_input_value(entry, "ckpt_name_1"))
desired_ckpt_names.add(self.get_input_value(entry, "ckpt_name_2"))
@@ -106,7 +128,7 @@ class easyLoader:
if vae_use != 'Use Model 1' and vae_use != 'Use Model 2':
desired_vae_names.add(vae_use)
object_types = ["ckpt", "unet", "clip", "bvae", "vae", "lora"]
object_types = ["ckpt", "unet", "clip", "bvae", "vae", "lora", "controlnet"]
for object_type in object_types:
if object_type == 'unet':
desired_names = desired_unet_names
@@ -117,6 +139,8 @@ class easyLoader:
desired_names = desired_ckpt_names
elif object_type == "vae":
desired_names = desired_vae_names
elif object_type == "controlnet":
desired_names = desired_controlnet_names
else:
desired_names = desired_lora_names
self.clear_unused_objects(desired_names, object_type)
@@ -155,7 +179,7 @@ class easyLoader:
current_memory = self.get_memory_usage()
if current_memory < self.memory_threshold:
return
eviction_order = ["vae", "lora", "bvae", "clip", "ckpt"]
eviction_order = ["vae", "lora", "bvae", "clip", "ckpt", "controlnet"]
for obj_type in eviction_order:
if current_memory < self.memory_threshold:
break
@@ -225,6 +249,25 @@ class easyLoader:
return model
def load_controlnet(self, control_net_name, scale_soft_weights=1):
unique_id = f'{control_net_name};{str(scale_soft_weights)}'
if unique_id in self.loaded_objects["controlnet"]:
return self.loaded_objects["controlnet"][unique_id][0]
if scale_soft_weights < 1:
if "ScaledSoftControlNetWeights" in NODE_CLASS_MAPPINGS:
soft_weight_cls = NODE_CLASS_MAPPINGS['ScaledSoftControlNetWeights']
(weights, timestep_keyframe) = soft_weight_cls().load_weights(scale_soft_weights, False)
cn_adv_cls = NODE_CLASS_MAPPINGS['ControlNetLoaderAdvanced']
control_net, = cn_adv_cls().load_controlnet(control_net_name, timestep_keyframe)
else:
raise Exception(
f"[Advanced-ControlNet Not Found] you need to install 'COMFYUI-Advanced-ControlNet'")
else:
controlnet_path = folder_paths.get_full_path("controlnet", control_net_name)
control_net = comfy.controlnet.load_controlnet(controlnet_path)
self.add_to_cache("controlnet", unique_id, control_net)
self.eviction_based_on_memory()
return control_net
def load_clip(self, clip_name, type='stable_diffusion'):
if type == 'stable_diffusion':
clip_type = comfy.sd.CLIPType.STABLE_DIFFUSION
+51
View File
@@ -47,6 +47,14 @@ class easySampler:
parts.append('None')
return parts
def add_model_patch_option(self, model):
if 'transformer_options' not in model.model_options:
model.model_options['transformer_options'] = {}
to = model.model_options['transformer_options']
if "model_patch" not in to:
to["model_patch"] = {}
return to
def common_ksampler(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent, denoise=1.0,
disable_noise=False, start_step=None, last_step=None, force_full_denoise=False,
preview_latent=True, disable_pbar=False, custom=None):
@@ -91,6 +99,26 @@ class easySampler:
batch_inds = latent["batch_index"] if "batch_index" in latent else None
noise = comfy.sample.prepare_noise(latent_image, seed, batch_inds)
#######################################################################################
# brushnet
transformer_options = model.model_options['transformer_options'] if "transformer_options" in model.model_options else {}
if 'model_patch' in transformer_options and 'brushnet' in transformer_options['model_patch']:
to = self.add_model_patch_option(model)
mp = to['model_patch']
if isinstance(model.model.model_config, comfy.supported_models.SD15):
mp['SDXL'] = False
elif isinstance(model.model.model_config, comfy.supported_models.SDXL):
mp['SDXL'] = True
else:
print('Base model type: ', type(model.model.model_config))
raise Exception("Unsupported model type: ", type(model.model.model_config))
mp['unet'] = model.model.diffusion_model
mp['step'] = 0
mp['total_steps'] = 1
#
#######################################################################################
samples = comfy.sample.sample(model, noise, steps, cfg, sampler_name, scheduler, positive, negative,
latent_image,
denoise=denoise, disable_noise=disable_noise, start_step=start_step,
@@ -129,8 +157,31 @@ class easySampler:
pbar = comfy.utils.ProgressBar(steps)
#######################################################################################
# brushnet
to = None
transformer_options = model.model_options['transformer_options'] if "transformer_options" in model.model_options else {}
if 'model_patch' in transformer_options and 'brushnet_model' in transformer_options['model_patch']:
to = self.add_model_patch_option(model)
mp = to['model_patch']
if isinstance(model.model.model_config, comfy.supported_models.SD15):
mp['SDXL'] = False
elif isinstance(model.model.model_config, comfy.supported_models.SDXL):
mp['SDXL'] = True
else:
print('Base model type: ', type(model.model.model_config))
raise Exception("Unsupported model type: ", type(model.model.model_config))
mp['unet'] = model.model.diffusion_model
mp['step'] = 0
mp['total_steps'] = 1
#
#######################################################################################
def callback(step, x0, x, total_steps):
preview_bytes = None
if to is not None and "model_patch" in to:
to['model_patch']['step'] = step + 1
if previewer:
preview_bytes = previewer.decode_latent_to_preview_image(preview_format, x0)
pbar.update_absolute(step + 1, total_steps, preview_bytes)
+5 -15
View File
@@ -21,6 +21,8 @@ def get_comfyui_revision():
import sys
import importlib.util
import importlib.metadata
import comfy.model_management as mm
import gc
from packaging import version
from server import PromptServer
def is_package_installed(package):
@@ -81,16 +83,6 @@ def find_tags(string: str, sep="/") -> list[str]:
return string.split(sep)[:-1]
return []
import folder_paths
def add_folder_path_and_extensions(folder_name, full_folder_paths, extensions):
for full_folder_path in full_folder_paths:
folder_paths.add_model_folder_path(folder_name, full_folder_path)
if folder_name in folder_paths.folder_names_and_paths:
current_paths, current_extensions = folder_paths.folder_names_and_paths[folder_name]
updated_extensions = current_extensions | extensions
folder_paths.folder_names_and_paths[folder_name] = (current_paths, updated_extensions)
else:
folder_paths.folder_names_and_paths[folder_name] = (full_folder_paths, extensions)
from comfy.model_base import BaseModel
import comfy.supported_models
@@ -259,8 +251,6 @@ def getMetadata(filepath):
return header
def cleanGPUUsedForce():
import torch.cuda
import comfy.model_management
if torch.cuda.is_available():
torch.cuda.empty_cache()
comfy.model_management.unload_all_models()
gc.collect()
mm.unload_all_models()
mm.soft_empty_cache()
+15
View File
@@ -0,0 +1,15 @@
[project]
name = "comfyui-easy-use"
description = "To enhance the usability of ComfyUI, optimizations and integrations have been implemented for several commonly used nodes."
version = "1.1.7"
license = "LICENSE"
dependencies = ["diffusers>=0.25.0", "clip_interrogator>=0.6.0", "onnxruntime", "aiohttp"]
[project.urls]
Repository = "https://github.com/yolain/ComfyUI-Easy-Use"
# Used by Comfy Registry https://comfyregistry.org
[tool.comfy]
PublisherId = "yolain"
DisplayName = "ComfyUI-Easy-Use"
Icon = ""
+5
View File
@@ -27,4 +27,9 @@
}
.easyuse-chooser-dialog-images img.selected{
border: 4px solid var(--success-color);
}
.easyuse-chooser-hidden{
display: none;
height:0;
}
+19
View File
@@ -10,4 +10,23 @@
background-color: var(--comfy-menu-bg);
padding: 10px 4px;
border: 1px solid var(--border-color);z-index: 999999999;padding-top: 0;
}
#easyuse_groups_map .icon{
width: 12px;
height:12px;
}
#easyuse_groups_map .closeBtn{
float: right;
color: var(--input-text);
border-radius:30px;
background-color: var(--comfy-input-bg);
border: 1px solid var(--border-color);
cursor: pointer;
aspect-ratio: 1 / 1;
display: flex;
justify-content: center;
align-items: center;
}
#easyuse_groups_map .closeBtn:hover{
filter:brightness(120%);
}
+70 -17
View File
@@ -1,7 +1,7 @@
.easyuse-toolbar{
background: rgba(35,35,35,.5);
background: rgba(15,15,15,.5);
backdrop-filter: blur(4px) brightness(120%);
border-radius:0 15px 15px 0;
border-radius:0 12px 12px 0;
min-width:50px;
height:24px;
position: fixed;
@@ -67,18 +67,63 @@
.easyuse-guide-dialog{
min-width: 600px;
max-width: 300px;
font-family: var(--font-family);
position: absolute;
z-index:100;
left:0;
bottom:140px;
background: rgba(25,25,25,.85);
backdrop-filter: blur(8px) brightness(120%);
border-radius:0 12px 12px 0;
padding:10px;
transition: .5s all ease-in-out;
visibility: visible;
opacity: 1;
transform: translateX(0%);
}
.easyuse-guide-dialog.disable-render-info{
bottom:110px;
}
.easyuse-guide-dialog-top{
display: flex;
justify-content: space-between;
align-items: center;
}
.easyuse-guide-dialog-top .icon{
width: 12px;
height:12px;
}
.easyuse-guide-dialog.hidden{
opacity: 0;
transform: translateX(-50%);
visibility: hidden;
}
.easyuse-guide-dialog .closeBtn{
float: right;
color: var(--input-text);
border-radius:30px;
background-color: var(--comfy-input-bg);
border: 1px solid var(--border-color);
cursor: pointer;
aspect-ratio: 1 / 1;
display: flex;
justify-content: center;
align-items: center;
}
.easyuse-guide-dialog .closeBtn:hover{
filter:brightness(120%);
}
.easyuse-guide-dialog-title{
color:var(--input-text);
font-size: 20px;
font-size: 16px;
font-weight: bold;
margin-bottom: 5px;
}
.easyuse-guide-dialog-remark{
color: var(--input-text);
font-size: 14px;
font-size: 12px;
margin-top: 5px;
}
.easyuse-guide-dialog-content{
max-height: 600px;
@@ -91,7 +136,6 @@
.easyuse-guide-dialog-note{
margin-top: 20px;
color:white;
font-size: 14px;
}
.easyuse-guide-dialog p{
margin:4px 0;
@@ -99,19 +143,31 @@
font-weight: 300;
}
.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 {
margin-top: 24px;
margin-bottom: 16px;
margin-top: 12px;
margin-bottom: 8px;
font-weight: 600;
line-height: 1.25;
padding-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid var(--border-color);
color: var(--input-text);
}
.markdown-body h1{
font-size: 18px;
}
.markdown-body h2{
font-size: 16px;
}
.markdown-body h3{
font-size: 14px;
}
.markdown-body h4{
font-size: 13px;
}
.markdown-body table {
display: block;
width: 100%;
width: max-content;
max-width: 100%;
/*width: 100%;*/
/*width: max-content;*/
max-width: 300px;
overflow: auto;
color:var(--input-text);
box-sizing: border-box;
@@ -121,7 +177,7 @@
}
.markdown-body table th, .markdown-body table td {
padding: 6px 13px;
font-size: 14px;
font-size: 12px;
margin:0;
border-right: 1px solid var(--border-color);
border-bottom: 1px solid var(--border-color);
@@ -138,14 +194,11 @@
.markdown-body table th{
font-weight: bold;
width: auto;
min-width: 100px;
min-width: 70px;
}
.markdown-body table th:last-child{
width:100%;
}
.markdown-body a{
margin-right: 10px;
}
.markdown-body .warning{
color:var(--warning-color)
}
+1
View File
@@ -26,6 +26,7 @@ const zhCN = {
"Trained Words": "训练词",
"BaseModel": "基础算法",
"Details": "详情",
"Description": "描述",
"Download": "下载量",
"Source": "来源",
"Saving Preview...": "正在保存预览图...",
+2 -1
View File
@@ -1,4 +1,5 @@
export const quesitonIcon = `<svg t="1714564780771" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1489" width="200" height="200" data-spm-anchor-id="a313x.search_index.0.i2.5a663a81pw6qup"><path d="M514.048 54.272q95.232 0 178.688 36.352t145.92 98.304 98.304 145.408 35.84 178.688-35.84 178.176-98.304 145.408-145.92 98.304-178.688 35.84-178.176-35.84-145.408-98.304-98.304-145.408-35.84-178.176 35.84-178.688 98.304-145.408 145.408-98.304 178.176-36.352zM515.072 826.368q26.624 0 44.544-17.92t17.92-43.52q0-26.624-17.92-44.544t-44.544-17.92-44.544 17.92-17.92 44.544q0 25.6 17.92 43.52t44.544 17.92zM567.296 574.464q-1.024-16.384 20.48-34.816t48.128-40.96 49.152-50.688 24.576-65.024q2.048-39.936-8.192-74.752t-33.792-59.904-60.928-39.936-87.552-14.848q-62.464 0-103.936 22.016t-67.072 53.248-35.84 64.512-9.216 55.808q1.024 26.624 16.896 38.912t34.304 12.8 33.792-10.24 15.36-31.232q0-12.288 7.68-30.208t20.992-34.304 32.256-27.648 42.496-11.264q46.08 0 73.728 23.04t25.6 57.856q0 17.408-10.24 32.256t-26.112 28.672-33.792 27.648-33.792 28.672-26.624 32.256-11.776 37.888l1.024 38.912q0 15.36 14.336 29.184t37.888 14.848q23.552-1.024 37.376-15.36t12.8-32.768l0-24.576z" p-id="1490" fill="currentColor"></path></svg>`
export const rocketIcon = `<svg t="1714565020764" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7999" width="200" height="200"><path d="M810.438503 379.664884l-71.187166-12.777183C737.426025 180.705882 542.117647 14.602496 532.991087 7.301248c-12.777184-10.951872-32.855615-10.951872-47.45811 0-9.12656 7.301248-204.434938 175.229947-206.26025 359.586453l-67.536542 10.951871c-18.253119 3.650624-31.030303 18.253119-31.030303 36.506239v189.832442c0 10.951872 5.475936 21.903743 12.777184 27.379679 7.301248 5.475936 14.602496 9.12656 23.729055 9.12656h5.475936l133.247772-23.729055c40.156863 47.458111 91.265597 73.012478 151.500891 73.012477 60.235294 0 111.344029-27.379679 151.500891-74.837789l136.898396 23.729055h5.475936c9.12656 0 16.427807-3.650624 23.729055-9.12656 9.12656-7.301248 12.777184-16.427807 12.777184-27.379679V412.520499c1.825312-14.602496-10.951872-29.204991-27.379679-32.855615zM620.606061 766.631016H401.568627c-20.078431 0-36.506239 16.427807-36.506238 36.506239v109.518716c0 14.602496 9.12656 29.204991 23.729055 34.680927 14.602496 5.475936 31.030303 1.825312 40.156863-9.126559l16.427807-18.25312 32.855615 80.313726c5.475936 14.602496 18.253119 23.729055 34.680927 23.729055 16.427807 0 27.379679-9.12656 34.680927-23.729055l32.855615-80.313726 16.427807 18.25312c10.951872 10.951872 25.554367 14.602496 40.156863 9.126559 14.602496-5.475936 23.729055-18.253119 23.729055-34.680927v-109.518716c-3.650624-20.078431-20.078431-36.506239-40.156862-36.506239z" fill="currentColor" p-id="8000"></path></svg>`
export const groupIcon = `<svg t="1714565543756" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="22538" width="200" height="200"><path d="M871.616 64H152.384c-31.488 0-60.416 25.28-60.416 58.24v779.52c0 32.896 26.24 58.24 60.352 58.24h719.232c34.112 0 60.352-25.344 60.352-58.24V122.24c0.128-32.96-28.8-58.24-60.288-58.24zM286.272 512c-23.616 0-44.672-20.224-44.672-43.008 0-22.784 20.992-43.008 44.608-43.008 23.616 0 44.608 20.224 44.608 43.008A43.328 43.328 0 0 1 286.272 512z m0-202.496c-23.616 0-44.608-20.224-44.608-43.008 0-22.784 20.992-43.008 44.608-43.008 23.616 0 44.608 20.224 44.608 43.008a43.456 43.456 0 0 1-44.608 43.008zM737.728 512H435.904c-23.68 0-44.672-20.224-44.672-43.008 0-22.784 20.992-43.008 44.608-43.008h299.264c23.616 0 44.608 20.224 44.608 43.008a42.752 42.752 0 0 1-41.984 43.008z m0-202.496H435.904c-23.616 0-44.608-20.224-44.608-43.008 0-22.784 20.992-43.008 44.608-43.008h299.264c23.616 0 44.608 20.224 44.608 43.008a42.88 42.88 0 0 1-42.048 43.008z" p-id="22539" fill="currentColor"></path></svg>`
export const rebootIcon = `<svg t="1714568501931" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4275" width="200" height="200"><path d="M511.721751 0.000278a511.999861 511.999861 0 1 0 512.277971 511.721751A511.721751 511.721751 0 0 0 511.721751 0.000278zM184.386696 511.722029A36.988583 36.988583 0 0 1 222.487718 475.011556h92.888622a36.710473 36.710473 0 0 1 0 73.420947H222.487718a36.710473 36.710473 0 0 1-38.101022-36.710474z m201.351385 158.522499l-65.911986 65.911987a36.988583 36.988583 0 0 1-62.852781-25.864197 38.101022 38.101022 0 0 1 10.846276-26.142307L333.731577 618.238024a36.710473 36.710473 0 1 1 52.006504 52.006504z m29.201513-256.138985a36.710473 36.710473 0 0 1-52.006504 0l-65.633877-65.633877a36.988583 36.988583 0 0 1 26.142307-62.85278 36.154254 36.154254 0 0 1 25.864197 10.846276L414.939594 361.54282a36.988583 36.988583 0 0 1 0 52.562723z m135.439398 373.779366a37.266693 37.266693 0 0 1-36.988583 36.988583 36.988583 36.988583 0 0 1-36.710473-36.988583V695.274397a36.988583 36.988583 0 0 1 36.710473-36.988583A37.266693 37.266693 0 0 1 550.378992 695.274397z m0-459.437137a37.266693 37.266693 0 0 1-36.988583 36.988583 36.988583 36.988583 0 0 1-36.710473-36.988583V235.559149a36.988583 36.988583 0 0 1 36.710473-36.988583 37.544802 37.544802 0 0 1 36.988583 36.988583z m63.965219 15.85225L679.978088 278.109926a36.710473 36.710473 0 0 1 52.006504 51.728394L667.463154 396.584635a37.544802 37.544802 0 0 1-52.284614 0 36.988583 36.988583 0 0 1-10.568166-26.142306 36.432364 36.432364 0 0 1 9.733837-26.142307z m122.090135 397.974905a37.544802 37.544802 0 0 1-52.284613 0l-65.355767-65.911986a36.154254 36.154254 0 0 1 0-51.728395 36.710473 36.710473 0 0 1 25.864197-10.846276 35.876145 35.876145 0 0 1 25.864197 10.846276l65.911986 65.633877a36.988583 36.988583 0 0 1 0 52.006504z m66.468206-194.676753h-92.888622a36.710473 36.710473 0 0 1 0-73.420947h92.888622a36.710473 36.710473 0 0 1 0 73.420947z" fill="currentColor" p-id="4276"></path></svg>`
export const rebootIcon = `<svg t="1714568501931" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4275" width="200" height="200"><path d="M511.721751 0.000278a511.999861 511.999861 0 1 0 512.277971 511.721751A511.721751 511.721751 0 0 0 511.721751 0.000278zM184.386696 511.722029A36.988583 36.988583 0 0 1 222.487718 475.011556h92.888622a36.710473 36.710473 0 0 1 0 73.420947H222.487718a36.710473 36.710473 0 0 1-38.101022-36.710474z m201.351385 158.522499l-65.911986 65.911987a36.988583 36.988583 0 0 1-62.852781-25.864197 38.101022 38.101022 0 0 1 10.846276-26.142307L333.731577 618.238024a36.710473 36.710473 0 1 1 52.006504 52.006504z m29.201513-256.138985a36.710473 36.710473 0 0 1-52.006504 0l-65.633877-65.633877a36.988583 36.988583 0 0 1 26.142307-62.85278 36.154254 36.154254 0 0 1 25.864197 10.846276L414.939594 361.54282a36.988583 36.988583 0 0 1 0 52.562723z m135.439398 373.779366a37.266693 37.266693 0 0 1-36.988583 36.988583 36.988583 36.988583 0 0 1-36.710473-36.988583V695.274397a36.988583 36.988583 0 0 1 36.710473-36.988583A37.266693 37.266693 0 0 1 550.378992 695.274397z m0-459.437137a37.266693 37.266693 0 0 1-36.988583 36.988583 36.988583 36.988583 0 0 1-36.710473-36.988583V235.559149a36.988583 36.988583 0 0 1 36.710473-36.988583 37.544802 37.544802 0 0 1 36.988583 36.988583z m63.965219 15.85225L679.978088 278.109926a36.710473 36.710473 0 0 1 52.006504 51.728394L667.463154 396.584635a37.544802 37.544802 0 0 1-52.284614 0 36.988583 36.988583 0 0 1-10.568166-26.142306 36.432364 36.432364 0 0 1 9.733837-26.142307z m122.090135 397.974905a37.544802 37.544802 0 0 1-52.284613 0l-65.355767-65.911986a36.154254 36.154254 0 0 1 0-51.728395 36.710473 36.710473 0 0 1 25.864197-10.846276 35.876145 35.876145 0 0 1 25.864197 10.846276l65.911986 65.633877a36.988583 36.988583 0 0 1 0 52.006504z m66.468206-194.676753h-92.888622a36.710473 36.710473 0 0 1 0-73.420947h92.888622a36.710473 36.710473 0 0 1 0 73.420947z" fill="currentColor" p-id="4276"></path></svg>`
export const closeIcon = `<svg t="1714965640187" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4264" width="200" height="200"><path d="M597.795527 511.488347 813.564755 295.718095c23.833825-23.833825 23.833825-62.47489 0.001023-86.307691-23.832801-23.832801-62.47489-23.833825-86.307691 0L511.487835 425.180656 295.717583 209.410404c-23.833825-23.833825-62.475913-23.833825-86.307691 0-23.832801 23.832801-23.833825 62.47489 0 86.308715l215.769228 215.769228L209.410915 727.258599c-23.833825 23.833825-23.833825 62.47489 0 86.307691 23.832801 23.833825 62.473867 23.833825 86.307691 0l215.768205-215.768205 215.769228 215.769228c23.834848 23.833825 62.475913 23.832801 86.308715 0 23.833825-23.833825 23.833825-62.47489 0-86.307691L597.795527 511.488347z" fill="currentColor" p-id="4265"></path></svg>`
+67 -43
View File
@@ -1,7 +1,7 @@
import { api } from "../../../../scripts/api.js";
import { app } from "../../../../scripts/app.js";
import {deepEqual, addCss, isLocalNetwork} from "../common/utils.js";
import {quesitonIcon, rocketIcon, groupIcon, rebootIcon} from "../common/icon.js";
import {quesitonIcon, rocketIcon, groupIcon, rebootIcon, closeIcon} from "../common/icon.js";
import {$t} from '../common/i18n.js';
import {toast} from "../common/toast.js";
import {$el, ComfyDialog} from "../../../../scripts/ui.js";
@@ -29,12 +29,15 @@ api.addEventListener("easyuse-toast",event=>{
let draggerEl = null
let isGroupMapcanMove = true
function createGroupMap(){
let div = document.querySelector('#easyuse_groups_map')
if(div){
div.style.display = div.style.display == 'none' ? 'flex' : 'none'
return
}
let groups = app.canvas.graph._groups
let nodes = app.canvas.graph._nodes
let old_nodes = groups.length
let div =
document.querySelector('#easyuse_groups_map') ||
document.createElement('div')
div = document.createElement('div')
div.id = 'easyuse_groups_map'
div.innerHTML = ''
let btn = document.createElement('div')
@@ -44,18 +47,15 @@ function createGroupMap(){
align-items: center;
padding: 0 6px;
height: 44px;`
let hideBtn = document.createElement('button')
let hideBtn = $el('button.closeBtn',{
innerHTML:closeIcon,
onclick:_=>div.style.display = 'none'
})
let textB = document.createElement('p')
btn.appendChild(textB)
btn.appendChild(hideBtn)
textB.style.fontSize = '11px'
textB.innerHTML = `<b>${$t('Groups Map')} (EasyUse)</b>`
hideBtn.style = `float: right;color: var(--input-text);border-radius:6px;font-size:9px;
background-color: var(--comfy-input-bg); border: 1px solid var(--border-color);cursor: pointer;padding: 5px;aspect-ratio: 1 / 1;`
hideBtn.addEventListener('click', () => {
div.style.display = 'none'
})
hideBtn.innerText = '❌'
div.appendChild(btn)
div.addEventListener('mousedown', function (e) {
@@ -301,10 +301,12 @@ function download_model(url,local_dir){
})
}
class GuideDialog extends ComfyDialog {
show(note, need_models){
class GuideDialog {
constructor(note, need_models){
this.dialogDiv = null
this.modelsDiv = null
let modelsDiv = null
if(need_models?.length>0){
let tbody = []
@@ -313,18 +315,17 @@ class GuideDialog extends ComfyDialog {
$el('td',{innerHTML:need_models[i].title || need_models[i].name || ''}),
$el('td',[
need_models[i]['download_url'] ? $el('a',{onclick:_=>download_model(need_models[i]['download_url'],need_models[i]['local_dir']), target:"_blank", textContent:$t('Download Model')}) : '',
need_models[i]['source_url'] ? $el('a',{href:need_models[i]['source_url'], target:"_blank", textContent:$t('Source Url')}) : '',
need_models[i]['source_url'] ? $el('a',{href:need_models[i]['source_url'], target:"_blank", textContent:$t('Source Url')}) : '',
need_models[i]['desciption'] ? $el('span',{textContent:need_models[i]['desciption']}) : '',
]),
$el('td',{innerHTML:need_models[i].description || ''}),
]))
}
modelsDiv = $el('div.easyuse-guide-dialog-models.markdown-body',[
this.modelsDiv = $el('div.easyuse-guide-dialog-models.markdown-body',[
$el('h3',{textContent:$t('Models Required')}),
$el('table',{cellpadding:0,cellspacing:0},[
$el('thead',[
$el('tr',[
$el('th',{innerHTML:$t('ModelName')}),
$el('th',{innerHTML:$t('Details')}),
$el('th',{innerHTML:$t('Description')}),
])
]),
@@ -333,36 +334,61 @@ class GuideDialog extends ComfyDialog {
])
}
super.show(
$el('div.easyuse-guide-dialog',[
$el('div.easyuse-guide-dialog-header',[
$el('div.easyuse-guide-dialog-title',{
this.dialogDiv = $el('div.easyuse-guide-dialog.hidden',[
$el('div.easyuse-guide-dialog-header',[
$el('div.easyuse-guide-dialog-top',[
$el('div.easyuse-guide-dialog-title',{
innerHTML:$t('Workflow Guide')
}),
$el('div.easyuse-guide-dialog-remark',{
innerHTML:`${$t('Workflow created by')} <a href="https://github.com/yolain/" target="_blank">Yolain</a> , ${$t('Watch more video content')} <a href="https://space.bilibili.com/1840885116" target="_blank">B站乱乱呀</a>`
})
]),
$el('div.easyuse-guide-dialog-content.markdown-body',[
$el('div.easyuse-guide-dialog-note',{
innerHTML:note
}),
modelsDiv
])
}),
$el('button.closeBtn',{innerHTML:closeIcon,onclick:_=>this.close()})
]),
$el('div.easyuse-guide-dialog-remark',{
innerHTML:`${$t('Workflow created by')} <a href="https://github.com/yolain/" target="_blank">Yolain</a> , ${$t('Watch more video content')} <a href="https://space.bilibili.com/1840885116" target="_blank">B站乱乱呀</a>`
})
]),
$el('div.easyuse-guide-dialog-content.markdown-body',[
$el('div.easyuse-guide-dialog-note',{
innerHTML:note
}),
...this.modelsDiv ? [this.modelsDiv] : []
])
)
])
if(disableRenderInfo){
this.dialogDiv.classList.add('disable-render-info')
}
document.body.appendChild(this.dialogDiv)
}
show(){
if(this.dialogDiv) this.dialogDiv.classList.remove('hidden')
}
close(){
guideDialog = null
super.close()
if(this.dialogDiv){
this.dialogDiv.classList.add('hidden')
}
}
toggle(){
if(this.dialogDiv){
if(this.dialogDiv.classList.contains('hidden')){
this.show()
}else{
this.close()
}
}
}
remove(){
if(this.dialogDiv) document.body.removeChild(this.dialogDiv)
}
}
const getEnableToolBar = _ => app.ui.settings.getSettingValue(toolBarId, true)
const toolBarId = "Comfy.EasyUse.toolBar"
let enableToolBar = true
let enableToolBar = getEnableToolBar()
let disableRenderInfo = localStorage['Comfy.Settings.Comfy.EasyUse.disableRenderInfo'] ? true : false
export function addToolBar(app) {
app.ui.settings.addSetting({
@@ -378,7 +404,6 @@ export function addToolBar(app) {
},
});
}
const getEnableToolBar = _ => app.ui.settings.getSettingValue(toolBarId, true)
let note = null
let toolbar = null
@@ -469,16 +494,15 @@ app.registerExtension({
// }
if(data?.extra?.note){
if(guideDialog) {
guideDialog.close()
guideDialog.remove()
guideDialog = null
}
if(note && toolbar) toolbar.removeChild(note)
const need_models = data.extra?.need_models || null
guideDialog = new GuideDialog(data.extra.note, need_models)
note = $el('div.easyuse-toolbar-item',{
onclick:async()=>{
if(guideDialog) return
guideDialog = new GuideDialog()
const need_models = data.extra?.need_models || null
guideDialog.show(data.extra.note, need_models)
guideDialog.toggle()
}
},[
$el('div.easyuse-toolbar-icon.question',{innerHTML:quesitonIcon}),
+26 -10
View File
@@ -111,16 +111,31 @@ function widgetLogic(node, widget) {
updateNodeHeight(node)
}
if (widget.name === 'mode') {
let number_to_show = findWidgetByName(node, 'num_loras').value + 1
for (let i = 0; i < number_to_show; i++) {
if (widget.value === "simple") {
toggleWidget(node, findWidgetByName(node, 'lora_'+i+'_strength'), true)
toggleWidget(node, findWidgetByName(node, 'lora_'+i+'_model_strength'))
toggleWidget(node, findWidgetByName(node, 'lora_'+i+'_clip_strength'))
} else {
toggleWidget(node, findWidgetByName(node, 'lora_'+i+'_strength'))
toggleWidget(node, findWidgetByName(node, 'lora_'+i+'_model_strength'), true)
toggleWidget(node, findWidgetByName(node, 'lora_'+i+'_clip_strength'), true)}
switch (node.comfyClass) {
case 'easy loraStack':
let number_to_show = findWidgetByName(node, 'num_loras').value + 1
for (let i = 0; i < number_to_show; i++) {
if (widget.value === "simple") {
toggleWidget(node, findWidgetByName(node, 'lora_'+i+'_strength'), true)
toggleWidget(node, findWidgetByName(node, 'lora_'+i+'_model_strength'))
toggleWidget(node, findWidgetByName(node, 'lora_'+i+'_clip_strength'))
} else {
toggleWidget(node, findWidgetByName(node, 'lora_'+i+'_strength'))
toggleWidget(node, findWidgetByName(node, 'lora_'+i+'_model_strength'), true)
toggleWidget(node, findWidgetByName(node, 'lora_'+i+'_clip_strength'), true)}
}
break
case 'easy icLightApply':
if (widget.value === "Foreground") {
toggleWidget(node, findWidgetByName(node, 'lighting'), true)
toggleWidget(node, findWidgetByName(node, 'remove_bg'), true)
toggleWidget(node, findWidgetByName(node, 'source'))
} else {
toggleWidget(node, findWidgetByName(node, 'lighting'))
toggleWidget(node, findWidgetByName(node, 'source'), true)
toggleWidget(node, findWidgetByName(node, 'remove_bg'))
}
break
}
updateNodeHeight(node)
}
@@ -590,6 +605,7 @@ app.registerExtension({
case "easy rangeFloat":
case 'easy latentCompositeMaskedWithCond':
case 'easy pipeEdit':
case 'easy icLightApply':
case 'easy ipadapterApply':
case 'easy ipadapterApplyADV':
case 'easy ipadapterApplyEncoder':
+3
View File
@@ -12,6 +12,7 @@ const customPipeLineLink = "#7737AA"
const customPipeLineSDXLLink = "#7737AA"
const customIntLink = "#29699C"
const customXYPlotLink = "#74DA5D"
const customLoraStackLink = "#94dccd"
const customXYLink = "#38291f"
var customLinkColors = JSON.parse(localStorage.getItem('Comfy.Settings.ttN.customLinkColors')) || {};
@@ -20,6 +21,8 @@ if (!customLinkColors["PIPE_LINE_SDXL"] || !LGraphCanvas.link_type_colors["PIPE_
if (!customLinkColors["INT"] || !LGraphCanvas.link_type_colors["INT"]) {customLinkColors["INT"] = customIntLink;}
if (!customLinkColors["XYPLOT"] || !LGraphCanvas.link_type_colors["XYPLOT"]) {customLinkColors["XYPLOT"] = customXYPlotLink;}
if (!customLinkColors["X_Y"] || !LGraphCanvas.link_type_colors["X_Y"]) {customLinkColors["X_Y"] = customXYLink;}
if (!customLinkColors["LORA_STACK"] || !LGraphCanvas.link_type_colors["LORA_STACK"]) {customLinkColors["LORA_STACK"] = customLoraStackLink;}
if (!customLinkColors["CONTROL_NET_STACK"] || !LGraphCanvas.link_type_colors["CONTROL_NET_STACK"]) {customLinkColors["CONTROL_NET_STACK"] = customLoraStackLink;}
localStorage.setItem('Comfy.Settings.easyUse.customLinkColors', JSON.stringify(customLinkColors));
+4 -4
View File
@@ -63,10 +63,10 @@ app.registerExtension({
selector.element.children[0].innerHTML = ''
if(method_values == 'selfie_multiclass_256x256'){
toggleWidget(this, findWidgetByName(this, 'confidence'), true)
this.setSize([300, 200]);
this.setSize([300, 260]);
}else{
toggleWidget(this, findWidgetByName(this, 'confidence'))
this.setSize([300, 400]);
this.setSize([300, 500]);
}
let list = getTagList(tags[method_values]);
selector.element.children[0].append(...list)
@@ -122,10 +122,10 @@ app.registerExtension({
}
if(method_values == 'selfie_multiclass_256x256'){
toggleWidget(this, findWidgetByName(this, 'confidence'), true)
this.setSize([300, 200]);
this.setSize([300, 260]);
}else{
toggleWidget(this, findWidgetByName(this, 'confidence'))
this.setSize([300, 420]);
this.setSize([300, 500]);
}
},1)
+5 -4
View File
@@ -92,10 +92,11 @@ async function displayImage(imgName, styleName) {
img.src = empty_img
}
}
var x = e.pageX-pxy.x-100;
var y = e.pageY-pxy.y+25;
img.style.left = x+"px";
img.style.top = y+"px";
var scale = app?.canvas?.ds?.scale || 1;
var x = (e.pageX-pxy.x-100)/scale;
var y = (e.pageY-pxy.y+25)/scale;
img.style.left = x+"px";
img.style.top = y+"px";
img.style.display = "block";
img.style.borderRadius = "10px";
img.style.borderColor = "var(--fg-color)"
+26 -3
View File
@@ -47,7 +47,7 @@ class chooserImageDialog extends ComfyDialog {
createButtons() {
const btns = super.createButtons();
btns[0].onclick = _ => {
cancelButtonPressed()
if (FlowState.running()) { send_cancel();}
super.close()
}
btns.unshift($el('button', {
@@ -72,6 +72,10 @@ class chooserImageDialog extends ComfyDialog {
function progressButtonPressed() {
const node = app.graph._nodes_by_id[this.node_id];
if (node) {
const selected = [...node.selected]
if(selected?.length>0){
node.setProperty('values',selected)
}
if (FlowState.paused()) {
send_message(node.id, [...node.selected, -1, ...node.anti_selected]);
}
@@ -79,10 +83,26 @@ function progressButtonPressed() {
skip_next_restart_message();
restart_from_here(node.id).then(() => { send_message(node.id, [...node.selected, -1, ...node.anti_selected]); });
}
const maxlength = node.imgs.length;
if (FlowState.paused_here(node.id) && selected>0) {
node.send_button_widget.name = (selected>1) ? "Progress selected (" + selected + '/' + maxlength +")" : "Progress selected image";
} else if (FlowState.idle() && selected>0) {
node.send_button_widget.name = (selected>1) ? "Progress selected (" + selected + '/' + maxlength +")" : "Progress selected image as restart";
}
else {
node.send_button_widget.name = "";
}
}
}
function cancelButtonPressed() { if (FlowState.running()) { send_cancel(); } }
function cancelButtonPressed() {
if (FlowState.running()) { send_cancel();}
const node = app.graph._nodes_by_id[this.node_id];
if (node) {
node.send_button_widget.name = "";
node.cancel_button_widget.name = "";
}
}
app.registerExtension({
name:'comfy.easyuse.imageChooser',
@@ -126,9 +146,12 @@ app.registerExtension({
},
async nodeCreated(node, app) {
if(node.comfyClass == 'easy imageChooser'){
node.send_button_widget = node.addWidget("button", "", "", progressButtonPressed, {serialize: false});
node.cancel_button_widget = node.addWidget("button", "", "", cancelButtonPressed, {serialize: false});
node.setProperty('values',[])
/* Capture clicks */
const org_onMouseDown = node.onMouseDown;
@@ -178,7 +201,7 @@ app.registerExtension({
if (this.send_button_widget) {
this.send_button_widget.node_id = this.id;
const selection = ( this.selected ? this.selected.size : 0 ) + ( this.anti_selected ? this.anti_selected.size : 0 )
const maxlength = this.imgs.length;
const maxlength = this.imgs?.length || 0;
if (FlowState.paused_here(this.id) && selection>0) {
this.send_button_widget.name = (selection>1) ? "Progress selected (" + selection + '/' + maxlength +")" : "Progress selected image";
} else if (FlowState.idle() && selection>0) {