diff --git a/README.en.md b/README.en.md index ab3921b..197a285 100644 --- a/README.en.md +++ b/README.en.md @@ -29,7 +29,7 @@ After installing the node package, the UI interface will be automatically switch ### Stable Cascade -[WorkFlow Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#StableCascade)

+[WorkFlow Example](https://github.com/yolain/ComfyUI-Easy-Use/blob/main/README.en.md#StableCascade)

Currently, txt2img and img2img are supported,Lora and Controlnet are comming soon!

Usage:
@@ -39,24 +39,21 @@ Usage:
## Changelog -**2024-02-29** +**v1.0.9 [2024-3-2]** -- Fixed `easy svdLoader` error when the positive or negative is empty +- Added `easy instantIDApply` - you need installed [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) fisrt, Workflow[Example](https://github.com/yolain/ComfyUI-Easy-Use/blob/main/README.en.md#InstantID) +- Fixed `easy detailerFix` not added to the list of nodes available for saving images formatting extensions +- Fixed `easy XYInputs: PromptSR` errors are reported when replacing negative prompts -**2024-02-28** - -- Fixed the issue that 'easy preSampling' and other similar node, latent could not be generated based on the batch index after passing in - -**2024-02-26** - -- `easy fullLoader` **positive**、**negative**、**latent** added to the output items -- Fixed the error of SDXLClipModel in ComfyUI revision 2016[c2cb8e88] and above (the revision number was judged to be compatible with the old revision) -- Fixed `easy detailerFix` generation error when batch size is greater than 1 - -**v1.0.8(2024-02-25)** +**v1.0.8 (f28cbf7)** - `easy cascadeLoader` stage_c and stage_b support the checkpoint model (Download [checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints) models) - `easy styleSelector` The search box is modified to be case-insensitive +- `easy fullLoader` **positive**、**negative**、**latent** added to the output items +- Fixed the issue that 'easy preSampling' and other similar node, latent could not be generated based on the batch index after passing in +- Fixed `easy svdLoader` error when the positive or negative is empty +- Fixed the error of SDXLClipModel in ComfyUI revision 2016[c2cb8e88] and above (the revision number was judged to be compatible with the old revision) +- Fixed `easy detailerFix` generation error when batch size is greater than 1 - Optimize the code, reduce a lot of redundant code and improve the running speed **v1.0.7 (2024-02-19)** @@ -66,7 +63,7 @@ Usage:
- Added `easy fullCascadeKSampler` - stable cascade stage-c ksampler full - Added `easy cascadeKSampler` - stable cascade stage-c ksampler simple - -- Optimize the image to image[Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#image-to-image) +- Optimize the image to image[Example](https://github.com/yolain/ComfyUI-Easy-Use/blob/main/README.en.md#image-to-image) **v1.0.6** diff --git a/README.md b/README.md index 51fe134..784bb3f 100644 --- a/README.md +++ b/README.md @@ -43,33 +43,28 @@ stage_c 与 stage_b 可以使用[checkpoints](https://huggingface.co/stabilityai ## 更新日志 -**2024-02-29** -- 修复 `easy svdLoader` 报错 +**v1.0.9 [2024-3-2]** -**2024-02-28** +- 新增 `easy instantIDApply` - 需要先安装 [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID), 工作流参考[示例](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#InstantID) +- 修复 `easy detailerFix` 未添加到保存图片格式化扩展名可用节点列表 +- 修复 `easy XYInputs: PromptSR` 在替换负面提示词时报错 +**v1.0.8 (f28cbf7)** + +- `easy cascadeLoader` stage_c 与 stage_b 支持checkpoint模型 (需要下载[checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints)) +- `easy styleSelector` 搜索框修改为不区分大小写匹配 +- `easy fullLoader` 增加 **positive**、**negative**、**latent** 输出项 +- 修复 SDXLClipModel 在 ComfyUI 修订版本号 2016[c2cb8e88] 及以上的报错(判断了版本号可兼容老版本) +- 修复 `easy detailerFix` 批次大小大于1时生成出错 - 修复`easy preSampling`等 latent传入后无法根据批次索引生成的问题 - -**2024-02-27** - +- 修复 `easy svdLoader` 报错 +- 优化代码,减少了诸多冗余,提升运行速度 - 去除中文翻译对照文本 (翻译对照已由 [AIGODLIKE-COMFYUI-TRANSLATION](https://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Translation) 统一维护啦! 首次下载或者版本较早的朋友请更新 AIGODLIKE-COMFYUI-TRANSLATION 和本节点包至最新版本。) -**2024-02-26** - -- `easy fullLoader` 增加 **positive**、**negative**、**latent** 输出项 -- 修复 SDXLClipModel 在 ComfyUI 修订版本号 2016[c2cb8e88] 及以上的报错(判断了版本号可兼容老版本) -- 修复 `easy detailerFix` 批次大小大于1时生成出错 - -**v1.0.8(2024-02-25)** - -- `easy cascadeLoader` stage_c 与 stage_b 支持checkpoint模型 (需要下载[checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints)) -- `easy styleSelector` 搜索框修改为不区分大小写匹配 -- 优化代码,减少了诸多冗余,提升运行速度 - **v1.0.7** - 增加 `easy cascadeLoader` - stable cascade 加载器 @@ -255,9 +250,9 @@ stage_c 与 stage_b 可以使用[checkpoints](https://huggingface.co/stabilityai -#### SDTurbo+高清修复+SVD +#### InstantID - + ### StableCascade #### 文生图 @@ -266,6 +261,7 @@ stage_c 与 stage_b 可以使用[checkpoints](https://huggingface.co/stabilityai #### 图生图 + ## Credits [ComfyUI](https://github.com/comfyanonymous/ComfyUI) - 功能强大且模块化的Stable Diffusion GUI diff --git a/__init__.py b/__init__.py index bbd5adc..93c24fc 100644 --- a/__init__.py +++ b/__init__.py @@ -74,4 +74,4 @@ WEB_DIRECTORY = "./web" __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', "WEB_DIRECTORY"] -print('\033[34mComfy-Easy-Use (v1.0.8): \033[92mLoaded\033[0m') \ No newline at end of file +print('\033[34mComfy-Easy-Use (v1.0.9): \033[92mLoaded\033[0m') \ No newline at end of file diff --git a/py/easyNodes.py b/py/easyNodes.py index 385317a..4ee0037 100644 --- a/py/easyNodes.py +++ b/py/easyNodes.py @@ -1441,6 +1441,7 @@ class controlnetSimple: "loader_settings": pipe["loader_settings"] } + del pipe return (new_pipe, positive, negative) # controlnetADV @@ -1535,6 +1536,8 @@ class LLLiteLoader: return (model_lllite,) +#---------------------------------------------------------------测试 开始----------------------------------------------------------------------# + # FooocusInpaint (Testing) from .fooocus import InpaintHead, InpaintWorker inpaint_head_model = None @@ -1567,6 +1570,90 @@ class fooocusInpaintLoader: return ((inpaint_head_model, inpaint_lora),) +#Apply InstantID +class instantIDApply: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required":{ + "pipe": ("PIPE_LINE",), + "image": ("IMAGE",), + "instantid_file": (folder_paths.get_filename_list("instantid"),), + "insightface": (["CPU", "CUDA", "ROCM"],), + "control_net_name": (folder_paths.get_filename_list("controlnet"),), + "cn_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + "cn_soft_weights": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},), + "weight": ("FLOAT", {"default": .8, "min": 0.0, "max": 5.0, "step": 0.01, }), + "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001, }), + "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001, }), + "noise": ("FLOAT", {"default": 0.35, "min": 0.0, "max": 1.0, "step": 0.05, }), + }, + "optional": { + "image_kps": ("IMAGE",), + "mask": ("MASK",), + "control_net": ("CONTROL_NET",), + }, + "hidden": { + "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID" + }, + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "CONDITIONING", "CONDITIONING") + RETURN_NAMES = ("pipe", "model", "positive", "negative") + OUTPUT_NODE = True + + FUNCTION = "apply" + CATEGORY = "EasyUse/__for_testing" + + def error(self): + raise Exception(f"[ERROR] To use instantIDApply, you need to install 'ComfyUI_InstantID'") + + def apply(self, pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps=None, mask=None, control_net=None, prompt=None, extra_pnginfo=None, my_unique_id=None): + instantid_model, insightface_model, face_embeds = None, None, None + model = pipe['model'] + positive = pipe['positive'] + negative = pipe['negative'] + # Load InstantID + if "InstantIDModelLoader" in ALL_NODE_CLASS_MAPPINGS: + load_instant_cls = ALL_NODE_CLASS_MAPPINGS["InstantIDModelLoader"] + instantid_model, = load_instant_cls().load_model(instantid_file) + else: + self.error() + if "InstantIDFaceAnalysis" in ALL_NODE_CLASS_MAPPINGS: + load_insightface_cls = ALL_NODE_CLASS_MAPPINGS["InstantIDFaceAnalysis"] + insightface_model, = load_insightface_cls().load_insight_face(insightface) + else: + self.error() + + # 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) + model, positive, negative = instantid_apply().apply_instantid(instantid_model, insightface_model, control_net, image, model, positive, negative, start_at, end_at, weight=weight, ip_weight=None, cn_strength=cn_strength, noise=noise, image_kps=image_kps, mask=None) + else: + self.error() + + new_pipe = { + "model": 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, model, positive, negative) #---------------------------------------------------------------预采样 开始----------------------------------------------------------------------# # 预采样设置(基础) @@ -3373,26 +3460,6 @@ class detailerFix: return {"ui": {"images": results}, "result": (new_pipe, result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, result_mask, result_cnet_images )} -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) - -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'}) - class ultralyticsDetectorForDetailerFix: @classmethod def INPUT_TYPES(s): @@ -4697,7 +4764,8 @@ NODE_CLASS_MAPPINGS = { # "easy imageRemoveBG": imageREMBG, "dynamicThresholdingFull": dynamicThresholdingFull, # __for_testing 测试 - "easy fooocusInpaintLoader": fooocusInpaintLoader + "easy fooocusInpaintLoader": fooocusInpaintLoader, + "easy instantIDApply": instantIDApply, } NODE_DISPLAY_NAME_MAPPINGS = { # prompt 提示词 @@ -4773,5 +4841,6 @@ NODE_DISPLAY_NAME_MAPPINGS = { "easy imageRemoveBG": "ImageRemoveBG", "dynamicThresholdingFull": "DynamicThresholdingFull", # __for_testing 测试 - "easy fooocusInpaintLoader": "Load Fooocus Inpaint" + "easy fooocusInpaintLoader": "Load Fooocus Inpaint", + "easy instantIDApply": "Easy Apply InstantID" } \ No newline at end of file diff --git a/py/libs/controlnet.py b/py/libs/controlnet.py index c37c8b2..034c9b0 100644 --- a/py/libs/controlnet.py +++ b/py/libs/controlnet.py @@ -1,12 +1,13 @@ import folder_paths import comfy.controlnet +import comfy.model_management from nodes import NODE_CLASS_MAPPINGS 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): + 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: @@ -19,43 +20,64 @@ class easyControlnet: 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): + if strength == 0: + return (positive, negative) + + control_net = self.load_controlnet(control_net_name, control_net, scale_soft_weights) + + if mask is not None: + mask = mask.to(self.device) + + if mask is not None and len(mask.shape) < 3: + mask = mask.unsqueeze(0) control_hint = image.movedim(-1, 1) - if strength != 0: - if negative is None: - p = [] - for t in positive: - n = [t[0], t[1].copy()] - c_net = control_net.copy().set_cond_hint(control_hint, strength, (start_percent, end_percent)) - if 'control' in t[1]: - c_net.set_previous_controlnet(t[1]['control']) - n[1]['control'] = c_net - n[1]['control_apply_to_uncond'] = True - p.append(n) - positive = p - else: - cnets = {} - out = [] - for conditioning in [positive, negative]: - c = [] - for t in conditioning: - d = t[1].copy() + is_cond = True + if negative is None: + p = [] + for t in positive: + n = [t[0], t[1].copy()] + c_net = control_net.copy().set_cond_hint(control_hint, strength, (start_percent, end_percent)) + if 'control' in t[1]: + c_net.set_previous_controlnet(t[1]['control']) + n[1]['control'] = c_net + n[1]['control_apply_to_uncond'] = True + if mask is not None: + n[1]['mask'] = mask + n[1]['set_area_to_bounds'] = False + p.append(n) + positive = p + else: + cnets = {} + out = [] + for conditioning in [positive, negative]: + c = [] + for t in conditioning: + d = t[1].copy() - prev_cnet = d.get('control', None) - if prev_cnet in cnets: - c_net = cnets[prev_cnet] - else: - c_net = control_net.copy().set_cond_hint(control_hint, strength, (start_percent, end_percent)) - c_net.set_previous_controlnet(prev_cnet) - cnets[prev_cnet] = c_net + prev_cnet = d.get('control', None) + if prev_cnet in cnets: + c_net = cnets[prev_cnet] + else: + c_net = control_net.copy().set_cond_hint(control_hint, strength, (start_percent, end_percent)) + c_net.set_previous_controlnet(prev_cnet) + cnets[prev_cnet] = c_net - d['control'] = c_net - d['control_apply_to_uncond'] = False - n = [t[0], d] - c.append(n) - out.append(c) - positive = out[0] - negative = out[1] + d['control'] = c_net + d['control_apply_to_uncond'] = False + + if mask is not None: + d['mask'] = mask + d['set_area_to_bounds'] = False + + n = [t[0], d] + c.append(n) + out.append(c) + positive = out[0] + negative = out[1] return (positive, negative) \ No newline at end of file diff --git a/py/libs/xyplot.py b/py/libs/xyplot.py index ffb9b47..4e21aef 100644 --- a/py/libs/xyplot.py +++ b/py/libs/xyplot.py @@ -367,7 +367,7 @@ class easyXYPlot(): w_max=1.0, apply_to_pooled="enable", a1111_prompt_style=a1111_prompt_style, steps=steps) if "negative_cond" in plot_image_vars: - positive = positive + plot_image_vars["negative_cond"] + negative = negative + plot_image_vars["negative_cond"] # ControlNet if "ControlNet" in self.x_type or "ControlNet" in self.y_type: diff --git a/web/js/easy/easySaveImage.js b/web/js/easy/easySaveImage.js index 469804e..84e8c63 100644 --- a/web/js/easy/easySaveImage.js +++ b/web/js/easy/easySaveImage.js @@ -4,7 +4,7 @@ import { applyTextReplacements } from "/scripts/utils.js"; app.registerExtension({ name: "Comfy.Easy.SaveImageExtraOutput", async beforeRegisterNodeDef(nodeType, nodeData, app) { - if (["easy imageSave", "easy fullkSampler", "easy kSampler", "easy kSamplerTiled","easy kSamplerInpainting", "easy kSamplerDownscaleUnet", "easy kSamplerSDTurbo"].includes(nodeData.name)) { + if (["easy imageSave", "easy fullkSampler", "easy kSampler", "easy kSamplerTiled","easy kSamplerInpainting", "easy kSamplerDownscaleUnet", "easy kSamplerSDTurbo","easy detailerFix"].includes(nodeData.name)) { const onNodeCreated = nodeType.prototype.onNodeCreated; // When the SaveImage node is created we want to override the serialization of the output name widget to run our S&R nodeType.prototype.onNodeCreated = function () {