From d95772147f57da0dd9f9c634ec8e71d72503a274 Mon Sep 17 00:00:00 2001 From: yolain Date: Sat, 25 May 2024 11:47:51 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=96Upgrade=20to=20v1.1.8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- README.en.md | 5 + README.md | 5 + __init__.py | 4 +- py/config.py | 5 + py/easyNodes.py | 157 ++++++++++++++++++++-- py/image.py | 6 +- py/{ => libs}/adv_encode.py | 4 +- py/libs/conditioning.py | 6 +- py/{ => libs}/dynthres_core.py | 0 py/{ => libs}/fooocus.py | 0 py/{ => libs}/gradual_latent_hires_fix.py | 0 py/{ => libs}/lllite.py | 0 py/libs/loader.py | 4 +- py/{ => libs}/log.py | 0 py/libs/stability.py | 2 +- py/{ => libs}/wildcards.py | 0 py/libs/xyplot.py | 7 +- py/server.py | 38 ------ 19 files changed, 178 insertions(+), 68 deletions(-) rename py/{ => libs}/adv_encode.py (99%) rename py/{ => libs}/dynthres_core.py (100%) rename py/{ => libs}/fooocus.py (100%) rename py/{ => libs}/gradual_latent_hires_fix.py (100%) rename py/{ => libs}/lllite.py (100%) rename py/{ => libs}/log.py (100%) rename py/{ => libs}/wildcards.py (100%) diff --git a/.gitignore b/.gitignore index 765fd21..14a8cf0 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ docs/** .vscode/ .idea/ mmb-preset.custom.txt -config.yaml \ No newline at end of file +config.yaml +node.tar.gz \ No newline at end of file diff --git a/README.en.md b/README.en.md index b43d5ec..7978894 100644 --- a/README.en.md +++ b/README.en.md @@ -33,6 +33,11 @@ - ## Changelog +**v1.1.8** + +- Added `easy applyBrushNet` - [Workflow Example](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4brushnet_1.1.8.json) +- Added `easy applyPowerPaint` - [Workflow Example](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4powerpaint_outpaint_1.1.8.json) + **v1.1.7** - Added `easy prompt` - Subject and light presets, maybe adjusted later diff --git a/README.md b/README.md index 10eaadd..d32b0bc 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,11 @@ ## 更新日志 +**v1.1.8** + +- 增加 `easy applyBrushNet` - [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4brushnet_1.1.8.json) +- 增加 `easy applyPowerPaint` - [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4powerpaint_outpaint_1.1.8.json) + **v1.1.7** - 修复 一些模型(如controlnet模型等)未成功写入缓存,导致修改前置节点束参数(如提示词)需要二次载入模型的问题 diff --git a/__init__.py b/__init__.py index fd1cf03..9c228a7 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.1.7" +__version__ = "1.1.8" import os import glob @@ -26,7 +26,7 @@ cwd_path = os.path.dirname(os.path.realpath(__file__)) comfy_path = folder_paths.base_path #Wildcards读取 -from .py.wildcards import read_wildcard_dict +from .py.libs.wildcards import read_wildcard_dict wildcards_path = os.path.join(os.path.dirname(__file__), "wildcards") if os.path.exists(wildcards_path): read_wildcard_dict(wildcards_path) diff --git a/py/config.py b/py/config.py index 3ee3c15..0442bb1 100644 --- a/py/config.py +++ b/py/config.py @@ -76,6 +76,11 @@ BRUSHNET_MODELS = { } } } +POWERPAINT_CLIP = { + "base_fp16":{ + "model_url":"https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/text_encoder/model.fp16.safetensors" + } +} # layerDiffuse LAYER_DIFFUSION_DIR = os.path.join(folder_paths.models_dir, "layer_model") diff --git a/py/easyNodes.py b/py/easyNodes.py index 5b75c22..378f09d 100644 --- a/py/easyNodes.py +++ b/py/easyNodes.py @@ -18,20 +18,20 @@ 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, 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 .config import MAX_SEED_NUM, BASE_RESOLUTIONS, RESOURCES_DIR, INPAINT_DIR, FOOOCUS_STYLES_DIR, FOOOCUS_INPAINT_HEAD, FOOOCUS_INPAINT_PATCH, BRUSHNET_MODELS, POWERPAINT_CLIP, IPADAPTER_DIR, IPADAPTER_MODELS, DYNAMICRAFTER_DIR, DYNAMICRAFTER_MODELS, IC_LIGHT_MODELS from .layer_diffuse.func import LayerDiffuse, LayerMethod +from .libs.log import log_node_info, log_node_error, log_node_warn +from .libs.adv_encode import advanced_encode +from .libs.wildcards import process_with_loras, get_wildcard_list, process 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 from .libs.controlnet import easyControlnet from .libs.conditioning import prompt_to_cond, set_cond -from .libs import cache as backend_cache from .libs.easing import EasingBase +from .libs import cache as backend_cache sampler = easySampler() easyCache = easyLoader() @@ -2076,7 +2076,7 @@ class controlnetAdvanced: return (new_pipe, positive, negative) # LLLiteLoader -from .lllite import load_control_net_lllite_patch +from .libs.lllite import load_control_net_lllite_patch class LLLiteLoader: def __init__(self): pass @@ -2117,7 +2117,7 @@ class LLLiteLoader: #---------------------------------------------------------------Inpaint 开始----------------------------------------------------------------------# # FooocusInpaint -from .fooocus import InpaintHead, InpaintWorker +from .libs.fooocus import InpaintHead, InpaintWorker inpaint_head_model = None class applyFooocusInpaint: @@ -2157,6 +2157,137 @@ class applyFooocusInpaint: m, = worker.patch(cloned, latent, patch) return (m,) +# brushnet +class applyBrushNet: + + def __init__(self): + self.inpaint_path = os.path.join(folder_paths.models_dir, 'inpaint') + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "pipe": ("PIPE_LINE",), + "image": ("IMAGE",), + "mask": ("MASK",), + "brushnet": (folder_paths.get_filename_list('inpaint'),), + "dtype": (['float16', 'bfloat16', 'float32', 'float64'], ), + "scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0}), + "start_at": ("INT", {"default": 0, "min": 0, "max": 10000}), + "end_at": ("INT", {"default": 10000, "min": 0, "max": 10000}), + }, + } + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("pipe",) + CATEGORY = "EasyUse/Inpaint" + FUNCTION = "apply" + + def apply(self, pipe, image, mask, brushnet, dtype, scale, start_at, end_at): + + model = pipe['model'] + vae = pipe['vae'] + positive = pipe['positive'] + negative = pipe['negative'] + if brushnet in backend_cache.cache: + log_node_info("easy brushnetApply", f"Using {brushnet} Cached") + _, brushnet_model = backend_cache.cache[brushnet][1] + else: + if "BrushNetLoader" not in ALL_NODE_CLASS_MAPPINGS: + raise Exception("BrushNetLoader not found,please install ComfyUI-BrushNet") + cls = ALL_NODE_CLASS_MAPPINGS['BrushNetLoader'] + brushnet_model, = cls.brushnet_loading(self, brushnet, dtype) + backend_cache.update_cache(brushnet, 'brushnet', (False, brushnet_model)) + cls = ALL_NODE_CLASS_MAPPINGS['BrushNet'] + m, positive, negative, latent = cls().model_update(model=model, vae=vae, image=image, mask=mask, + brushnet=brushnet_model, positive=positive, + negative=negative, scale=scale, start_at=start_at, + end_at=end_at) + new_pipe = { + **pipe, + "model": m, + "positive": positive, + "negative": negative, + "samples": latent, + } + del pipe + return (new_pipe,) + +#powerpaint +class applyPowerPaint: + + def __init__(self): + self.inpaint_path = os.path.join(folder_paths.models_dir, 'inpaint') + self.clip_path = os.path.join(folder_paths.models_dir, 'clip') + + def get_file_list(filenames, ext='.bin'): + return [file for file in filenames if file.lower().endswith(ext)] + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "pipe": ("PIPE_LINE",), + "image": ("IMAGE",), + "mask": ("MASK",), + "powerpaint_model": (s.get_file_list(folder_paths.get_filename_list('inpaint'),'.safetensors'),), + "powerpaint_clip": (s.get_file_list(folder_paths.get_filename_list('inpaint')),), + "dtype": (['float16', 'bfloat16', 'float32', 'float64'],), + "fitting": ("FLOAT", {"default": 1.0, "min": 0.3, "max": 1.0}), + "function": (['text guided', 'shape guided', 'object removal', 'context aware', 'image outpainting'],), + "scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0}), + "start_at": ("INT", {"default": 0, "min": 0, "max": 10000}), + "end_at": ("INT", {"default": 10000, "min": 0, "max": 10000}), + }, + } + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("pipe",) + CATEGORY = "EasyUse/Inpaint" + FUNCTION = "apply" + + def apply(self, pipe, image, mask, powerpaint_model, powerpaint_clip, dtype, fitting, function, scale, start_at, end_at): + model = pipe['model'] + vae = pipe['vae'] + positive = pipe['positive'] + negative = pipe['negative'] + # load powerpaint clip + if powerpaint_clip in backend_cache.cache: + log_node_info("easy powerpaintApply", f"Using {powerpaint_clip} Cached") + _, ppclip = backend_cache.cache[powerpaint_clip][1] + else: + if "PowerPaintCLIPLoader" not in ALL_NODE_CLASS_MAPPINGS: + raise Exception("PowerPaintCLIPLoader not found,please install ComfyUI-Brushnet") + cls = ALL_NODE_CLASS_MAPPINGS['PowerPaintCLIPLoader'] + model_url = POWERPAINT_CLIP['base_fp16']['model_url'] + base_clip = get_local_filepath(model_url, os.path.join(folder_paths.models_dir, 'clip')) + base = os.path.basename(base_clip) + ppclip, = cls.ppclip_loading(self, base, powerpaint_clip) + backend_cache.update_cache(powerpaint_clip, 'ppclip', (False,ppclip)) + # load powerpaint model + if powerpaint_model in backend_cache.cache: + log_node_info("easy powerpaintApply", f"Using {powerpaint_model} Cached") + _, powerpaint = backend_cache.cache[powerpaint_model][1] + else: + if "BrushNetLoader" not in ALL_NODE_CLASS_MAPPINGS: + raise Exception("BrushNetLoader not found,please install ComfyUI-Brushnet") + cls = ALL_NODE_CLASS_MAPPINGS['BrushNetLoader'] + powerpaint, = cls.brushnet_loading(self, powerpaint_model, dtype) + backend_cache.update_cache(powerpaint_model, 'powerpaint', (False, powerpaint)) + cls = ALL_NODE_CLASS_MAPPINGS['PowerPaint'] + m, positive, negative, latent = cls().model_update(model=model, vae=vae, image=image, mask=mask, powerpaint=powerpaint, + clip=ppclip, positive=positive, + negative=negative, fitting=fitting, function=function, + scale=scale, start_at=start_at, end_at=end_at) + new_pipe = { + **pipe, + "model": m, + "positive": positive, + "negative": negative, + "samples": latent, + } + del pipe + return (new_pipe,) #---------------------------------------------------------------适配器 开始----------------------------------------------------------------------# @@ -3645,7 +3776,7 @@ class samplerCustomSettings: return {"ui": {"value": [seed]}, "result": (new_pipe,)} # 预采样设置(SDTurbo) -from .gradual_latent_hires_fix import sample_dpmpp_2s_ancestral, sample_dpmpp_2m_sde, sample_lcm, sample_euler_ancestral +from .libs.gradual_latent_hires_fix import sample_dpmpp_2s_ancestral, sample_dpmpp_2m_sde, sample_lcm, sample_euler_ancestral class sdTurboSettings: def __init__(self): pass @@ -4014,7 +4145,7 @@ class layerDiffusionSettingsADDTL: return (new_pipe,) # 预采样设置(动态CFG) -from .dynthres_core import DynThresh +from .libs.dynthres_core import DynThresh class dynamicCFGSettings: def __init__(self): pass @@ -7253,8 +7384,9 @@ NODE_CLASS_MAPPINGS = { "easy styleAlignedBatchAlign": styleAlignedBatchAlign, "easy icLightApply": icLightApply, # Inpaint 内补 - # "easy fooocusInpaintLoader": fooocusInpaintLoader, "easy applyFooocusInpaint": applyFooocusInpaint, + "easy applyBrushNet": applyBrushNet, + "easy applyPowerPaint": applyPowerPaint, # latent 潜空间 "easy latentNoisy": latentNoisy, "easy latentCompositeMaskedWithCond": latentCompositeMaskedWithCond, @@ -7362,8 +7494,9 @@ NODE_DISPLAY_NAME_MAPPINGS = { "easy styleAlignedBatchAlign": "Easy Apply StyleAlign", "easy icLightApply": "Easy Apply ICLight", # Inpaint 内补 - # "easy fooocusInpaintLoader": "Load Fooocus Inpaint(Removed)", - "easy applyFooocusInpaint": "Apply Fooocus Inpaint", + "easy applyFooocusInpaint": "Easy Apply Fooocus Inpaint", + "easy applyBrushNet": "Easy Apply BrushNet", + "easy applyPowerPaint": "Easy Apply PowerPaint", # latent 潜空间 "easy latentNoisy": "LatentNoisy", "easy latentCompositeMaskedWithCond": "LatentCompositeMaskedWithCond", diff --git a/py/image.py b/py/image.py index e8a27f2..6c9cbde 100644 --- a/py/image.py +++ b/py/image.py @@ -8,10 +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 import Resize, CenterCrop 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, mask2image, blendImage +from .libs.log import log_node_info +from .libs.image import pil2tensor, tensor2pil, ResizeMode, get_new_bounds, RGB2RGBA, image2mask 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 diff --git a/py/adv_encode.py b/py/libs/adv_encode.py similarity index 99% rename from py/adv_encode.py rename to py/libs/adv_encode.py index ec4d4e4..fbebf48 100644 --- a/py/adv_encode.py +++ b/py/libs/adv_encode.py @@ -4,9 +4,7 @@ import itertools from comfy import model_management from comfy.sdxl_clip import SDXLClipModel, SDXLRefinerClipModel, SDXLClipG -from nodes import NODE_CLASS_MAPPINGS, ConditioningConcat, CLIPTextEncode - -from .libs.utils import compare_revision +from nodes import NODE_CLASS_MAPPINGS, ConditioningConcat def _grouper(n, iterable): it = iter(iterable) diff --git a/py/libs/conditioning.py b/py/libs/conditioning.py index bcb4ab9..45965b7 100644 --- a/py/libs/conditioning.py +++ b/py/libs/conditioning.py @@ -1,7 +1,7 @@ from .utils import find_wildcards_seed, find_nearest_steps, is_linked_styles_selector -from ..log import log_node_warn -from ..adv_encode import advanced_encode -from ..wildcards import process_with_loras +from .log import log_node_warn +from .wildcards import process_with_loras +from .adv_encode import advanced_encode from nodes import ConditioningConcat, ConditioningCombine, ConditioningAverage, ConditioningSetTimestepRange diff --git a/py/dynthres_core.py b/py/libs/dynthres_core.py similarity index 100% rename from py/dynthres_core.py rename to py/libs/dynthres_core.py diff --git a/py/fooocus.py b/py/libs/fooocus.py similarity index 100% rename from py/fooocus.py rename to py/libs/fooocus.py diff --git a/py/gradual_latent_hires_fix.py b/py/libs/gradual_latent_hires_fix.py similarity index 100% rename from py/gradual_latent_hires_fix.py rename to py/libs/gradual_latent_hires_fix.py diff --git a/py/lllite.py b/py/libs/lllite.py similarity index 100% rename from py/lllite.py rename to py/libs/lllite.py diff --git a/py/libs/loader.py b/py/libs/loader.py index 11a89a6..2d9acd1 100644 --- a/py/libs/loader.py +++ b/py/libs/loader.py @@ -5,7 +5,7 @@ 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 +from .log import log_node_info, log_node_error stable_diffusion_loaders = ["easy fullLoader", "easy a1111Loader", "easy comfyLoader", "easy zero123Loader", "easy svdLoader"] stable_cascade_loaders = ["easy cascadeLoader"] @@ -268,7 +268,7 @@ class easyLoader: 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'): + def load_clip(self, clip_name, type='stable_diffusion', load_clip=None): if type == 'stable_diffusion': clip_type = comfy.sd.CLIPType.STABLE_DIFFUSION else: diff --git a/py/log.py b/py/libs/log.py similarity index 100% rename from py/log.py rename to py/libs/log.py diff --git a/py/libs/stability.py b/py/libs/stability.py index 7fede13..26688fa 100644 --- a/py/libs/stability.py +++ b/py/libs/stability.py @@ -6,7 +6,7 @@ import pathlib from aiohttp import web from server import PromptServer from .image import tensor2pil, pil2tensor, image2base64, pil2byte -from ..log import log_node_error +from .log import log_node_error root_path = pathlib.Path(__file__).parent.parent.parent diff --git a/py/wildcards.py b/py/libs/wildcards.py similarity index 100% rename from py/wildcards.py rename to py/libs/wildcards.py diff --git a/py/libs/xyplot.py b/py/libs/xyplot.py index a6a5309..e1d4f7a 100644 --- a/py/libs/xyplot.py +++ b/py/libs/xyplot.py @@ -2,11 +2,12 @@ import os, torch from pathlib import Path from PIL import Image, ImageDraw, ImageFont from .utils import easySave -from ..config import RESOURCES_DIR -from ..log import log_node_warn -from ..adv_encode import advanced_encode +from .adv_encode import advanced_encode from .controlnet import easyControlnet +from .log import log_node_warn from ..layer_diffuse.func import LayerDiffuse +from ..config import RESOURCES_DIR + class easyXYPlot(): def __init__(self, xyPlotData, save_prefix, image_output, prompt, extra_pnginfo, my_unique_id, sampler, easyCache): diff --git a/py/server.py b/py/server.py index 2657fb9..0719b48 100644 --- a/py/server.py +++ b/py/server.py @@ -2,9 +2,6 @@ import random import server from enum import Enum - -seed_nodes = ["easy wildcards","easy preSampling","easy preSamplingAdvanced","easy preSamplingSdTurbo","easy preSamplingDynamicCFG","easy preSamplingLayerDiffusion","easy preSamplingCascade","easy fullCascadeKSampler","easy fullkSampler","easy seed","easy latentNoisy", "easy preSamplingNoiseIn"] - class SGmode(Enum): FIX = 1 INCR = 2 @@ -123,41 +120,6 @@ def prompt_seed_update(json_data): # control after generated if mode is not None and not mode: control_seed(node[1], action, seed_is_global) - # else: - # prompts = json_data['prompt'].items() - # for k, v in prompts: - # if 'class_type' not in v: - # continue - # cls = v['class_type'] - # if cls in seed_nodes: - # extra_data = next((x for x in workflow["nodes"] if str(x["id"]) == k), None) - # if extra_data is not None: - # inputs = extra_data.get('inputs') - # widgets_value = extra_data.get('widgets_values') - # widgets_length = len(widgets_value) - # if "disable" in widgets_value: - # break - # if inputs is not None and inputs != []: - # seed_num_input = next((x for x in inputs if x['name'] == 'seed_num' and x['type'] == 'INT'), None) - # if seed_num_input is not None: - # action = 'fixed' - # else: - # action = widgets_value[widgets_length - 1] - # else: - # control_index = widgets_length - 2 if cls == 'easy seed' else widgets_length - 1 - # action = widgets_value[control_index] - # - # # print(action) - # node = k, v - # value = control_seed(node[1], action, False) - # - # if k not in seed_widget_map: - # continue - # - # if 'seed_num' in v['inputs']: - # if isinstance(v['inputs']['seed_num'], int): - # v['inputs']['seed_num'] = value - return value is not None