diff --git a/ComfyUI-Easy-Use.json b/ComfyUI-Easy-Use.json index 6711810..97c6bae 100644 --- a/ComfyUI-Easy-Use.json +++ b/ComfyUI-Easy-Use.json @@ -42,6 +42,15 @@ "text": "提示词" } }, + "easy promptList": { + "title": "提示词列表", + "inputs": { + "optional_prompt_list": "列表(可选)" + }, + "outputs": { + "prompt_list": "列表" + } + }, "easy stylesSelector": { "title": "风格提示词选择器", "inputs": { @@ -330,10 +339,12 @@ "pipe": "节点束", "source_latent": "潜空间(源)", "source_mask": "遮罩(源)", - "new_mask": "遮罩(新)" + "destination_mask": "遮罩(目标)" }, "widgets": { - "text_combine": "文字拼接" + "text_combine": "文字拼接", + "text_combine_mode": "文字拼接模式", + "replace_text": "替换文字" }, "outputs": { "pipe": "节点束", @@ -756,6 +767,18 @@ "positive_4": "反向条件_4" } }, + "easy XYInputs: PositiveCondList": { + "title": "XY输入: 正向条件列表", + "inputs": { + "positive": "正向条件列表" + } + }, + "easy XYInputs: NegativeCondList": { + "title": "XY输入: 反向条件列表", + "inputs": { + "negative": "反向条件列表" + } + }, "easy XYInputs: PromptSR": { "title": "XY输入: 替换提示词", "widgets": { @@ -1178,7 +1201,8 @@ "images": "图像" }, "widgets": { - "filename_prefix": "图片名前缀" + "filename_prefix": "图片名前缀", + "only_preview": "仅预览" } }, "easy joinImageBatch": { diff --git a/README.en.md b/README.en.md index a106cc3..1199ff7 100644 --- a/README.en.md +++ b/README.en.md @@ -29,6 +29,13 @@ After installing the node package, the UI interface will be automatically switch ## Changelog +**2024-01-29** + +- Changing the first-time install node package no longer automatically replaces the theme, you need to manually adjust and refresh the page +- `easy imageSave` added **only_preivew** +- Added `easy promptList` +- Adjust the `easy latentCompositeMaskedWithCond` node + **v1.0.4(2024-01-28)** - `easy kSamplerInpainting` Added the **patch** input value to be used with the FooocusInpaintLoader node @@ -39,19 +46,23 @@ After installing the node package, the UI interface will be automatically switch - Fixed `easy XYInputs: ControlNet` Error - Fixed `easy loraStack` error when **toggle** is disabled -**2024-01-22** +
+2024-01-22 - Fixed `easy XYInputs: Sampler/Scheduler` Error - Fixed the right menu has a problem when clicking the button - Adjust the UI theme, divided into two sets of styles: the official default background and the dark black background, which can be switched in the color palette in the settings +
-**2024-01-21** +
+22024-01-21 - Modify the styles path to be compatible with other environments - Fixed `easy comfyLoader` error - Fixed xyPlot error when connecting to zero123 +
-**v1.0.3(2024-01-19)** +v1.0.3(2024-01-19)** - Fixed the error message in the loader when the prompt word was component - Added `easy stylesSelector` @@ -60,25 +71,33 @@ After installing the node package, the UI interface will be automatically switch - Fixed `easy getNode` and `easy setNode` the title does not change when loading - Fixed all samplers using subdirectories to store images -**2024-01-12** +
+2024-01-12 - Fixed UI loading failure in the new version of ComfyUI +
-**2024-01-11** +
+2024-01-11 - Replaced the font file used in the generation of XY diagrams +
-**2024-01-09** +
+2024-01-09 - Fixed XYPlot is not working when `a1111_prompt_style` is True - An `autocomplete` folder has been added, If you have [ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) installed, the txt files in that folder will be merged and overwritten to the autocomplete .txt file of the pyssss package at startup. - Added a `📜Groups Map(EasyUse)` to the context menu. +
-**2024-01-08** +
+2024-01-08 - `easy XYInputs ModelMergeBlocks` Values can be imported from CSV files - Fixed `easy pipeToBasicPipe` Bug - Removed `easy imageRemBg` +
**v1.0.2(2024-01-05)** diff --git a/README.md b/README.md index 1362266..224126c 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ ### UI界面美化 -安装节点包后会自动切换UI界面,如需更换其他主题请在 Settings -> Color Palette 中自行切换并刷新页面即可。 +首次安装的用户,如需使用本节点包UI主题,请在安装节点包在 Settings -> Color Palette 中自行切换并**刷新页面**即可。 ### 简体中文节点 @@ -37,6 +37,13 @@ ## 更新日志 +**2024-01-29** + +- 更改首次安装节点包不再自动替换主题,需手动调整并刷新页面 +- `easy imageSave` 增加 **only_preivew** +- 增加 `easy promptList` - 提示词列表 +- 修改 `easy latentCompositeMaskedWithCond` + **v1.0.4(2024-01-28)** - `easy kSamplerInpainting` 增加 **patch** 传入值,配合Fooocus内补节点使用 diff --git a/__init__.py b/__init__.py index 085ba97..de407f9 100644 --- a/__init__.py +++ b/__init__.py @@ -87,4 +87,4 @@ WEB_DIRECTORY = "./web" __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', "WEB_DIRECTORY"] -print('\033[34mComfy-Easy-Use (v1.0.3): \033[92mLoaded\033[0m') \ No newline at end of file +print('\033[34mComfy-Easy-Use (v1.0.4): \033[92mLoaded\033[0m') \ No newline at end of file diff --git a/py/easyNodes.py b/py/easyNodes.py index 0737e43..da72a1e 100644 --- a/py/easyNodes.py +++ b/py/easyNodes.py @@ -1394,6 +1394,44 @@ class stylesPromptSelector: return (positive_prompt, negative_prompt) +#promptList +class promptList: + @classmethod + def INPUT_TYPES(cls): + return {"required": { + "prompt_1": ("STRING", {"multiline": True, "default": ""}), + "prompt_2": ("STRING", {"multiline": True, "default": ""}), + "prompt_3": ("STRING", {"multiline": True, "default": ""}), + "prompt_4": ("STRING", {"multiline": True, "default": ""}), + "prompt_5": ("STRING", {"multiline": True, "default": ""}), + }, + "optional": { + "optional_prompt_list": ("LIST",) + } + } + + RETURN_TYPES = ("LIST",) + RETURN_NAMES = ("prompt_list",) + FUNCTION = "run" + CATEGORY = "EasyUse/Prompt" + + def run(self, **kwargs): + prompts = [] + + if "optional_prompt_list" in kwargs: + for l in kwargs["optional_prompt_list"]: + prompts.append(l) + + # Iterate over the received inputs in sorted order. + for k in sorted(kwargs.keys()): + v = kwargs[k] + + # Only process string input ports. + if isinstance(v, str) and v != '': + prompts.append(v) + + return (prompts,) + # 肖像大师 # Created by AI Wiz Art (Stefano Flore) # Version: 2.2 @@ -1669,14 +1707,17 @@ class latentCompositeMaskedWithCond: return { "required": { "pipe": ("PIPE_LINE",), - "text_combine": ("STRING", {"default": ""}), + "text_combine": ("LIST",), "source_latent": ("LATENT",), "source_mask": ("MASK",), - "new_mask": ("MASK",), + "destination_mask": ("MASK",), + "text_combine_mode": (["add", "replace", "cover"], {"default": "add"}), + "replace_text": ("STRING", {"default": ""}) }, "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, } + OUTPUT_IS_LIST = (False, False, True) RETURN_TYPES = ("PIPE_LINE", "LATENT", "CONDITIONING") RETURN_NAMES = ("pipe", "latent", "conditioning",) FUNCTION = "run" @@ -1684,43 +1725,53 @@ class latentCompositeMaskedWithCond: CATEGORY = "EasyUse/Latent" - def run(self, pipe, text_combine, source_latent, source_mask, new_mask, prompt=None, extra_pnginfo=None, my_unique_id=None): + def run(self, pipe, text_combine, source_latent, source_mask, destination_mask, text_combine_mode, replace_text, prompt=None, extra_pnginfo=None, my_unique_id=None): + clip = pipe["clip"] destination_latent = pipe["samples"] - positive = pipe["loader_settings"]["positive"] + ',' + text_combine - positive_token_normalization = pipe["loader_settings"]["positive_token_normalization"] - positive_weight_interpretation = pipe["loader_settings"]["positive_weight_interpretation"] - a1111_prompt_style = pipe["loader_settings"]["a1111_prompt_style"] - positive_cond = pipe["positive"] - log_node_warn("正在处理提示词编码...") - # Use new clip text encode by smzNodes like same as webui, when if you installed the smzNodes - if a1111_prompt_style: - if "smZ CLIPTextEncode" in ALL_NODE_CLASS_MAPPINGS: - cls = ALL_NODE_CLASS_MAPPINGS['smZ CLIPTextEncode'] - steps = pipe["loader_settings"]["steps"] if "steps" in pipe["loader_settings"] else 5 - positive_embeddings_final, = cls().encode(clip, positive, "A1111", True, True, False, False, 6, 1024, - 1024, 0, 0, 1024, 1024, '', '', steps) + conds = [] + + for text in text_combine: + if text_combine_mode == 'cover': + positive = text + elif text_combine_mode == 'replace' and replace_text != '': + positive = pipe["loader_settings"]["positive"].replace(replace_text, text) else: - raise Exception(f"[ERROR] To use clip text encode same as webui, you need to install 'smzNodes'") - else: - positive_embeddings_final, positive_pooled = advanced_encode(clip, positive, - positive_token_normalization, - positive_weight_interpretation, w_max=1.0, - apply_to_pooled='enable') - positive_embeddings_final = [[positive_embeddings_final, {"pooled_output": positive_pooled}]] + positive = pipe["loader_settings"]["positive"] + ',' + text + positive_token_normalization = pipe["loader_settings"]["positive_token_normalization"] + positive_weight_interpretation = pipe["loader_settings"]["positive_weight_interpretation"] + a1111_prompt_style = pipe["loader_settings"]["a1111_prompt_style"] + positive_cond = pipe["positive"] - # source cond - (cond_1,) = ConditioningSetMask().append(positive_cond, source_mask, "default", 1) - (cond_2,) = ConditioningSetMask().append(positive_embeddings_final, new_mask, "default", 1) - positive_cond = cond_1 + cond_2 + log_node_warn("正在处理提示词编码...") + # Use new clip text encode by smzNodes like same as webui, when if you installed the smzNodes + if a1111_prompt_style: + if "smZ CLIPTextEncode" in ALL_NODE_CLASS_MAPPINGS: + cls = ALL_NODE_CLASS_MAPPINGS['smZ CLIPTextEncode'] + steps = pipe["loader_settings"]["steps"] if "steps" in pipe["loader_settings"] else 5 + positive_embeddings_final, = cls().encode(clip, positive, "A1111", True, True, False, False, 6, 1024, + 1024, 0, 0, 1024, 1024, '', '', steps) + else: + raise Exception(f"[ERROR] To use clip text encode same as webui, you need to install 'smzNodes'") + else: + positive_embeddings_final, positive_pooled = advanced_encode(clip, positive, + positive_token_normalization, + positive_weight_interpretation, w_max=1.0, + apply_to_pooled='enable') + positive_embeddings_final = [[positive_embeddings_final, {"pooled_output": positive_pooled}]] + # source cond + (cond_1,) = ConditioningSetMask().append(positive_cond, source_mask, "default", 1) + (cond_2,) = ConditioningSetMask().append(positive_embeddings_final, destination_mask, "default", 1) + positive_cond = cond_1 + cond_2 + + conds.append(positive_cond) # latent composite masked (samples,) = LatentCompositeMasked().composite(destination_latent, source_latent, 0, 0, False) new_pipe = { **pipe, - "positive": positive_cond, "samples": samples, "loader_settings": { **pipe["loader_settings"], @@ -1730,7 +1781,7 @@ class latentCompositeMaskedWithCond: del pipe - return (new_pipe, samples, positive_cond) + return (new_pipe, samples, conds) # 随机种 class easySeed: @@ -4979,6 +5030,58 @@ class XYplot_Negative_Cond: return ({"axis": axis, "values": values, "cond": cond},) if values is not None else (None,) +# XYPlot Pos Condition List +class XYplot_Positive_Cond_List: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "positive": ("CONDITIONING",), + } + } + + INPUT_IS_LIST = True + RETURN_TYPES = ("X_Y",) + RETURN_NAMES = ("X or Y",) + FUNCTION = "xy_value" + CATEGORY = "EasyUse/XY Inputs" + + def xy_value(self, positive): + axis = "advanced: Pos Condition" + values = [] + cond = [] + for index, c in enumerate(positive): + values.append(str(index)) + cond.append(c) + + return ({"axis": axis, "values": values, "cond": cond},) if values is not None else (None,) + +# XYPlot Neg Condition List +class XYplot_Negative_Cond_List: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "negative": ("CONDITIONING",), + } + } + + INPUT_IS_LIST = True + RETURN_TYPES = ("X_Y",) + RETURN_NAMES = ("X or Y",) + FUNCTION = "xy_value" + CATEGORY = "EasyUse/XY Inputs" + + def xy_value(self, negative): + axis = "advanced: Neg Condition" + values = [] + cond = [] + for index, c in enumerate(negative): + values.append(index) + cond.append(c) + + return ({"axis": axis, "values": values, "cond": cond},) if values is not None else (None,) + # XY Plot: ControlNet class XYplot_Control_Net: parameters = ["strength", "start_percent", "end_percent"] @@ -5137,6 +5240,7 @@ NODE_CLASS_MAPPINGS = { "easy positive": positivePrompt, "easy negative": negativePrompt, "easy wildcards": wildcardsPrompt, + "easy promptList": promptList, "easy stylesSelector": stylesPromptSelector, "easy portraitMaster": portraitMaster, # loaders 加载器 @@ -5189,7 +5293,9 @@ NODE_CLASS_MAPPINGS = { "easy XYInputs: PromptSR": XYplot_PromptSR, "easy XYInputs: ControlNet": XYplot_Control_Net, "easy XYInputs: PositiveCond": XYplot_Positive_Cond, + "easy XYInputs: PositiveCondList": XYplot_Positive_Cond_List, "easy XYInputs: NegativeCond": XYplot_Negative_Cond, + "easy XYInputs: NegativeCondList": XYplot_Negative_Cond_List, # others 其他 "easy showSpentTime": showSpentTime, # "easy imageRemoveBG": imageREMBG, @@ -5202,6 +5308,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "easy positive": "Positive", "easy negative": "Negative", "easy wildcards": "Wildcards", + "easy promptList": "PromptList", "easy stylesSelector": "Styles Selector", "easy portraitMaster": "Portrait Master", # loaders 加载器 @@ -5254,8 +5361,10 @@ NODE_DISPLAY_NAME_MAPPINGS = { "easy XYInputs: ModelMergeBlocks": "XY Inputs: ModelMergeBlocks //EasyUse", "easy XYInputs: PromptSR": "XY Inputs: PromptSR //EasyUse", "easy XYInputs: ControlNet": "XY Inputs: Controlnet //EasyUse", - "easy XYInputs: PositiveCond": "XY Inputs: PositiveCond //EasyUse", - "easy XYInputs: NegativeCond": "XY Inputs: NegativeCond //EasyUse", + "easy XYInputs: PositiveCond": "XY Inputs: PosCond //EasyUse", + "easy XYInputs: PositiveCondList": "XY Inputs: PosCondList //EasyUse", + "easy XYInputs: NegativeCond": "XY Inputs: NegCond //EasyUse", + "easy XYInputs: NegativeCondList": "XY Inputs: NegCondList //EasyUse", # others 其他 "easy showSpentTime": "ShowSpentTime", "easy imageRemoveBG": "ImageRemoveBG", diff --git a/py/image.py b/py/image.py index d07a60c..b7e8854 100644 --- a/py/image.py +++ b/py/image.py @@ -2,6 +2,7 @@ from PIL import Image from PIL.PngImagePlugin import PngInfo from enum import Enum import datetime +import random import re import json import os @@ -410,12 +411,17 @@ class imageSaveSimple: def __init__(self): self.output_dir = folder_paths.get_output_directory() self.type = "output" + self.prefix_append = "" + self.compress_level = 4 @classmethod def INPUT_TYPES(s): return {"required": - {"images": ("IMAGE",), - "filename_prefix": ("STRING", {"default": "ComfyUI"}),}, + { + "images": ("IMAGE",), + "filename_prefix": ("STRING", {"default": "ComfyUI"}), + "only_preview": ("BOOLEAN", {"default": False}), + }, "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, } @@ -450,10 +456,23 @@ class imageSaveSimple: text = text.replace(format_str, str(date_formats[format_str](date))) return text - def save(self, images, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None): + def save(self, images, filename_prefix="ComfyUI", only_preview=False, prompt=None, extra_pnginfo=None): + + if only_preview: + self.output_dir = folder_paths.get_temp_directory() + self.prefix_append = "_temp_" + ''.join(random.choice("abcdefghijklmnopqrstupvxyz") for x in range(5)) + self.type = 'temp' + self.compress_level = 1 + else: + self.output_dir = folder_paths.get_output_directory() + self.type = "output" + self.prefix_append = "" + self.compress_level = 4 filename_prefix = re.sub(r'%date:(.*?)%', lambda m: self._format_date(m.group(1), datetime.datetime.now()), filename_prefix) + + filename_prefix += self.prefix_append full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]) results = list() @@ -470,7 +489,7 @@ class imageSaveSimple: metadata.add_text(x, json.dumps(extra_pnginfo[x])) file = f"{filename}_{counter:05}_.png" - img.save(os.path.join(full_output_folder, file), pnginfo=metadata) + img.save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=self.compress_level) results.append({ "filename": file, "subfolder": subfolder, diff --git a/py/logic.py b/py/logic.py index 8e4bbd5..cf8f0da 100644 --- a/py/logic.py +++ b/py/logic.py @@ -248,6 +248,33 @@ class Boolean: def execute(self, value): return (value,) +# ---------------------------------------------------------------开关 开始----------------------------------------------------------------------# +class imageSwitch: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image_a": ("IMAGE",), + "image_b": ("IMAGE",), + "boolean": ("BOOLEAN", {"default": False}), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "image_switch" + + CATEGORY = "EasyUse/Logic/Switch" + + def image_switch(self, image_a, image_b, boolean): + + if boolean: + return (image_a, ) + else: + return (image_b, ) + # ---------------------------------------------------------------运算 开始----------------------------------------------------------------------# COMPARE_FUNCTIONS = { @@ -404,6 +431,7 @@ NODE_CLASS_MAPPINGS = { "easy rangeFloat": RangeFloat, "easy boolean": Boolean, "easy compare": Compare, + "easy imageSwitch": imageSwitch, "easy if": If, "easy xyAny": xyAny, "easy convertAnything": ConvertAnything, @@ -417,6 +445,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "easy rangeFloat": "Range(Float)", "easy boolean": "Boolean", "easy compare": "Compare", + "easy imageSwitch": "Image Switch", "easy if": "If", "easy xyAny": "XYAny", "easy convertAnything": "ConvertAnything", diff --git a/web/js/easy/easyDynamicWidgets.js b/web/js/easy/easyDynamicWidgets.js index 68d6e19..5343fd5 100644 --- a/web/js/easy/easyDynamicWidgets.js +++ b/web/js/easy/easyDynamicWidgets.js @@ -167,6 +167,14 @@ function widgetLogic(node, widget) { widget.options = {on: 'Enabled', off: 'Disabled'} } + if(widget.name == 'text_combine_mode'){ + if(widget.value == 'replace'){ + toggleWidget(node, findWidgetByName(node, 'replace_text'), true) + }else{ + toggleWidget(node, findWidgetByName(node, 'replace_text')) + } + updateNodeHeight(node) + } } function widgetLogic2(node, widget) { @@ -344,6 +352,7 @@ app.registerExtension({ case "easy XYInputs: ControlNet": case "easy rangeInt": case "easy rangeFloat": + case 'easy latentCompositeMaskedWithCond': getSetters(node) break case "easy wildcards": @@ -731,7 +740,7 @@ const getSetWidgets = ['rescale_after_model', 'rescale', 'image_output', 'refiner_lora1_name', 'refiner_lora2_name', 'upscale_method', 'image_output', 'add_noise', 'info', 'sampler_name', 'ckpt_B_name', 'ckpt_C_name', 'save_model', 'refiner_ckpt_name', - 'num_loras', 'mode', 'toggle', 'resolution', 'target_parameter', 'input_count', 'replace_count', 'downscale_mode', 'range_mode'] + 'num_loras', 'mode', 'toggle', 'resolution', 'target_parameter', 'input_count', 'replace_count', 'downscale_mode', 'range_mode','text_combine_mode'] function getSetters(node) { if (node.widgets) diff --git a/web/js/easy/easyInterface.js b/web/js/easy/easyInterface.js index 54667eb..d0d8dba 100644 --- a/web/js/easy/easyInterface.js +++ b/web/js/easy/easyInterface.js @@ -106,7 +106,7 @@ try{ ui2.colors.litegraph_base.CLEAR_BACKGROUND_COLOR = '#000' custom_theme[ui2.id] = ui2 localStorage.setItem('Comfy.Settings.Comfy.CustomColorPalettes', JSON.stringify(custom_theme)); - localStorage.setItem('Comfy.Settings.Comfy.ColorPalette',`"${custom_theme_name}"`) + // localStorage.setItem('Comfy.Settings.Comfy.ColorPalette',`"${custom_theme_name}"`) } const theme_name = localStorage.getItem('Comfy.Settings.Comfy.ColorPalette') // 兼容 ComfyUI Revision: 1887 [235727fe] 以上版本 @@ -834,6 +834,7 @@ const COLOR_THEMES = LGraphCanvas.node_colors const NODE_COLORS = { "easy positive":"green", "easy negative":"red", + "easy promptList":"cyan", "easy XYInputs: Seeds++ Batch": customXYLink, "easy XYInputs: ModelMergeBlocks": customXYLink, }