diff --git a/README.en.md b/README.en.md index 798b9e5..e373e18 100644 --- a/README.en.md +++ b/README.en.md @@ -45,6 +45,7 @@ Double-click install.bat to install the required dependencies **v1.2.1** +- Added `easy controlnetLoader++` - Added **PLUS (kolors genernal)** preset to `easy ipadapterApply` and `easy ipadapterApplyADV` (Supported kolors ipadapter) - Added `easy kolorsLoader` - Code based on [MinusZoneAI](https://github.com/MinusZoneAI/ComfyUI-Kolors-MZ)'s and [kijai](https://github.com/kijai/ComfyUI-KwaiKolorsWrapper)'s repo, thanks for their contribution. diff --git a/README.md b/README.md index f142f0d..c4ce000 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,12 @@ - 支持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) - 中文提示词自动识别,使用[opus-mt-zh-en模型](https://huggingface.co/Helsinki-NLP/opus-mt-zh-en) -- 支持 sd3 模型 +- 支持 sd3 模型 +- 支持 kolors 模型 ## 安装 -将存储库克隆到 **custom_nodes** 目录并安装依赖 + +1. 将存储库克隆到 **custom_nodes** 目录并安装依赖 ```shell #1. git下载 git clone https://github.com/yolain/ComfyUI-Easy-Use @@ -48,10 +50,12 @@ git clone https://github.com/yolain/ComfyUI-Easy-Use 双击install.bat安装依赖 ``` + ## 更新日志 **v1.2.1** +- 增加 `easy controlnetLoader++` - `easy ipadapterApply` 和 `easy ipadapterApplyADV` 增加 **PLUS (kolors genernal)** 预置项 (支持kolors IPA,可能会改名) - 去除 `easy positive` `easy negative` 等prompt节点的自动将中文翻译功能,自动翻译仅在 `easy a1111Loader` 等不支持中文TE的加载器中生效 - 增加 `easy kolorsLoader` - 可灵加载器,参考了 [MinusZoneAI](https://github.com/MinusZoneAI/ComfyUI-Kolors-MZ) 和 [kijai](https://github.com/kijai/ComfyUI-KwaiKolorsWrapper) 的代码。 diff --git a/py/easyNodes.py b/py/easyNodes.py index 3a0ea0a..29b0723 100644 --- a/py/easyNodes.py +++ b/py/easyNodes.py @@ -29,7 +29,7 @@ from .libs.utils import find_wildcards_seed, is_linked_styles_selector, easySave from .libs.loader import easyLoader from .libs.sampler import easySampler, alignYourStepsScheduler, gitsScheduler from .libs.xyplot import easyXYPlot -from .libs.controlnet import easyControlnet +from .libs.controlnet import easyControlnet, union_controlnet_types from .libs.conditioning import prompt_to_cond, set_cond from .libs.easing import EasingBase from .libs.translate import has_chinese, zh_to_en @@ -2209,8 +2209,6 @@ class loraStack: class controlnetStack: - def get_file_list(filenames): - return [file for file in filenames if file != "put_models_here.txt" and "lllite" not in file] @classmethod def INPUT_TYPES(s): @@ -2227,7 +2225,7 @@ class controlnetStack: } for i in range(1, max_cn_num+1): - inputs["optional"][f"controlnet_{i}"] = (["None"] + s.get_file_list(folder_paths.get_filename_list("controlnet")), {"default": "None"}) + inputs["optional"][f"controlnet_{i}"] = (["None"] + folder_paths.get_filename_list("controlnet"), {"default": "None"}) inputs["optional"][f"controlnet_{i}_strength"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01},) inputs["optional"][f"start_percent_{i}"] = ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001},) inputs["optional"][f"end_percent_{i}"] = ("FLOAT",{"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},) @@ -2270,14 +2268,12 @@ class controlnetStack: class controlnetSimple: @classmethod def INPUT_TYPES(s): - def get_file_list(filenames): - return [file for file in filenames if file != "put_models_here.txt" and "lllite" not in file] return { "required": { "pipe": ("PIPE_LINE",), "image": ("IMAGE",), - "control_net_name": (get_file_list(folder_paths.get_filename_list("controlnet")),), + "control_net_name": (folder_paths.get_filename_list("controlnet"),), }, "optional": { "control_net": ("CONTROL_NET",), @@ -2293,9 +2289,9 @@ class controlnetSimple: FUNCTION = "controlnetApply" CATEGORY = "EasyUse/Loaders" - def controlnetApply(self, pipe, image, control_net_name, control_net=None, strength=1, scale_soft_weights=1): + def controlnetApply(self, pipe, image, control_net_name, control_net=None, strength=1, scale_soft_weights=1, union_type=None): - positive, negative = easyControlnet().apply(control_net_name, image, pipe["positive"], pipe["negative"], strength, 0, 1, control_net, scale_soft_weights, None, easyCache) + positive, negative = easyControlnet().apply(control_net_name, image, pipe["positive"], pipe["negative"], strength, 0, 1, control_net, scale_soft_weights, mask=None, easyCache=easyCache) new_pipe = { "model": pipe['model'], @@ -2319,14 +2315,12 @@ class controlnetAdvanced: @classmethod def INPUT_TYPES(s): - def get_file_list(filenames): - return [file for file in filenames if file != "put_models_here.txt" and "lllite" not in file] return { "required": { "pipe": ("PIPE_LINE",), "image": ("IMAGE",), - "control_net_name": (get_file_list(folder_paths.get_filename_list("controlnet")),), + "control_net_name": (folder_paths.get_filename_list("controlnet"),), }, "optional": { "control_net": ("CONTROL_NET",), @@ -2347,7 +2341,71 @@ 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, None, easyCache) + strength, start_percent, end_percent, control_net, scale_soft_weights, union_type=None, mask=None, easyCache=easyCache) + + new_pipe = { + "model": pipe['model'], + "positive": positive, + "negative": negative, + "vae": pipe['vae'], + "clip": pipe['clip'], + + "samples": pipe["samples"], + "images": pipe["images"], + "seed": 0, + + "loader_settings": pipe["loader_settings"] + } + + del pipe + + return (new_pipe, positive, negative) + +# controlnetPlusPlus +class controlnetPlusPlus: + + @classmethod + def INPUT_TYPES(s): + + return { + "required": { + "pipe": ("PIPE_LINE",), + "image": ("IMAGE",), + "control_net_name": (folder_paths.get_filename_list("controlnet"),), + }, + "optional": { + "control_net": ("CONTROL_NET",), + "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "scale_soft_weights": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},), + "union_type": (list(union_controlnet_types.keys()),) + } + } + + RETURN_TYPES = ("PIPE_LINE", "CONDITIONING", "CONDITIONING") + RETURN_NAMES = ("pipe", "positive", "negative") + OUTPUT_NODE = True + + FUNCTION = "controlnetApply" + CATEGORY = "EasyUse/Loaders" + + + def controlnetApply(self, pipe, image, control_net_name, control_net=None, strength=1, start_percent=0, end_percent=1, scale_soft_weights=1, union_type=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['ACN_ControlNet++LoaderSingle'] + control_net, = cn_adv_cls().load_controlnet_plusplus(control_net_name, union_type) + apply_adv_cls = NODE_CLASS_MAPPINGS['ACN_AdvancedControlNetApply'] + positive, negative, _ = apply_adv_cls().controlnetApply(pipe["positive"], pipe["negative"], control_net, image, strength, start_percent, end_percent, timestep_kf=timestep_keyframe,) + else: + raise Exception( + f"[Advanced-ControlNet Not Found] you need to install 'COMFYUI-Advanced-ControlNet'") + else: + positive, negative = easyControlnet().apply(control_net_name, image, pipe["positive"], pipe["negative"], + strength, start_percent, end_percent, control_net, scale_soft_weights, union_type=union_type, mask=None, easyCache=easyCache) new_pipe = { "model": pipe['model'], @@ -3037,9 +3095,8 @@ class ipadapter: # 1. Load the clipvision model if not clip_vision: clipvision_file, clipvision_name = self.get_clipvision_file(preset, node_name) - preset = preset.lower() if clipvision_file is None: - if preset.startswith("plus (kolors"): + if preset.lower().startswith("plus (kolors"): model_url = IPADAPTER_CLIPVISION_MODELS["clip-vit-large-patch14-336"]["model_url"] clipvision_file = get_local_filepath(model_url, IPADAPTER_DIR, "clip-vit-large-patch14-336.bin") else: @@ -3052,7 +3109,7 @@ class ipadapter: log_node_info("easy ipadapterApply", f"Using ClipVisonModel {clipvision_name} Cached") _, clip_vision = backend_cache.cache[clipvision_name][1] else: - if preset.startswith("plus (kolors"): + if preset.lower().startswith("plus (kolors"): from .kolors.loader import load_kolors_clip_vision clip_vision = load_kolors_clip_vision(clipvision_file) else: @@ -7492,6 +7549,7 @@ NODE_CLASS_MAPPINGS = { "easy controlnetStack": controlnetStack, "easy controlnetLoader": controlnetSimple, "easy controlnetLoaderADV": controlnetAdvanced, + "easy controlnetLoader++": controlnetPlusPlus, "easy LLLiteLoader": LLLiteLoader, # Adapter 适配器 "easy ipadapterApply": ipadapterApply, @@ -7610,6 +7668,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "easy controlnetStack": "EasyControlnetStack", "easy controlnetLoader": "EasyControlnet", "easy controlnetLoaderADV": "EasyControlnet (Advanced)", + "easy controlnetLoader++": "EasyControlnet++", "easy LLLiteLoader": "EasyLLLite", # Adapter 适配器 "easy ipadapterApply": "Easy Apply IPAdapter", diff --git a/py/libs/controlnet.py b/py/libs/controlnet.py index 7c41af4..26532f4 100644 --- a/py/libs/controlnet.py +++ b/py/libs/controlnet.py @@ -3,17 +3,28 @@ import comfy.controlnet import comfy.model_management from nodes import NODE_CLASS_MAPPINGS +union_controlnet_types = {"auto": -1, "openpose": 0, "depth": 1, "hed/pidi/scribble/ted": 2, "canny/lineart/anime_lineart/mlsd": 3, "normal": 4, "segment": 5, "tile": 6, "repaint": 7} + class easyControlnet: def __init__(self): pass - def apply(self, control_net_name, image, positive, negative, strength, start_percent=0, end_percent=1, control_net=None, scale_soft_weights=1, mask=None, easyCache=None, use_cache=True): + 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, union_type=None, easyCache=None, use_cache=True): if strength == 0: return (positive, negative) if control_net is None: control_net = easyCache.load_controlnet(control_net_name, scale_soft_weights, use_cache) + # union controlnet + if union_type is not None: + control_net = control_net.copy() + type_number = union_controlnet_types[union_type] + if type_number >= 0: + control_net.set_extra_arg("control_type", [type_number]) + else: + control_net.set_extra_arg("control_type", []) + if mask is not None: mask = mask.to(self.device) diff --git a/web/js/easy/easyExtraMenu.js b/web/js/easy/easyExtraMenu.js index c9366f0..d28e6bf 100644 --- a/web/js/easy/easyExtraMenu.js +++ b/web/js/easy/easyExtraMenu.js @@ -2,10 +2,10 @@ import {app} from "../../../../scripts/app.js"; import {$t} from '../common/i18n.js' import {CheckpointInfoDialog, LoraInfoDialog} from "../common/model.js"; -const loaders = ['easy fullLoader', 'easy a1111Loader', 'easy comfyLoader', 'easy hunyuanDiTLoader', 'easy pixArtLoader'] +const loaders = ['easy fullLoader', 'easy a1111Loader', 'easy comfyLoader', 'easy kolorsLoader', 'easy hunyuanDiTLoader', 'easy pixArtLoader'] const preSampling = ['easy preSampling', 'easy preSamplingAdvanced', 'easy preSamplingDynamicCFG', 'easy preSamplingNoiseIn', 'easy preSamplingCustom', 'easy preSamplingLayerDiffusion', 'easy fullkSampler'] const kSampler = ['easy kSampler', 'easy kSamplerTiled', 'easy kSamplerInpainting', 'easy kSamplerDownscaleUnet', 'easy kSamplerLayerDiffusion'] -const controlnet = ['easy controlnetLoader', 'easy controlnetLoaderADV', 'easy instantIDApply', 'easy instantIDApplyADV'] +const controlnet = ['easy controlnetLoader', 'easy controlnetLoaderADV', 'easy controlnetLoader++', 'easy instantIDApply', 'easy instantIDApplyADV'] const ipadapter = ['easy ipadapterApply', 'easy ipadapterApplyADV', 'easy ipadapterStyleComposition', 'easy ipadapterApplyFromParams', 'easy pulIDApply', 'easy pulIDApplyADV'] const positive_prompt = ['easy positive', 'easy wildcards'] const imageNode = ['easy loadImageBase64', 'LoadImage', 'LoadImageMask']