Compare commits

..
27 Commits
Author SHA1 Message Date
yolain d820842e39 Upgrade v1.3.0 to ComfyRegistry 2025-05-10 00:02:13 +08:00
yolain 2f78a523b3 Fix easy imageConcat match image size bug 2025-05-10 00:01:03 +08:00
yolain b2a8666423 Fix easy humanSegmentation not being selected 2025-05-09 16:10:33 +08:00
yolain 2c02a471d0 Fix latest commit #758 2025-05-09 07:46:20 +08:00
yolain ea521e0303 Set loop nodes maximum number of inputs or outputs to 20 2025-05-09 00:50:13 +08:00
yolain 9b5daac023 Fix cannot redefine property: value 2025-05-08 15:17:56 +08:00
yolain 0de83f88dc Force default web version to v2 2025-05-06 16:15:06 +08:00
yolain 342ce8ccad Fix last commit 2025-05-06 14:04:42 +08:00
yolain b7881d84b1 Add uniform width method to easy makeImageForICLora 2025-05-06 14:01:11 +08:00
yolain 0f5ad38384 Add apikey_override to easy joycaption2API 2025-05-06 12:56:07 +08:00
yolain a3f487c822 Merge pull request #752 from yolain/wildcardsPromptMatrix
Add output_limit to `easy wildcardsMatrix`
2025-04-30 18:03:08 +08:00
yolain d0f496adc1 Add output_limit to easy wildcardsMatrix 2025-04-30 18:01:14 +08:00
yolain 665861ff35 Merge wildcardsPromptMatrix Node from Rosmeowtis/main 2025-04-29 12:09:00 +08:00
yolain 61568e021c Update wildcardsPromptMatrix Node #743 2025-04-29 12:03:54 +08:00
yolain a2edc37d89 Merge pull request #743 from Rosmeowtis/main
Add wildcardsPromptMatrix Node
2025-04-29 11:37:02 +08:00
yolain 1c4cb43f7b Fix line are removed at the end of a connection on easy related nodes #748 2025-04-28 19:11:42 +08:00
yolain 66143b0e20 Merge pull request #746 from Hapseleg/Pipe-info-fix
missing vars
2025-04-27 11:03:03 +08:00
Hapseleg f0da5e25c9 missing vars 2025-04-26 20:03:18 +02:00
rosmeowtis ebf25b585f update descriptions to conform to reality 2025-04-25 18:10:35 +08:00
rosmeowtis fb8968d438 wildcardsPromptMatrix node will treat offset in cycle 2025-04-25 18:06:07 +08:00
rosmeowtis 15cfeedf7b Add wildcardsPromptMatrix Node:
1. wildcard-replaced prompt will be returned in order rather than randomly
2. will return the amount of probilities and amount of probilities each option or wildcard
3. the prompt can be selected by offset argument, the order of probilities is fixed
4. even if the offset exceeds the total, it will not stop, but will always return to the last probility, requiring additional nodes to control.
2025-04-25 03:07:49 +08:00
yolain 50ae13a993 Fix hidden item judgment needs to be delayed when first loading the page #741 2025-04-24 14:36:56 +08:00
yolain aedf917067 Remove getModelsThumbnail API #702 2025-04-22 13:48:14 +08:00
yolain 69ac5e52a0 EasyUse still works when layerDiffuse-related diffusers error 2025-04-21 19:56:17 +08:00
yolain 368f7e508d EasyUse still works when brushnet-related diffusers error 2025-04-21 11:00:38 +08:00
yolain 44f0676323 Fix an issue where some widgets' associated input sockets fail to display in the new release #734 2025-04-16 22:21:20 +08:00
yolain 98273b37f2 Fix Easy KSamplers preview&choose bug #733 2025-04-16 18:39:28 +08:00
20 changed files with 430 additions and 102 deletions
+6
View File
@@ -52,6 +52,12 @@ git clone https://github.com/yolain/ComfyUI-Easy-Use
## 📜 更新日志
**v1.3.0**
- 将循环节点设置为最大输入和输出数量为20
- 添加 `uniform width` 方式到 `easy makeImageForICLora`
- 增加 `wildcardsPromptMatrix` 通配符提示词矩阵,由 [Rosmeowtis](https://github.com/Rosmeowtis) 贡献
**v1.2.9**
- 修复 Imagechooser 会导致工作流处理取消
+6
View File
@@ -47,6 +47,12 @@ Double-click install.bat to install the required dependencies
## 📜 Changelog
**v1.3.0**
- Set loop nodes maximum number of inputs and outputs to 20
- Add `uniform width` method to `easy makeImageForICLora`
- Add `wildcardsPromptMatrix` Node by [Rosmeowtis](https://github.com/Rosmeowtis)
**v1.2.9**
- Fix ImageChooser causes workflow processing to cancel
+2 -30
View File
@@ -1,4 +1,4 @@
__version__ = "1.2.9"
__version__ = "1.3.0"
import yaml
import json
@@ -20,31 +20,6 @@ for module_name in nodes_list:
imported_module = importlib.import_module(".py.nodes.{}".format(module_name), __name__)
NODE_CLASS_MAPPINGS = {**NODE_CLASS_MAPPINGS, **imported_module.NODE_CLASS_MAPPINGS}
NODE_DISPLAY_NAME_MAPPINGS = {**NODE_DISPLAY_NAME_MAPPINGS, **imported_module.NODE_DISPLAY_NAME_MAPPINGS}
# transfer python nodes to locale file
# for i in imported_module.NODE_CLASS_MAPPINGS:
# locale[i] = {
# "display_name": imported_module.NODE_DISPLAY_NAME_MAPPINGS[i] if i in imported_module.NODE_DISPLAY_NAME_MAPPINGS else i,
# "inputs":{},
# "outputs":{},
# }
# node_class = imported_module.NODE_CLASS_MAPPINGS[i]
# input_types = node_class.INPUT_TYPES()
# if "required" in input_types:
# for j in input_types["required"]:
# locale[i]['inputs'][j] = {"name": j}
# if "optional" in input_types:
# for j in input_types["optional"]:
# locale[i]['inputs'][j] = {"name": j}
# count = 0
# if "RETURN_NAMES" in node_class.__dict__:
# for j in node_class.RETURN_NAMES:
# locale[i]['outputs'][str(count)] = {"name": j}
# count+=1
# en_json_path = os.path.join(cwd_path,'locales/en/nodeDefs.json')
# with open(en_json_path, 'w', encoding='utf-8') as f:
# json.dump(locale, f, ensure_ascii=False, indent=2)
#Wildcards
from .py.libs.wildcards import read_wildcard_dict
@@ -86,11 +61,8 @@ if not os.path.exists(example_path):
with open(example_path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=4, ensure_ascii=False)
# get comfyui revision
from .py.libs.utils import compare_revision
new_frontend_revision = 2546
web_default_version = 'v2' if compare_revision(new_frontend_revision) else 'v1'
web_default_version = 'v2'
# web directory
config_path = os.path.join(cwd_path, "config.yaml")
if os.path.isfile(config_path):
+29
View File
@@ -145,6 +145,35 @@
}
}
},
"easy wildcardsMatrix": {
"display_name": "Wildcards Matrix",
"inputs": {
"Select to add LoRA": {
"name": "Select to add LoRA"
},
"Select to add Wildcard": {
"name": "Select to add Wildcard"
},
"offset": {
"name": "Offset in All Probilities"
},
"output_limit": {
"name": "Output Limit",
"tooltip": "Output n fill wildcards, -1 is output all possibilities (force offset to zero), the default value is 1"
}
},
"outputs": {
"0": {
"name": "Replaced Prompt"
},
"1": {
"name": "Total Count of the Probilities"
},
"2": {
"name": "Factors"
}
}
},
"easy prompt": {
"display_name": "Prompt",
"inputs": {
+32
View File
@@ -75,6 +75,35 @@
}
}
},
"easy wildcardsMatrix": {
"display_name": "通配符提示词矩阵",
"inputs": {
"Select to add LoRA": {
"name": "选择添加Lora"
},
"Select to add Wildcard": {
"name": "选择添加通配符"
},
"offset": {
"name": "偏移量"
},
"output_limit": {
"name": "输出个数限制",
"tooltip": "输出n个填充后通配符, -1为输出所有可能性(偏移值失效),默认值为1"
}
},
"outputs": {
"0": {
"name": "通配填充词"
},
"1": {
"name": "可能性总数"
},
"2": {
"name": "可能性总数(每通配符)"
}
}
},
"easy prompt": {
"display_name": "提示词",
"inputs": {
@@ -5691,6 +5720,9 @@
},
"pixels": {
"name": "限制像素"
},
"method": {
"name": "限制方式"
}
},
"outputs": {
+5 -2
View File
@@ -57,8 +57,11 @@ class BizyAIRAPI:
)
# joycaptionTwo
def joyCaption2(self, payload, image):
api_key = self.getAPIKey()
def joyCaption2(self, payload, image, apikey_override=None):
if apikey_override is not None:
api_key = apikey_override
else:
api_key = self.getAPIKey()
url = f"{self.base_url}/supernode/joycaption2"
auth = f"Bearer {api_key}"
headers = {
+29 -1
View File
@@ -125,7 +125,35 @@ class ResizeMode(Enum):
return 2
assert False, "NOTREACHED"
# credit by https://github.com/chflame163/ComfyUI_LayerStyle/blob/main/py/imagefunc.py#L591C1-L617C22
def fit_resize_image(image: Image, target_width: int, target_height: int, fit: str, resize_sampler: str,
background_color: str = '#000000') -> Image:
image = image.convert('RGB')
orig_width, orig_height = image.size
if image is not None:
if fit == 'letterbox':
if orig_width / orig_height > target_width / target_height: # 更宽,上下留黑
fit_width = target_width
fit_height = int(target_width / orig_width * orig_height)
else: # 更瘦,左右留黑
fit_height = target_height
fit_width = int(target_height / orig_height * orig_width)
fit_image = image.resize((fit_width, fit_height), resize_sampler)
ret_image = Image.new('RGB', size=(target_width, target_height), color=background_color)
ret_image.paste(fit_image, box=((target_width - fit_width) // 2, (target_height - fit_height) // 2))
elif fit == 'crop':
if orig_width / orig_height > target_width / target_height: # 更宽,裁左右
fit_width = int(orig_height * target_width / target_height)
fit_image = image.crop(
((orig_width - fit_width) // 2, 0, (orig_width - fit_width) // 2 + fit_width, orig_height))
else: # 更瘦,裁上下
fit_height = int(orig_width * target_height / target_width)
fit_image = image.crop(
(0, (orig_height - fit_height) // 2, orig_width, (orig_height - fit_height) // 2 + fit_height))
ret_image = fit_image.resize((target_width, target_height), resize_sampler)
else:
ret_image = image.resize((target_width, target_height), resize_sampler)
return ret_image
# CLIP反推
import comfy.utils
+177 -5
View File
@@ -1,9 +1,13 @@
import re
import random
import os
import folder_paths
import yaml
import json
import os
import random
import re
from math import prod
import yaml
import folder_paths
from .log import log_node_info
easy_wildcard_dict = {}
@@ -302,3 +306,171 @@ def process_with_loras(wildcard_opt, model, clip, title="Positive", seed=None, c
log_node_info("easy wildcards",f'{title}_decode: {pass1}')
return model, clip, pass2, pass1, show_wildcard_prompt, pipe_lora_stack
def expand_wildcard(keyword: str) -> tuple[str]:
"""传入文件通配符的关键词,从 easy_wildcard_dict 中获取通配符的所有选项。"""
global easy_wildcard_dict
if keyword in easy_wildcard_dict:
return tuple(easy_wildcard_dict[keyword])
elif '*' in keyword:
subpattern = keyword.replace('*', '.*').replace('+', r"\+")
total_pattern = []
for k, v in easy_wildcard_dict.items():
if re.match(subpattern, k) is not None:
total_pattern.extend(v)
if total_pattern:
return tuple(total_pattern)
elif '/' not in keyword:
return expand_wildcard(f"*/{keyword}")
def expand_options(options: str) -> tuple[str]:
"""传入去掉 {} 的选项。
展开选项通配符,返回该选项中的每一项,这里的每一项都是一个替换项。
不会对选项内容进行任何处理,即便存在空格或特殊符号,也会原样返回。"""
return tuple(options.split("|"))
def decimal_to_irregular(n, bases):
"""
将十进制数转换为不规则进制
:param n: 十进制数
:param bases: 各位置的基数列表,从低位到高位
:return: 不规则进制表示的列表,从低位到高位
"""
if n == 0:
return [0] * len(bases) if bases else [0]
digits = []
remaining = n
# 从低位到高位处理
for base in bases:
digit = remaining % base
digits.append(digit)
remaining = remaining // base
return digits
class WildcardProcessor:
"""通配符处理器
通配符格式:
+ option : {a|b}
+ wildcard: __keyword__ 通配符内容将从 Easy-Use 插件提供的 easy_wildcard_dict 中获取
"""
RE_OPTIONS = re.compile(r"{([^{}]*?)}")
RE_WILDCARD = re.compile(r"__([\w\s.\-+/*\\]+?)__")
RE_REPLACER = re.compile(r"{([^{}]*?)}|__([\w\s.\-+/*\\]+?)__")
# 将输入的提示词转化成符合 python str.format 要求格式的模板,并将 option 和 wildcard 按照顺序在模板中留下 {0}, {1} 等占位符
template: str
# option、wildcard 的替换项列表,按照在模板中出现的顺序排列,相同的替换项列表只保留第一份
replacers: dict[int, tuple[str]]
# 占位符的编号和替换项列表的索引的映射,占位符编号按照在模板中出现的顺序排列,方便减少替换项的存储占用
placeholder_mapping: dict[str, int] # placeholder_id => replacer_id
# 各替换项列表的项数,按照在模板中出现的顺序排列,提前计算,方便后续使用
placeholder_choices: dict[str, int] # placeholder_id => len(replacer)
def __init__(self, text: str):
self.__make_template(text)
self.__total = None
def random(self, seed=None) -> str:
"从所有可能性中随机获取一个"
if seed is not None:
random.seed(seed)
return self.getn(random.randint(0, self.total() - 1))
def getn(self, n: int) -> str:
"从所有可能性中获取第 n 个,以 self.total() 为周期循环"
n = n % self.total()
indice = decimal_to_irregular(n, self.placeholder_choices.values())
replacements = {
placeholder_id: self.replacers[self.placeholder_mapping[placeholder_id]][i]
for placeholder_id, i in zip(self.placeholder_mapping.keys(), indice)
}
return self.template.format(**replacements)
def getmany(self, limit: int, offset: int = 0) -> list[str]:
"""返回一组可能性组成的列表,为了避免结果太长导致内存占用超限,使用 limit 限制列表的长度,使用 offset 调整偏移。
若 limit 和 offset 的设置导致预期的结果长度超过剩下的实际长度,则会回到开头。
"""
return [self.getn(n) for n in range(offset, offset + limit)]
def total(self) -> int:
"计算可能性的数目"
if self.__total is None:
self.__total = prod(self.placeholder_choices.values())
return self.__total
def __make_template(self, text: str):
"""将输入的提示词转化成符合 python str.format 要求格式的模板,
并将 option 和 wildcard 按照顺序在模板中留下 {r0}, {r1} 等占位符,
即使遇到相同的 option 或 wildcard,留下的占位符编号也不同,从而使每项都独立变化。
"""
self.placeholder_mapping = {}
placeholder_id = 0
replacer_id = 0
replacers_rev = {} # replacers => id
blocks = []
# 记录所处理过的通配符末尾在文本中的位置,用于拼接完整的模板
tail = 0
for match in self.RE_REPLACER.finditer(text):
# 提取并展开通配符内容
m = match.group(0)
if m.startswith("{"):
choices = expand_options(m[1:-1])
elif m.startswith("__"):
keyword = m[2:-2].lower()
keyword = wildcard_normalize(keyword)
choices = expand_wildcard(keyword)
else:
raise ValueError(f"{m!r} is not a wildcard or option")
# 记录通配符的替换项列表和ID,相同的通配符只保留第一个
if choices not in replacers_rev:
replacers_rev[choices] = replacer_id
replacer_id += 1
# 拼接通配符前方文本
start, end = match.span()
blocks.append(text[tail:start])
tail = end
# 将通配符替换为占位符,并记录占位符和替换项列表的索引的映射
blocks.append(f"{{r{placeholder_id}}}")
self.placeholder_mapping[f"r{placeholder_id}"] = replacers_rev[choices]
placeholder_id += 1
if tail < len(text):
blocks.append(text[tail:])
self.template = "".join(blocks)
self.replacers = {v: k for k, v in replacers_rev.items()}
self.placeholder_choices = {
placeholder_id: len(self.replacers[replacer_id])
for placeholder_id, replacer_id in self.placeholder_mapping.items()
}
def test_option():
text = "{|a|b|c}"
answer = ["", "a", "b", "c"]
p = WildcardProcessor(text)
assert p.total() == len(answer)
assert p.getn(0) == answer[0]
assert p.getmany(4) == answer
assert p.getmany(4, 1) == answer[1:]
def test_same():
text = "{a|b},{a|b}"
answer = ["a,a", "b,a", "a,b", "b,b"]
p = WildcardProcessor(text)
assert p.total() == len(answer)
assert p.getn(0) == answer[0]
assert p.getmany(4) == answer
assert p.getmany(4, 1) == answer[1:]
+12 -4
View File
@@ -5,13 +5,21 @@ import os
import types
import torch
from accelerate import init_empty_weights, load_checkpoint_and_dispatch
try:
from accelerate import init_empty_weights, load_checkpoint_and_dispatch
except:
init_empty_weights, load_checkpoint_and_dispatch = None, None
import comfy
from .model import BrushNetModel, PowerPaintModel
from .model_patch import add_model_patch_option, patch_model_function_wrapper
from .powerpaint_utils import TokenizerWrapper, add_tokens
try:
from .model import BrushNetModel, PowerPaintModel
from .model_patch import add_model_patch_option, patch_model_function_wrapper
from .powerpaint_utils import TokenizerWrapper, add_tokens
except:
BrushNetModel, PowerPaintModel = None, None
add_model_patch_option, patch_model_function_wrapper = None, None
TokenizerWrapper, add_tokens = None, None
cwd_path = os.path.dirname(os.path.realpath(__file__))
brushnet_config_file = os.path.join(cwd_path, 'config', 'brushnet.json')
+5 -2
View File
@@ -9,7 +9,10 @@ from enum import Enum
from comfy.utils import load_torch_file
from comfy.conds import CONDRegular
from comfy_extras.nodes_compositing import JoinImageWithAlpha
from .model import ModelPatcher, TransparentVAEDecoder, calculate_weight_adjust_channel
try:
from .model import ModelPatcher, TransparentVAEDecoder, calculate_weight_adjust_channel
except:
ModelPatcher, TransparentVAEDecoder, calculate_weight_adjust_channel = None, None, None
from .attension_sharing import AttentionSharingPatcher
from ...config import LAYER_DIFFUSION, LAYER_DIFFUSION_DIR, LAYER_DIFFUSION_VAE
from ...libs.utils import to_lora_patch_dict, get_local_filepath, get_sd_version
@@ -51,7 +54,7 @@ class LayerDiffuse:
return (write_c_concat(cond), write_c_concat(uncond))
def apply_layer_diffusion(self, model: ModelPatcher, method, weight, samples, blend_samples, positive, negative, image=None, additional_cond=(None, None, None)):
def apply_layer_diffusion(self, model, method, weight, samples, blend_samples, positive, negative, image=None, additional_cond=(None, None, None)):
control_img: Optional[torch.TensorType] = None
sd_version = get_sd_version(model)
model_url = LAYER_DIFFUSION[method.value][sd_version]["model_url"]
+5 -1
View File
@@ -101,6 +101,9 @@ class joyCaption2API:
"multiline": True,
},
),
},
"optional":{
"apikey_override": ("STRING", {"default": "", "forceInput": True, "tooltip":"Override the API key in the local config"}),
}
}
@@ -123,6 +126,7 @@ class joyCaption2API:
extra_options,
name_input,
custom_prompt,
apikey_override=None
):
pbar = comfy.utils.ProgressBar(100)
pbar.update_absolute(10)
@@ -145,7 +149,7 @@ class joyCaption2API:
}
pbar.update_absolute(30)
caption = bizyairAPI.joyCaption2(payload, image)
caption = bizyairAPI.joyCaption2(payload, image, apikey_override)
pbar.update_absolute(100)
return (caption,)
+55 -17
View File
@@ -14,7 +14,7 @@ from torchvision.transforms.functional import to_pil_image
from ..libs.log import log_node_info
from ..libs.utils import AlwaysEqualProxy, ByPassTypeTuple
from ..libs.cache import cache, update_cache, remove_cache
from ..libs.image import pil2tensor, tensor2pil, ResizeMode, get_new_bounds, RGB2RGBA, image2mask, empty_image
from ..libs.image import pil2tensor, tensor2pil, ResizeMode, get_new_bounds, RGB2RGBA, image2mask, empty_image, fit_resize_image
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
@@ -802,7 +802,20 @@ class imageConcat:
elif image2 is None:
return (image1,)
if match_image_size:
image2 = torch.nn.functional.interpolate(image2, size=(image1.shape[2], image1.shape[3]), mode="bilinear")
# Convert tensor to PIL for proper aspect ratio resizing
pil_image2 = tensor2pil(image2)
if direction in ['right', 'left']:
aspect_ratio = pil_image2.width / pil_image2.height
new_height = image1.shape[1]
new_width = int(aspect_ratio * new_height)
pil_image2 = fit_resize_image(pil_image2, new_width, new_height, 'fill', Image.LANCZOS, '#000000')
else: # 'up' or 'down'
aspect_ratio = pil_image2.height / pil_image2.width
new_width = image1.shape[2]
new_height = int(aspect_ratio * new_width)
pil_image2 = fit_resize_image(pil_image2, new_width, new_height, 'fill', Image.LANCZOS, '#000000')
image2 = pil2tensor(pil_image2)
if direction == 'right':
row = torch.cat((image1, image2), dim=2)
elif direction == 'down':
@@ -1924,6 +1937,7 @@ class makeImageForICRepaint:
"image_1": ("IMAGE",),
"direction": (["top-bottom", "left-right"], {"default": "left-right"}),
"pixels": ("INT", {"default": 0, "max": MAX_RESOLUTION, "min": 0, "step": 8, "tooltip": "The pixel of the output image is not set when it is 0"}),
"method": (["uniform height", "uniform width", "auto"],{"default": "auto"}),
},
"optional": {
"image_2": ("IMAGE",),
@@ -1950,26 +1964,52 @@ class makeImageForICRepaint:
b = torch.full([batch_size, height, width, 1], ((color) & 0xFF) / 0xFF)
return torch.cat((r, g, b), dim=-1)
def make(self, image_1, direction, pixels=0, image_2=None, mask_1=None, mask_2=None):
def resize_image_and_mask(self, image, mask, w, h ):
ret_images = []
ret_masks = []
_mask = Image.new('L', size=(w, h), color='black')
_image = Image.new('RGB', size=(w, h), color='black')
if image is not None and len(image) > 0:
for i in image:
_image = tensor2pil(i).convert('RGB')
_image = fit_resize_image(_image, w, h, 'fill', Image.LANCZOS, '#000000')
ret_images.append(pil2tensor(_image))
if mask is not None and len(mask) > 0:
for m in mask:
_mask = tensor2pil(m).convert('L')
_mask = fit_resize_image(_mask, w, h, 'fill', Image.LANCZOS).convert('L')
ret_masks.append(image2mask(_mask))
if len(ret_images) > 0 and len(ret_masks) > 0:
return (torch.cat(ret_images, dim=0), torch.cat(ret_masks, dim=0),)
elif len(ret_images) > 0 and len(ret_masks) == 0:
return (torch.cat(ret_images, dim=0), None,)
elif len(ret_images) == 0 and len(ret_masks) > 0:
return (None, torch.cat(ret_masks, dim=0),)
else:
return (None, None)
def make(self, image_1, direction, pixels, method, image_2=None, mask_1=None, mask_2=None):
if image_2 is None:
image_2 = self.emptyImage(image_1.shape[2], image_1.shape[1])
mask_2 = torch.full((1, image_1.shape[1], image_1.shape[2]), 1, dtype=torch.float32, device="cpu")
elif image_2 is not None and mask_2 is None:
raise ValueError("mask_2 is required when image_2 is provided")
mask_2 = torch.full((1, image_2.shape[1], image_2.shape[2]), 1, dtype=torch.float32, device="cpu")
if pixels > 0:
_, img2_h, img2_w, _ = image_2.shape
h = pixels if direction == 'left-right' else int(img2_h * (pixels / img2_w))
w = pixels if direction == 'top-bottom' else int(img2_w * (pixels / img2_h))
if method == "uniform height":
h = pixels
w = int(img2_w * (pixels / img2_h))
elif method == "uniform width":
w = pixels
h = int(img2_h * (pixels / img2_w))
else:
h = pixels if direction == 'left-right' else int(img2_h * (pixels / img2_w))
w = pixels if direction == 'top-bottom' else int(img2_w * (pixels / img2_h))
image_2 = image_2.movedim(-1, 1)
image_2 = comfy.utils.common_upscale(image_2, w, h, 'bicubic', 'disabled')
image_2 = image_2.movedim(1, -1)
orig_image_2 = tensor2pil(image_2)
orig_mask_2 = tensor2pil(mask_2).convert('L')
orig_mask_2 = orig_mask_2.resize(orig_image_2.size)
mask_2 = pil2tensor(orig_mask_2)
image_2, mask_2 = self.resize_image_and_mask(image_2, mask_2, w, h)
_, img1_h, img1_w, _ = image_1.shape
_, img2_h, img2_w, _ = image_2.shape
@@ -1986,9 +2026,7 @@ class makeImageForICRepaint:
scale_factor = img2_w / img1_w
height = round(img1_h * scale_factor)
image_1 = image_1.movedim(-1, 1)
image_1 = comfy.utils.common_upscale(image_1, width, height, 'bicubic', 'disabled')
image_1 = image_1.movedim(1, -1)
image_1, mask_1 = self.resize_image_and_mask(image_1, mask_1, width, height)
if mask_1 is None:
mask_1 = torch.full((1, image_1.shape[1], image_1.shape[2]), 0, dtype=torch.float32, device="cpu")
+1 -1
View File
@@ -18,7 +18,7 @@ import comfy.utils
import folder_paths
DEFAULT_FLOW_NUM = 2
MAX_FLOW_NUM = 10
MAX_FLOW_NUM = 20
lazy_options = {"lazy": True} if compare_revision(2543) else {}
any_type = AlwaysEqualProxy("*")
+56 -12
View File
@@ -1,12 +1,15 @@
import os
import json
import folder_paths
import os
from urllib.request import urlopen
from ..libs.log import log_node_info
from ..libs.wildcards import get_wildcard_list, process
from ..libs.utils import AlwaysEqualProxy
from ..config import RESOURCES_DIR, FOOOCUS_STYLES_DIR, MAX_SEED_NUM, PROMPT_TEMPLATE
import folder_paths
from .. import easyCache
from ..config import FOOOCUS_STYLES_DIR, MAX_SEED_NUM, PROMPT_TEMPLATE, RESOURCES_DIR
from ..libs.log import log_node_info
from ..libs.utils import AlwaysEqualProxy
from ..libs.wildcards import WildcardProcessor, get_wildcard_list, process
# 正面提示词
class positivePrompt:
@@ -40,7 +43,7 @@ class wildcardsPrompt:
def INPUT_TYPES(s):
wildcard_list = get_wildcard_list()
return {"required": {
"text": ("STRING", {"default": "", "multiline": True, "dynamicPrompts": False, "placeholder": "(Support Lora Block Weight and wildcard)"}),
"text": ("STRING", {"default": "", "multiline": True, "dynamicPrompts": False, "placeholder": "(Support wildcard)"}),
"Select to add LoRA": (["Select the LoRA to add to the text"] + folder_paths.get_filename_list("loras"),),
"Select to add Wildcard": (["Select the Wildcard to add to the text"] + wildcard_list,),
"seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
@@ -56,9 +59,6 @@ class wildcardsPrompt:
CATEGORY = "EasyUse/Prompt"
def translate(self, text):
return text
def main(self, *args, **kwargs):
prompt = kwargs["prompt"] if "prompt" in kwargs else None
seed = kwargs["seed"]
@@ -73,16 +73,58 @@ class wildcardsPrompt:
_text = []
text = text.split("\n")
for t in text:
t = self.translate(t)
_text.append(t)
populated_text.append(process(t, seed))
text = _text
else:
text = self.translate(text)
populated_text = [process(text, seed)]
text = [text]
return {"ui": {"value": [seed]}, "result": (text, populated_text)}
# 通配符提示词矩阵,会按顺序返回包含通配符的提示词所生成的所有可能
class wildcardsPromptMatrix:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
wildcard_list = get_wildcard_list()
return {"required": {
"text": ("STRING", {"default": "", "multiline": True, "dynamicPrompts": False, "placeholder": "(Support Lora Block Weight and wildcard)"}),
"Select to add LoRA": (["Select the LoRA to add to the text"] + folder_paths.get_filename_list("loras"),),
"Select to add Wildcard": (["Select the Wildcard to add to the text"] + wildcard_list,),
"offset": ("INT", {"default": 0, "min": 0, "step": 1, "control_after_generate": True}),
},
"optional":{
"output_limit": ("INT", {"default": 1, "min": -1, "step": 1, "tooltip": "Output All Probilities"})
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
RETURN_TYPES = ("STRING", "INT", "INT")
RETURN_NAMES = ("populated_text", "total", "factors")
OUTPUT_IS_LIST = (True, False, True)
FUNCTION = "main"
CATEGORY = "EasyUse/Prompt"
def main(self, *args, **kwargs):
prompt = kwargs["prompt"] if "prompt" in kwargs else None
offset = kwargs["offset"]
output_limit = kwargs.get("output_limit", 1)
# Clean loaded_objects
if prompt:
easyCache.update_loaded_objects(prompt)
text = kwargs['text']
p = WildcardProcessor(text)
total = p.total()
limit = total if output_limit > total or output_limit == -1 else output_limit
offset = 0 if output_limit == -1 else offset
populated_text = p.getmany(limit, offset) if output_limit != 1 else [p.getn(offset)]
return {"ui": {"value": [offset]}, "result": (populated_text, p.total(), list(p.placeholder_choices.values()))}
# 负面提示词
class negativePrompt:
@@ -518,6 +560,7 @@ NODE_CLASS_MAPPINGS = {
"easy positive": positivePrompt,
"easy negative": negativePrompt,
"easy wildcards": wildcardsPrompt,
"easy wildcardsMatrix": wildcardsPromptMatrix,
"easy prompt": prompt,
"easy promptList": promptList,
"easy promptLine": promptLine,
@@ -531,6 +574,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"easy positive": "Positive",
"easy negative": "Negative",
"easy wildcards": "Wildcards",
"easy wildcardsMatrix": "Wildcards Matrix",
"easy prompt": "Prompt",
"easy promptList": "PromptList",
"easy promptLine": "PromptLine",
+6
View File
@@ -389,6 +389,12 @@ class samplerFull:
"loader_settings": {
**pipe["loader_settings"],
"steps": steps,
"cfg": cfg,
"sampler_name": sampler_name,
"scheduler": scheduler,
"denoise": denoise,
"add_noise": add_noise,
"spent_time": spent_time
}
}
-23
View File
@@ -146,29 +146,6 @@ async def getModelsList(request):
else:
return web.Response(status=400)
# get models thumbnails
@PromptServer.instance.routes.get("/easyuse/models/thumbnail")
async def getModelsThumbnail(request):
limit = 500
if "limit" in request.rel_url.query:
limit = request.rel_url.query.get("limit")
limit = int(limit)
checkpoints = folder_paths.get_filename_list("checkpoints_thumb")
loras = folder_paths.get_filename_list("loras_thumb")
checkpoints_full = []
loras_full = []
if len(checkpoints) + len(loras) >= limit:
return web.Response(status=400)
for index, i in enumerate(checkpoints):
full_path = folder_paths.get_full_path('checkpoints_thumb', str(i))
if full_path:
checkpoints_full.append(full_path)
for index, i in enumerate(loras):
full_path = folder_paths.get_full_path('loras_thumb', str(i))
if full_path:
loras_full.append(full_path)
return web.json_response(checkpoints_full + loras_full)
@PromptServer.instance.routes.post("/easyuse/metadata/notes/{name}")
async def save_notes(request):
name = request.match_info["name"]
+1 -1
View File
@@ -1,7 +1,7 @@
[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.2.9"
version = "1.3.0"
license = { file = "LICENSE" }
dependencies = ["diffusers", "accelerate", "clip_interrogator>=0.6.0", "sentencepiece", "lark", "onnxruntime", "spandrel", "opencv-python", "matplotlib", "peft"]
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long