Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cbc2a2a2b | ||
|
|
63811206c9 | ||
|
|
48a4b5bfc6 | ||
|
|
7980f4eef4 | ||
|
|
b19c8b07ea | ||
|
|
3f506d265c | ||
|
|
d8af918e7d | ||
|
|
33566e8474 | ||
|
|
ea0350c2dc | ||
|
|
24526623fb | ||
|
|
7715ebfd06 | ||
|
|
2ba814c131 | ||
|
|
d5ad332666 | ||
|
|
fee7e7bf73 | ||
|
|
01f17ff02b | ||
|
|
da7120219a | ||
|
|
d616d18069 | ||
|
|
dbf76f288c | ||
|
|
05124006ba | ||
|
|
4586af311c | ||
|
|
1cea58c7cf | ||
|
|
a84f7c4a58 | ||
|
|
1d9bf86560 | ||
|
|
8d352b85bc | ||
|
|
403562575c | ||
|
|
dbe2cd6569 | ||
|
|
b3b0a961c5 | ||
|
|
9dfb8b9c15 | ||
|
|
b4ea58946c | ||
|
|
513fc4b67e |
@@ -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 }}
|
||||
@@ -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`
|
||||
|
||||
@@ -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">
|
||||
[](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
@@ -1,4 +1,4 @@
|
||||
__version__ = "1.1.6"
|
||||
__version__ = "1.1.7"
|
||||
|
||||
import os
|
||||
import glob
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
@@ -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",
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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()
|
||||
@@ -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 = ""
|
||||
@@ -27,4 +27,9 @@
|
||||
}
|
||||
.easyuse-chooser-dialog-images img.selected{
|
||||
border: 4px solid var(--success-color);
|
||||
}
|
||||
|
||||
.easyuse-chooser-hidden{
|
||||
display: none;
|
||||
height:0;
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ const zhCN = {
|
||||
"Trained Words": "训练词",
|
||||
"BaseModel": "基础算法",
|
||||
"Details": "详情",
|
||||
"Description": "描述",
|
||||
"Download": "下载量",
|
||||
"Source": "来源",
|
||||
"Saving Preview...": "正在保存预览图...",
|
||||
|
||||
@@ -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
@@ -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}),
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user