Upgrade to v1.0.4
This commit is contained in:
+143
-2
@@ -127,6 +127,7 @@
|
||||
"inputs": {
|
||||
"optional_lora_stack": "Lora堆(可选)",
|
||||
"model_override": "模型(可选)",
|
||||
"vae_override": "VAE(可选)",
|
||||
"clip_override": "CLIP(可选)"
|
||||
},
|
||||
"outputs": {
|
||||
@@ -265,7 +266,9 @@
|
||||
"widgets": {
|
||||
"control_net_name": "ControlNet",
|
||||
"strength": "强度",
|
||||
"start_percent": "开始时间"
|
||||
"start_percent": "开始时间",
|
||||
"end_percent": "结束时间",
|
||||
"scale_soft_weights": "缩放柔和权重"
|
||||
},
|
||||
"inputs": {
|
||||
"pipe": "节点束",
|
||||
@@ -1112,6 +1115,45 @@
|
||||
"image": "图像"
|
||||
}
|
||||
},
|
||||
"easy imageScaleDown": {
|
||||
"title": "图像缩小",
|
||||
"inputs": {
|
||||
"images": "图像"
|
||||
},
|
||||
"widgets": {
|
||||
"width": "宽度",
|
||||
"height": "高度",
|
||||
"crop": "裁切"
|
||||
},
|
||||
"outputs": {
|
||||
"IMAGE": "图像"
|
||||
}
|
||||
},
|
||||
"easy imageScaleDownBy": {
|
||||
"title": "图像缩小(按比例)",
|
||||
"inputs": {
|
||||
"images": "图像"
|
||||
},
|
||||
"widgets": {
|
||||
"scale_by": "缩小比例"
|
||||
},
|
||||
"outputs": {
|
||||
"IMAGE": "图像"
|
||||
}
|
||||
},
|
||||
"easy imageScaleDownToSize": {
|
||||
"title": "图像缩小(按边)",
|
||||
"inputs": {
|
||||
"images": "图像"
|
||||
},
|
||||
"widgets": {
|
||||
"size": "尺寸",
|
||||
"mode": "模式"
|
||||
},
|
||||
"outputs": {
|
||||
"IMAGE": "图像"
|
||||
}
|
||||
},
|
||||
"easy imagePixelPerfect": {
|
||||
"title": "图像完美像素",
|
||||
"widgets": {
|
||||
@@ -1124,7 +1166,28 @@
|
||||
"image": "图像"
|
||||
},
|
||||
"outputs": {
|
||||
"resolution": "分辨率(整数)"
|
||||
"resolution": "分辨率(整数)"
|
||||
}
|
||||
},
|
||||
"easy imageSave": {
|
||||
"title": "图像保存",
|
||||
"inputs":{
|
||||
"images": "图像"
|
||||
},
|
||||
"widgets": {
|
||||
"filename_prefix": "图片名前缀"
|
||||
}
|
||||
},
|
||||
"easy joinImageBatch": {
|
||||
"title": "合并图像批次",
|
||||
"inputs": {
|
||||
"images": "图像"
|
||||
},
|
||||
"widgets": {
|
||||
"mode": "合并模式"
|
||||
},
|
||||
"outputs": {
|
||||
"image": "图像"
|
||||
}
|
||||
},
|
||||
"easy poseEditor": {
|
||||
@@ -1143,5 +1206,83 @@
|
||||
"inputs": {
|
||||
"pipe": "节点束"
|
||||
}
|
||||
},
|
||||
"easy string": {
|
||||
"title": "字符串",
|
||||
"widgets": {
|
||||
"value": "值"
|
||||
}
|
||||
},
|
||||
"easy int": {
|
||||
"title": "整数",
|
||||
"widgets": {
|
||||
"value": "值"
|
||||
}
|
||||
},
|
||||
"easy rangeInt": {
|
||||
"title": "整数(范围)",
|
||||
"widgets": {
|
||||
"range_mode": "范围模式",
|
||||
"start": "开始",
|
||||
"stop": "终止",
|
||||
"step": "步进",
|
||||
"end_mode": "结束模式",
|
||||
"num_steps": "步进数量"
|
||||
}
|
||||
},
|
||||
"easy float": {
|
||||
"title": "浮点数",
|
||||
"widgets": {
|
||||
"value": "值"
|
||||
}
|
||||
},
|
||||
"easy rangeFloat": {
|
||||
"title": "浮点数(范围)",
|
||||
"widgets": {
|
||||
"range_mode": "范围模式",
|
||||
"start": "开始",
|
||||
"stop": "终止",
|
||||
"step": "步进",
|
||||
"end_mode": "结束模式",
|
||||
"num_steps": "步进数量"
|
||||
}
|
||||
},
|
||||
"easy boolean": {
|
||||
"title": "布尔",
|
||||
"widgets": {
|
||||
"value": "值"
|
||||
}
|
||||
},
|
||||
"easy compare": {
|
||||
"title": "比较",
|
||||
"widgets": {
|
||||
"comparison": "比较方式"
|
||||
}
|
||||
},
|
||||
"easy if": {
|
||||
"title": "If判断",
|
||||
"inputs": {
|
||||
"any": "输入任何"
|
||||
}
|
||||
},
|
||||
"easy xyAny": {
|
||||
"widgets": {
|
||||
"direction": "方向"
|
||||
}
|
||||
},
|
||||
"easy convertAnything": {
|
||||
"title": "转换任何",
|
||||
"inputs": {
|
||||
"anything": "输入任何"
|
||||
},
|
||||
"widgets": {
|
||||
"output_type": "输出类型"
|
||||
}
|
||||
},
|
||||
"easy showAnything": {
|
||||
"title": "展示任何",
|
||||
"inputs": {
|
||||
"anything": "输入任何"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,14 @@ After installing the node package, the UI interface will be automatically switch
|
||||
|
||||
## Changelog
|
||||
|
||||
**v1.0.4(2024-01-28)**
|
||||
|
||||
- Added **Logic** nodes
|
||||
- Added `easy imageSave` - Image saving node with date conversion and aspect and height formatting
|
||||
- Added `easy joinImageBatch`
|
||||
- Fixed `easy XYInputs: ControlNet` Error
|
||||
- Fixed `easy loraStack` error when **toggle** is disabled
|
||||
|
||||
**2024-01-22**
|
||||
|
||||
- Fixed `easy XYInputs: Sampler/Scheduler` Error
|
||||
|
||||
@@ -37,17 +37,27 @@
|
||||
|
||||
## 更新日志
|
||||
|
||||
**v1.0.4(2024-01-28)**
|
||||
|
||||
- 增加 **Logic** 逻辑类节点
|
||||
- 增加 `easy imageSave` - 带日期转换和宽高格式化的图像保存节点
|
||||
- 增加 `easy joinImageBatch` - 合并图像批次
|
||||
- 修复 `easy XYInputs: ControlNet` 报错
|
||||
- 修复 `easy loraStack` **toggle** 为 disabled 时报错
|
||||
|
||||
**2024-01-22**
|
||||
|
||||
- 修复 `easy XYInputs: Sampler/Scheduler` 报错
|
||||
- 修复 右侧菜单 点击按钮时老是跑位的问题
|
||||
- 调整UI主题,分为官方默认背景和深黑色背景两套样式,可在设置里调色板里切换(调整原因:深黑色背景对亮度较暗的用户和UP主不太友好)
|
||||
|
||||
**2024-01-21**
|
||||
<details>
|
||||
<summary>2024-01-21</summary>>
|
||||
|
||||
- 修改 styles 路径以兼容其他环境
|
||||
- 修复 `easy comfyLoader` 读取错误
|
||||
- 修复 xyPlot 在连接 zero123 时报错
|
||||
</details>
|
||||
|
||||
**v1.0.3(2024-01-19)**
|
||||
|
||||
@@ -215,6 +225,7 @@
|
||||
| dynamicThresholdingFull | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull |
|
||||
| easy imageInsetCrop | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | ImageInsetCrop |
|
||||
| easy poseEditor | [ComfyUI_Custom_Nodes_AlekPet](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet) | poseNode |
|
||||
| easy if | [ComfyUI-Logic](https://github.com/theUpsider/ComfyUI-Logic) | IfExecute |
|
||||
|
||||
## 示例
|
||||
|
||||
@@ -240,9 +251,10 @@
|
||||
|
||||
[tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) - 管道节点(节点束)让用户减少了不必要的连接
|
||||
|
||||
[ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) - diffus3的获取与设置点让用户可以分离工作流构成
|
||||
|
||||
[ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) - diffus3的获取与设置点让用户可以分离工作流构成
|
||||
|
||||
[ComfyUI-Impact-Pack](https://github.com/ltdrdata/ComfyUI-Impact-Pack) - 常规整合包1
|
||||
|
||||
[ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) - 常规整合包2
|
||||
[ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) - 常规整合包2
|
||||
|
||||
[ComfyUI-Logic](https://github.com/theUpsider/ComfyUI-Logic) - ComfyUI逻辑运算
|
||||
+2
-1
@@ -10,7 +10,8 @@ node_list = [
|
||||
"api",
|
||||
"easyNodes",
|
||||
"image",
|
||||
"lllite"
|
||||
"lllite",
|
||||
"logic"
|
||||
]
|
||||
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
|
||||
+67
-52
@@ -18,11 +18,6 @@ import latent_preview
|
||||
import comfy.model_base
|
||||
import comfy.model_management
|
||||
from comfy.sd import CLIP, VAE
|
||||
import comfy.clip_vision
|
||||
from comfy.clip_vision import ClipVisionModel
|
||||
import comfy.clip_model
|
||||
import comfy.sd1_clip
|
||||
from comfy.sd1_clip import escape_important, token_weights, unescape_important
|
||||
from pathlib import Path
|
||||
from urllib.request import urlopen
|
||||
from collections import defaultdict
|
||||
@@ -31,9 +26,6 @@ from PIL import Image, ImageDraw, ImageFont
|
||||
from comfy.model_patcher import ModelPatcher
|
||||
from comfy_extras.chainner_models import model_loading
|
||||
from typing import Dict, List, Optional, Tuple, Union, Any
|
||||
from transformers import CLIPImageProcessor
|
||||
from transformers.image_utils import PILImageResampling
|
||||
from itertools import zip_longest
|
||||
from .adv_encode import advanced_encode, advanced_encode_XL
|
||||
|
||||
from server import PromptServer
|
||||
@@ -496,9 +488,7 @@ class easyXYPlot:
|
||||
value_label = f"{value_type}: {value}"
|
||||
|
||||
if "ControlNet" in value_type:
|
||||
if "," in value:
|
||||
line = value.split(',')
|
||||
value_label = f"{value_type}: {line[2]}"
|
||||
value_label = f"ControlNet {index + 1}"
|
||||
|
||||
if value_type in ["ModelMergeBlocks"]:
|
||||
if ":" in value:
|
||||
@@ -757,6 +747,9 @@ class easyXYPlot:
|
||||
w_max=1.0,
|
||||
apply_to_pooled="enable")
|
||||
positive = [[positive, {"pooled_output": positive_pooled}]]
|
||||
if "positive_cond" in plot_image_vars:
|
||||
positive = positive + plot_image_vars["positive_cond"]
|
||||
|
||||
if "Negative" in self.x_type or "Negative" in self.y_type:
|
||||
if self.x_type == 'Negative Prompt S/R' or self.y_type == 'Negative Prompt S/R':
|
||||
negative = x_value if self.x_type == "Negative Prompt S/R" else y_value
|
||||
@@ -779,6 +772,8 @@ class easyXYPlot:
|
||||
w_max=1.0,
|
||||
apply_to_pooled="enable")
|
||||
negative = [[negative, {"pooled_output": negative_pooled}]]
|
||||
if "negative_cond" in plot_image_vars:
|
||||
positive = positive + plot_image_vars["negative_cond"]
|
||||
|
||||
# ControlNet
|
||||
if "ControlNet" in self.x_type or "ControlNet" in self.y_type:
|
||||
@@ -794,20 +789,15 @@ class easyXYPlot:
|
||||
}
|
||||
cnet = plot_image_vars["cnet"] if "cnet" in plot_image_vars else None
|
||||
if cnet:
|
||||
strength, start_percent, end_percent = x_value.split(',') if "ControlNet" in self.x_type else y_value.split(',')
|
||||
strength = float(strength)
|
||||
start_percent = float(start_percent)
|
||||
end_percent = float(end_percent)
|
||||
for index, item in enumerate(cnet):
|
||||
control_net_names = item[0]
|
||||
index = x_value if "ControlNet" in self.x_type else y_value
|
||||
controlnet = cnet[index]
|
||||
for index, item in enumerate(controlnet):
|
||||
control_net_name = item[0]
|
||||
image = item[1]
|
||||
for idx, control_net_name in enumerate(control_net_names):
|
||||
# print(control_net_name)
|
||||
_pipe, = controlnetAdvanced().controlnetApply(_pipe, image, control_net_name, None, strength, start_percent,
|
||||
end_percent)
|
||||
|
||||
positive = _pipe['positive']
|
||||
negative = _pipe['negative']
|
||||
strength = item[2]
|
||||
start_percent = item[3]
|
||||
end_percent = item[4]
|
||||
_pipe, positive, negative = controlnetAdvanced().controlnetApply(_pipe, image, control_net_name, None, strength, start_percent, end_percent, 1)
|
||||
|
||||
del _pipe
|
||||
|
||||
@@ -1382,7 +1372,7 @@ class stylesPromptSelector:
|
||||
|
||||
CATEGORY = 'EasyUse/Prompt'
|
||||
FUNCTION = 'run'
|
||||
OUTPUT_MODE = True
|
||||
OUTPUT_NODE = True
|
||||
|
||||
|
||||
def replace_repeat(self, prompt):
|
||||
@@ -1727,7 +1717,7 @@ class latentCompositeMaskedWithCond:
|
||||
RETURN_TYPES = ("PIPE_LINE", "LATENT", "CONDITIONING")
|
||||
RETURN_NAMES = ("pipe", "latent", "conditioning",)
|
||||
FUNCTION = "run"
|
||||
OUTPUT_MODE = True
|
||||
OUTPUT_NODE = True
|
||||
|
||||
CATEGORY = "EasyUse/Latent"
|
||||
|
||||
@@ -2363,7 +2353,7 @@ class loraStackLoader:
|
||||
|
||||
def stack(self, toggle, mode, num_loras, lora_stack=None, **kwargs):
|
||||
if (toggle in [False, None, "False"]) or not kwargs:
|
||||
return None
|
||||
return (None,)
|
||||
|
||||
loras = []
|
||||
|
||||
@@ -2387,12 +2377,45 @@ class loraStackLoader:
|
||||
loras.append((lora_name, model_strength, clip_strength))
|
||||
return (loras,)
|
||||
|
||||
class controlnetNameStack:
|
||||
|
||||
def get_file_list(filenames):
|
||||
return [file for file in filenames if file != "put_models_here.txt" and "lllite" not in file]
|
||||
|
||||
controlnets = ["None"] + get_file_list(folder_paths.get_filename_list("controlnet"))
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {},
|
||||
"optional": {
|
||||
"switch_1": (["Off", "On"],),
|
||||
"controlnet_1": (s.controlnets,),
|
||||
"controlnet_strength_1": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
"start_percent_1": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
||||
"end_percent_1": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
||||
"switch_2": (["Off", "On"],),
|
||||
"controlnet_2": (s.controlnets,),
|
||||
"controlnet_strength_2": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
"start_percent_2": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
||||
"end_percent_2": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
||||
"switch_3": (["Off", "On"],),
|
||||
"controlnet_3": (s.controlnets,),
|
||||
"controlnet_strength_3": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
"start_percent_3": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
||||
"end_percent_3": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
||||
"image_1": ("IMAGE",),
|
||||
"image_2": ("IMAGE",),
|
||||
"image_3": ("IMAGE",),
|
||||
"controlnet_stack": ("CONTROL_NET_STACK",)
|
||||
},
|
||||
}
|
||||
# controlnet
|
||||
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",),
|
||||
@@ -2516,7 +2539,7 @@ class controlnetAdvanced:
|
||||
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=0):
|
||||
def controlnetApply(self, pipe, image, control_net_name, control_net=None, strength=1, start_percent=0, end_percent=1, scale_soft_weights=1):
|
||||
if control_net is None:
|
||||
if scale_soft_weights < 1:
|
||||
if "ScaledSoftControlNetWeights" in ALL_NODE_CLASS_MAPPINGS:
|
||||
@@ -4615,9 +4638,8 @@ class pipeXYPlotAdvanced:
|
||||
x_values = []
|
||||
cnet = []
|
||||
for index, value in enumerate(x_value):
|
||||
cnet_stack, image, strength, start_percent, end_percent = value
|
||||
cnet.append((cnet_stack, image,), )
|
||||
x_values.append((",".join((str(strength), str(start_percent), str(end_percent),))),)
|
||||
cnet.append(value)
|
||||
x_values.append(str(index))
|
||||
x_values = "; ".join(x_values)
|
||||
new_pipe['loader_settings'] = {
|
||||
**pipe['loader_settings'],
|
||||
@@ -4629,9 +4651,8 @@ class pipeXYPlotAdvanced:
|
||||
y_values = []
|
||||
cnet = []
|
||||
for index, value in enumerate(y_value):
|
||||
cnet_stack, image, strength, start_percent, end_percent = value
|
||||
cnet.append((cnet_stack, image,),)
|
||||
y_values.append((",".join((str(strength), str(start_percent), str(end_percent),))),)
|
||||
cnet.append(value)
|
||||
y_values.append(str(index))
|
||||
y_values = "; ".join(y_values)
|
||||
new_pipe['loader_settings'] = {
|
||||
**pipe['loader_settings'],
|
||||
@@ -5021,49 +5042,43 @@ class XYplot_Control_Net:
|
||||
|
||||
axis, = None,
|
||||
|
||||
# If cnet_stack is provided, extend each inner array with its content
|
||||
# if cnet_stack:
|
||||
# for inner_list in values:
|
||||
# inner_list.extend(cnet_stack)
|
||||
cnet_stack = [control_net_name]
|
||||
|
||||
values = []
|
||||
|
||||
if target_parameter == "strength":
|
||||
axis = "advanced: ControlNetStrength"
|
||||
|
||||
values.append((cnet_stack, image, first_strength, start_percent, end_percent))
|
||||
values.append([(control_net_name, image, first_strength, start_percent, end_percent)])
|
||||
strength_increment = (last_strength - first_strength) / (batch_count - 1) if batch_count > 1 else 0
|
||||
for i in range(1, batch_count - 1):
|
||||
values.append((cnet_stack, image, first_strength + i * strength_increment, start_percent,
|
||||
end_percent))
|
||||
values.append([(control_net_name, image, first_strength + i * strength_increment, start_percent,
|
||||
end_percent)])
|
||||
if batch_count > 1:
|
||||
values.append((cnet_stack, image, last_strength, start_percent, end_percent))
|
||||
values.append([(control_net_name, image, last_strength, start_percent, end_percent)])
|
||||
|
||||
elif target_parameter == "start_percent":
|
||||
axis = "advanced: ControlNetStart%"
|
||||
|
||||
percent_increment = (last_start_percent - first_start_percent) / (batch_count - 1) if batch_count > 1 else 0
|
||||
values.append((cnet_stack, image, strength, first_start_percent, end_percent))
|
||||
values.append([(control_net_name, image, strength, first_start_percent, end_percent)])
|
||||
for i in range(1, batch_count - 1):
|
||||
values.append((cnet_stack, image, strength, first_start_percent + i * percent_increment,
|
||||
end_percent))
|
||||
values.append([(control_net_name, image, strength, first_start_percent + i * percent_increment,
|
||||
end_percent)])
|
||||
|
||||
# Always add the last start_percent if batch_count is more than 1.
|
||||
if batch_count > 1:
|
||||
values.append((cnet_stack, image, strength, last_start_percent, end_percent))
|
||||
values.append((control_net_name, image, strength, last_start_percent, end_percent))
|
||||
|
||||
elif target_parameter == "end_percent":
|
||||
axis = "advanced: ControlNetEnd%"
|
||||
|
||||
percent_increment = (last_end_percent - first_end_percent) / (batch_count - 1) if batch_count > 1 else 0
|
||||
values.append((cnet_stack, image, strength, start_percent, first_end_percent))
|
||||
values.append([(control_net_name, image, image, strength, start_percent, first_end_percent)])
|
||||
for i in range(1, batch_count - 1):
|
||||
values.append((cnet_stack, image, strength, start_percent,
|
||||
first_end_percent + i * percent_increment))
|
||||
values.append([(control_net_name, image, strength, start_percent,
|
||||
first_end_percent + i * percent_increment)])
|
||||
|
||||
if batch_count > 1:
|
||||
values.append((cnet_stack, image, strength, start_percent, last_end_percent))
|
||||
values.append([(control_net_name, image, strength, start_percent, last_end_percent)])
|
||||
|
||||
|
||||
return ({"axis": axis, "values": values},)
|
||||
|
||||
+221
@@ -1,5 +1,9 @@
|
||||
from PIL import Image
|
||||
from PIL.PngImagePlugin import PngInfo
|
||||
from enum import Enum
|
||||
import datetime
|
||||
import re
|
||||
import json
|
||||
import os
|
||||
import hashlib
|
||||
import folder_paths
|
||||
@@ -218,6 +222,109 @@ class imageSizeByLongerSide:
|
||||
result = (0,)
|
||||
return {"ui": {"text": str(result[0])}, "result": result}
|
||||
|
||||
# 图像缩放
|
||||
class imageScaleDown:
|
||||
crop_methods = ["disabled", "center"]
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"width": (
|
||||
"INT",
|
||||
{"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 1},
|
||||
),
|
||||
"height": (
|
||||
"INT",
|
||||
{"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 1},
|
||||
),
|
||||
"crop": (s.crop_methods,),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
CATEGORY = "EasyUse/Image"
|
||||
FUNCTION = "image_scale_down"
|
||||
|
||||
def image_scale_down(self, images, width, height, crop):
|
||||
if crop == "center":
|
||||
old_width = images.shape[2]
|
||||
old_height = images.shape[1]
|
||||
old_aspect = old_width / old_height
|
||||
new_aspect = width / height
|
||||
x = 0
|
||||
y = 0
|
||||
if old_aspect > new_aspect:
|
||||
x = round((old_width - old_width * (new_aspect / old_aspect)) / 2)
|
||||
elif old_aspect < new_aspect:
|
||||
y = round((old_height - old_height * (old_aspect / new_aspect)) / 2)
|
||||
s = images[:, y: old_height - y, x: old_width - x, :]
|
||||
else:
|
||||
s = images
|
||||
|
||||
results = []
|
||||
for image in s:
|
||||
img = tensor2pil(image).convert("RGB")
|
||||
img = img.resize((width, height), Image.LANCZOS)
|
||||
results.append(pil2tensor(img))
|
||||
|
||||
return (torch.cat(results, dim=0),)
|
||||
|
||||
# 图像缩放比例
|
||||
class imageScaleDownBy(imageScaleDown):
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"scale_by": (
|
||||
"FLOAT",
|
||||
{"default": 0.5, "min": 0.01, "max": 1.0, "step": 0.01},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
CATEGORY = "EasyUse/Image"
|
||||
FUNCTION = "image_scale_down_by"
|
||||
|
||||
def image_scale_down_by(self, images, scale_by):
|
||||
width = images.shape[2]
|
||||
height = images.shape[1]
|
||||
new_width = int(width * scale_by)
|
||||
new_height = int(height * scale_by)
|
||||
return self.image_scale_down(images, new_width, new_height, "center")
|
||||
|
||||
# 图像缩放尺寸
|
||||
class imageScaleDownToSize(imageScaleDownBy):
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"size": ("INT", {"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 1}),
|
||||
"mode": ("BOOLEAN", {"default": True, "label_on": "max", "label_off": "min"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
CATEGORY = "EasyUse/Image"
|
||||
FUNCTION = "image_scale_down_to_size"
|
||||
|
||||
def image_scale_down_to_size(self, images, size, mode):
|
||||
width = images.shape[2]
|
||||
height = images.shape[1]
|
||||
|
||||
if mode:
|
||||
scale_by = size / max(width, height)
|
||||
else:
|
||||
scale_by = size / min(width, height)
|
||||
|
||||
scale_by = min(scale_by, 1.0)
|
||||
return self.image_scale_down_by(images, scale_by)
|
||||
|
||||
|
||||
# 图像完美像素
|
||||
class imagePixelPerfect:
|
||||
@classmethod
|
||||
@@ -296,6 +403,110 @@ class imageToMask:
|
||||
image = pil2tensor(image)
|
||||
return (image.squeeze().mean(2),)
|
||||
|
||||
# 图像保存 (简易)
|
||||
from comfy.cli_args import args
|
||||
class imageSaveSimple:
|
||||
|
||||
def __init__(self):
|
||||
self.output_dir = folder_paths.get_output_directory()
|
||||
self.type = "output"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"images": ("IMAGE",),
|
||||
"filename_prefix": ("STRING", {"default": "ComfyUI"}),},
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "save"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "EasyUse/Image"
|
||||
|
||||
@staticmethod
|
||||
def _format_date(text: str, date: datetime.datetime) -> str:
|
||||
"""Format the date according to specific patterns."""
|
||||
date_formats = {
|
||||
'd': lambda d: d.day,
|
||||
'dd': lambda d: '{:02d}'.format(d.day),
|
||||
'M': lambda d: d.month,
|
||||
'MM': lambda d: '{:02d}'.format(d.month),
|
||||
'h': lambda d: d.hour,
|
||||
'hh': lambda d: '{:02d}'.format(d.hour),
|
||||
'm': lambda d: d.minute,
|
||||
'mm': lambda d: '{:02d}'.format(d.minute),
|
||||
's': lambda d: d.second,
|
||||
'ss': lambda d: '{:02d}'.format(d.second),
|
||||
'y': lambda d: d.year,
|
||||
'yy': lambda d: str(d.year)[2:],
|
||||
'yyy': lambda d: str(d.year)[1:],
|
||||
'yyyy': lambda d: d.year,
|
||||
}
|
||||
|
||||
# We need to sort the keys in reverse order to ensure we match the longest formats first
|
||||
for format_str in sorted(date_formats.keys(), key=len, reverse=True):
|
||||
if format_str in text:
|
||||
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):
|
||||
|
||||
filename_prefix = re.sub(r'%date:(.*?)%', lambda m: self._format_date(m.group(1), datetime.datetime.now()),
|
||||
filename_prefix)
|
||||
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()
|
||||
for image in images:
|
||||
img = Image.fromarray(np.clip(255. * image.cpu().numpy(), 0, 255).astype(np.uint8))
|
||||
filename = filename.replace("%width%", str(img.size[0])).replace("%height%", str(img.size[1]))
|
||||
|
||||
metadata = None
|
||||
metadata = PngInfo()
|
||||
if prompt is not None:
|
||||
metadata.add_text("prompt", json.dumps(prompt))
|
||||
if extra_pnginfo is not None:
|
||||
for x in extra_pnginfo:
|
||||
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)
|
||||
results.append({
|
||||
"filename": file,
|
||||
"subfolder": subfolder,
|
||||
"type": self.type
|
||||
})
|
||||
counter += 1
|
||||
|
||||
return { "ui": { "images": results } }
|
||||
# 图像批次合并
|
||||
class JoinImageBatch:
|
||||
"""Turns an image batch into one big image."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"mode": (("horizontal", "vertical"), {"default": "horizontal"}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("image",)
|
||||
FUNCTION = "join"
|
||||
CATEGORY = "EasyUse/Image"
|
||||
|
||||
def join(self, images, mode):
|
||||
n, h, w, c = images.shape
|
||||
image = None
|
||||
if mode == "vertical":
|
||||
# for vertical we can just reshape
|
||||
image = images.reshape(1, n * h, w, c)
|
||||
elif mode == "horizontal":
|
||||
# for horizontal we have to swap axes
|
||||
image = torch.transpose(torch.transpose(images, 1, 2).reshape(1, n * w, h, c), 1, 2)
|
||||
return (image,)
|
||||
|
||||
# 姿势编辑器
|
||||
class poseEditor:
|
||||
@@ -345,7 +556,12 @@ NODE_CLASS_MAPPINGS = {
|
||||
"easy imageSizeBySide": imageSizeBySide,
|
||||
"easy imageSizeByLongerSide": imageSizeByLongerSide,
|
||||
"easy imagePixelPerfect": imagePixelPerfect,
|
||||
"easy imageScaleDown": imageScaleDown,
|
||||
"easy imageScaleDownBy": imageScaleDownBy,
|
||||
"easy imageScaleDownToSize": imageScaleDownToSize,
|
||||
"easy imageToMask": imageToMask,
|
||||
"easy imageSave": imageSaveSimple,
|
||||
"easy joinImageBatch": JoinImageBatch,
|
||||
"easy poseEditor": poseEditor
|
||||
}
|
||||
|
||||
@@ -355,7 +571,12 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"easy imageSizeBySide": "ImageSize (Side)",
|
||||
"easy imageSizeByLongerSide": "ImageSize (LongerSide)",
|
||||
"easy imagePixelPerfect": "ImagePixelPerfect",
|
||||
"easy imageScaleDown": "Image Scale Down",
|
||||
"easy imageScaleDownBy": "Image Scale Down By",
|
||||
"easy imageScaleDownToSize": "Image Scale Down To Size",
|
||||
"easy imageToMask": "ImageToMask",
|
||||
"easy imageHSVMask": "ImageHSVMask",
|
||||
"easy imageSave": "SaveImage (Simple)",
|
||||
"easy joinImageBatch": "JoinImageBatch",
|
||||
"easy poseEditor": "PoseEditor"
|
||||
}
|
||||
+424
@@ -0,0 +1,424 @@
|
||||
from typing import Iterator, List, Tuple, Dict, Any, Union, Optional
|
||||
from _decimal import Context, getcontext
|
||||
from decimal import Decimal
|
||||
import numpy as np
|
||||
|
||||
def validate_list_args(args: Dict[str, List[Any]]) -> Tuple[bool, Optional[str], Optional[str]]:
|
||||
"""
|
||||
Checks that if there are multiple arguments, they are all the same length or 1
|
||||
:param args:
|
||||
:return: Tuple (Status, mismatched_key_1, mismatched_key_2)
|
||||
"""
|
||||
# Only have 1 arg
|
||||
if len(args) == 1:
|
||||
return True, None, None
|
||||
|
||||
len_to_match = None
|
||||
matched_arg_name = None
|
||||
for arg_name, arg in args.items():
|
||||
if arg_name == 'self':
|
||||
# self is in locals()
|
||||
continue
|
||||
|
||||
if len(arg) != 1:
|
||||
if len_to_match is None:
|
||||
len_to_match = len(arg)
|
||||
matched_arg_name = arg_name
|
||||
elif len(arg) != len_to_match:
|
||||
return False, arg_name, matched_arg_name
|
||||
|
||||
return True, None, None
|
||||
def error_if_mismatched_list_args(args: Dict[str, List[Any]]) -> None:
|
||||
is_valid, failed_key1, failed_key2 = validate_list_args(args)
|
||||
if not is_valid:
|
||||
assert failed_key1 is not None
|
||||
assert failed_key2 is not None
|
||||
raise ValueError(
|
||||
f"Mismatched list inputs received. {failed_key1}({len(args[failed_key1])}) !== {failed_key2}({len(args[failed_key2])})"
|
||||
)
|
||||
|
||||
def zip_with_fill(*lists: Union[List[Any], None]) -> Iterator[Tuple[Any, ...]]:
|
||||
"""
|
||||
Zips lists together, but if a list has 1 element, it will be repeated for each element in the other lists.
|
||||
If a list is None, None will be used for that element.
|
||||
(Not intended for use with lists of different lengths)
|
||||
:param lists:
|
||||
:return: Iterator of tuples of length len(lists)
|
||||
"""
|
||||
max_len = max(len(lst) if lst is not None else 0 for lst in lists)
|
||||
for i in range(max_len):
|
||||
yield tuple(None if lst is None else (lst[0] if len(lst) == 1 else lst[i]) for lst in lists)
|
||||
|
||||
# ---------------------------------------------------------------类型 开始----------------------------------------------------------------------#
|
||||
|
||||
# 字符串
|
||||
class String:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {"value": ("STRING", {"default": ""})},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("string",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "EasyUse/Logic/Type"
|
||||
|
||||
def execute(self, value):
|
||||
return (value,)
|
||||
|
||||
# 整数
|
||||
class Int:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {"value": ("INT", {"default": 0})},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
RETURN_NAMES = ("int",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "EasyUse/Logic/Type"
|
||||
|
||||
def execute(self, value):
|
||||
return (value,)
|
||||
|
||||
# 整数范围
|
||||
class RangeInt:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s) -> Dict[str, Dict[str, Any]]:
|
||||
return {
|
||||
"required": {
|
||||
"range_mode": (["step", "num_steps"], {"default": "step"}),
|
||||
"start": ("INT", {"default": 0, "min": -4096, "max": 4096, "step": 1}),
|
||||
"stop": ("INT", {"default": 0, "min": -4096, "max": 4096, "step": 1}),
|
||||
"step": ("INT", {"default": 0, "min": -4096, "max": 4096, "step": 1}),
|
||||
"num_steps": ("INT", {"default": 0, "min": -4096, "max": 4096, "step": 1}),
|
||||
"end_mode": (["Inclusive", "Exclusive"], {"default": "Inclusive"}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT", "INT")
|
||||
RETURN_NAMES = ("range", "range_sizes")
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True, True)
|
||||
FUNCTION = "build_range"
|
||||
|
||||
CATEGORY = "EasyUse/Logic/Type"
|
||||
|
||||
def build_range(
|
||||
self, range_mode, start, stop, step, num_steps, end_mode
|
||||
) -> Tuple[List[int], List[int]]:
|
||||
error_if_mismatched_list_args(locals())
|
||||
|
||||
ranges = []
|
||||
range_sizes = []
|
||||
for range_mode, e_start, e_stop, e_num_steps, e_step, e_end_mode in zip_with_fill(
|
||||
range_mode, start, stop, num_steps, step, end_mode
|
||||
):
|
||||
if range_mode == 'step':
|
||||
if e_end_mode == "Inclusive":
|
||||
e_stop += 1
|
||||
vals = list(range(e_start, e_stop, e_step))
|
||||
ranges.extend(vals)
|
||||
range_sizes.append(len(vals))
|
||||
elif range_mode == 'num_steps':
|
||||
direction = 1 if e_stop > e_start else -1
|
||||
if e_end_mode == "Exclusive":
|
||||
e_stop -= direction
|
||||
vals = (np.rint(np.linspace(e_start, e_stop, e_num_steps)).astype(int).tolist())
|
||||
ranges.extend(vals)
|
||||
range_sizes.append(len(vals))
|
||||
return ranges, range_sizes
|
||||
|
||||
|
||||
|
||||
# 浮点数
|
||||
class Float:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {"value": ("FLOAT", {"default": 0, "step": 0.01})},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("FLOAT",)
|
||||
RETURN_NAMES = ("float",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "EasyUse/Logic/Type"
|
||||
|
||||
def execute(self, value):
|
||||
return (value,)
|
||||
|
||||
|
||||
# 浮点数范围
|
||||
class RangeFloat:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s) -> Dict[str, Dict[str, Any]]:
|
||||
return {
|
||||
"required": {
|
||||
"range_mode": (["step", "num_steps"], {"default": "step"}),
|
||||
"start": ("FLOAT", {"default": 0, "min": -4096, "max": 4096, "step": 0.1}),
|
||||
"stop": ("FLOAT", {"default": 0, "min": -4096, "max": 4096, "step": 0.1}),
|
||||
"step": ("FLOAT", {"default": 0, "min": -4096, "max": 4096, "step": 0.1}),
|
||||
"num_steps": ("INT", {"default": 0, "min": -4096, "max": 4096, "step": 1}),
|
||||
"end_mode": (["Inclusive", "Exclusive"], {"default": "Inclusive"}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("FLOAT", "INT")
|
||||
RETURN_NAMES = ("range", "range_sizes")
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True, True)
|
||||
FUNCTION = "build_range"
|
||||
|
||||
CATEGORY = "EasyUse/Logic/Type"
|
||||
|
||||
@staticmethod
|
||||
def _decimal_range(
|
||||
range_mode: String, start: Decimal, stop: Decimal, step: Decimal, num_steps: Int, inclusive: bool
|
||||
) -> Iterator[float]:
|
||||
if range_mode == 'step':
|
||||
ret_val = start
|
||||
if inclusive:
|
||||
stop = stop + step
|
||||
direction = 1 if step > 0 else -1
|
||||
while (ret_val - stop) * direction < 0:
|
||||
yield float(ret_val)
|
||||
ret_val += step
|
||||
elif range_mode == 'num_steps':
|
||||
step = (stop - start) / (num_steps - 1)
|
||||
direction = 1 if step > 0 else -1
|
||||
|
||||
ret_val = start
|
||||
for _ in range(num_steps):
|
||||
if (ret_val - stop) * direction > 0: # Ensure we don't exceed the 'stop' value
|
||||
break
|
||||
yield float(ret_val)
|
||||
ret_val += step
|
||||
|
||||
def build_range(
|
||||
self,
|
||||
range_mode,
|
||||
start,
|
||||
stop,
|
||||
step,
|
||||
num_steps,
|
||||
end_mode,
|
||||
) -> Tuple[List[float], List[int]]:
|
||||
error_if_mismatched_list_args(locals())
|
||||
getcontext().prec = 12
|
||||
|
||||
start = [Decimal(s) for s in start]
|
||||
stop = [Decimal(s) for s in stop]
|
||||
step = [Decimal(s) for s in step]
|
||||
|
||||
ranges = []
|
||||
range_sizes = []
|
||||
for range_mode, e_start, e_stop, e_step, e_num_steps, e_end_mode in zip_with_fill(
|
||||
range_mode, start, stop, step, num_steps, end_mode
|
||||
):
|
||||
vals = list(
|
||||
self._decimal_range(range_mode, e_start, e_stop, e_step, e_num_steps, e_end_mode == 'Inclusive')
|
||||
)
|
||||
ranges.extend(vals)
|
||||
range_sizes.append(len(vals))
|
||||
|
||||
return ranges, range_sizes
|
||||
|
||||
|
||||
# 布尔
|
||||
class Boolean:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {"value": ("BOOLEAN", {"default": False})},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("BOOLEAN",)
|
||||
RETURN_NAMES = ("boolean",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "EasyUse/Logic/Type"
|
||||
|
||||
def execute(self, value):
|
||||
return (value,)
|
||||
|
||||
# ---------------------------------------------------------------运算 开始----------------------------------------------------------------------#
|
||||
|
||||
COMPARE_FUNCTIONS = {
|
||||
"a == b": lambda a, b: a == b,
|
||||
"a != b": lambda a, b: a != b,
|
||||
"a < b": lambda a, b: a < b,
|
||||
"a > b": lambda a, b: a > b,
|
||||
"a <= b": lambda a, b: a <= b,
|
||||
"a >= b": lambda a, b: a >= b,
|
||||
}
|
||||
class AlwaysEqualProxy(str):
|
||||
def __eq__(self, _):
|
||||
return True
|
||||
|
||||
def __ne__(self, _):
|
||||
return False
|
||||
|
||||
# 比较
|
||||
class Compare:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
s.compare_functions = list(COMPARE_FUNCTIONS.keys())
|
||||
return {
|
||||
"required": {
|
||||
"a": (AlwaysEqualProxy("*"), {"default": 0}),
|
||||
"b": (AlwaysEqualProxy("*"), {"default": 0}),
|
||||
"comparison": (s.compare_functions, {"default": "a == b"}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("BOOLEAN",)
|
||||
RETURN_NAMES = ("boolean",)
|
||||
FUNCTION = "compare"
|
||||
CATEGORY = "EasyUse/Logic/Math"
|
||||
|
||||
def compare(self, a, b, comparison):
|
||||
return (COMPARE_FUNCTIONS[comparison](a, b),)
|
||||
|
||||
# 判断
|
||||
class If:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"any": (AlwaysEqualProxy("*"),),
|
||||
"if": (AlwaysEqualProxy("*"),),
|
||||
"else": (AlwaysEqualProxy("*"),),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (AlwaysEqualProxy("*"),)
|
||||
RETURN_NAMES = ("?",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "EasyUse/Logic/Math"
|
||||
|
||||
def execute(self, *args, **kwargs):
|
||||
return (kwargs['if'] if kwargs['any'] else kwargs['else'],)
|
||||
|
||||
#xy矩阵
|
||||
class xyAny:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"X": (AlwaysEqualProxy("*"), {}),
|
||||
"Y": (AlwaysEqualProxy("*"), {}),
|
||||
"direction": (["horizontal", "vertical"], {"default": "horizontal"})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (AlwaysEqualProxy("*"), AlwaysEqualProxy("*"))
|
||||
RETURN_NAMES = ("X", "Y")
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True, True)
|
||||
CATEGORY = "EasyUse/Logic"
|
||||
FUNCTION = "to_xy"
|
||||
|
||||
def to_xy(self, X, Y, direction):
|
||||
new_x = list()
|
||||
new_y = list()
|
||||
if direction[0] == "horizontal":
|
||||
for y in Y:
|
||||
for x in X:
|
||||
new_x.append(x)
|
||||
new_y.append(y)
|
||||
else:
|
||||
for x in X:
|
||||
for y in Y:
|
||||
new_x.append(x)
|
||||
new_y.append(y)
|
||||
|
||||
return (new_x, new_y)
|
||||
|
||||
# 转换所有类型
|
||||
class ConvertAnything:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"anything": (AlwaysEqualProxy("*"),),
|
||||
"output_type": (["string", "int", "float", "boolean"], {"default": "string"}),
|
||||
}}
|
||||
|
||||
RETURN_TYPES = (AlwaysEqualProxy("*"),),
|
||||
RETURN_NAMES = ('*',)
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "convert"
|
||||
CATEGORY = "EasyUse/Logic"
|
||||
|
||||
def convert(self, *args, **kwargs):
|
||||
print(kwargs)
|
||||
anything = kwargs['anything']
|
||||
output_type = kwargs['output_type']
|
||||
params = None
|
||||
if output_type == 'string':
|
||||
params = str(anything)
|
||||
elif output_type == 'int':
|
||||
params = int(anything)
|
||||
elif output_type == 'float':
|
||||
params = float(anything)
|
||||
elif output_type == 'boolean':
|
||||
params = bool(anything)
|
||||
return (params,)
|
||||
|
||||
# 将所有类型的内容都转成字符串输出
|
||||
class ShowAnything:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {}, "optional": {"anything": (AlwaysEqualProxy("*"), {}), },
|
||||
"hidden": {"unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO",
|
||||
}}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "log_input"
|
||||
CATEGORY = "EasyUse/Logic"
|
||||
|
||||
def log_input(self, unique_id=None, extra_pnginfo=None, **kwargs):
|
||||
print(kwargs['anything'])
|
||||
if unique_id and extra_pnginfo and "workflow" in extra_pnginfo[0]:
|
||||
workflow = extra_pnginfo[0]["workflow"]
|
||||
node = next((x for x in workflow["nodes"] if str(x["id"]) == unique_id[0]), None)
|
||||
if node:
|
||||
node["widgets_values"] = [kwargs['anything']]
|
||||
return {"ui": {"text": kwargs['anything']}}
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"easy string": String,
|
||||
"easy int": Int,
|
||||
"easy rangeInt": RangeInt,
|
||||
"easy float": Float,
|
||||
"easy rangeFloat": RangeFloat,
|
||||
"easy boolean": Boolean,
|
||||
"easy compare": Compare,
|
||||
"easy if": If,
|
||||
"easy xyAny": xyAny,
|
||||
"easy convertAnything": ConvertAnything,
|
||||
"easy showAnything": ShowAnything,
|
||||
}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"easy string": "String",
|
||||
"easy int": "Int",
|
||||
"easy rangeInt": "Range(Int)",
|
||||
"easy float": "Float",
|
||||
"easy rangeFloat": "Range(Float)",
|
||||
"easy boolean": "Boolean",
|
||||
"easy compare": "Compare",
|
||||
"easy if": "If",
|
||||
"easy xyAny": "XYAny",
|
||||
"easy convertAnything": "ConvertAnything",
|
||||
"easy showAnything": "ShowAnything"
|
||||
}
|
||||
+3
-1
@@ -384,7 +384,9 @@ app.registerExtension({
|
||||
e.currentTarget.parentNode.draggable = 'true';
|
||||
})
|
||||
text_group_title.addEventListener('mouseleave',e=>{
|
||||
isGroupMapcanMove = true
|
||||
setTimeout(_=>{
|
||||
isGroupMapcanMove = true
|
||||
},150)
|
||||
})
|
||||
group_item.append(text_group_title)
|
||||
// 按钮组
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { app } from "/scripts/app.js";
|
||||
import { api } from "/scripts/api.js";
|
||||
import { ComfyWidgets } from "/scripts/widgets.js";
|
||||
|
||||
|
||||
let origProps = {};
|
||||
|
||||
@@ -149,6 +151,17 @@ function widgetLogic(node, widget) {
|
||||
updateNodeHeight(node)
|
||||
}
|
||||
|
||||
if (widget.name == 'range_mode'){
|
||||
if(widget.value == 'step'){
|
||||
toggleWidget(node, findWidgetByName(node, 'step'), true)
|
||||
toggleWidget(node, findWidgetByName(node, 'num_steps'))
|
||||
}else if(widget.value == 'num_steps'){
|
||||
toggleWidget(node, findWidgetByName(node, 'step'))
|
||||
toggleWidget(node, findWidgetByName(node, 'num_steps'), true)
|
||||
}
|
||||
updateNodeHeight(node)
|
||||
}
|
||||
|
||||
if (widget.name === 'toggle') {
|
||||
widget.type = 'toggle'
|
||||
widget.options = {on: 'Enabled', off: 'Disabled'}
|
||||
@@ -329,6 +342,8 @@ app.registerExtension({
|
||||
case "easy XYInputs: Sampler/Scheduler":
|
||||
case "easy XYInputs: PromptSR":
|
||||
case "easy XYInputs: ControlNet":
|
||||
case "easy rangeInt":
|
||||
case "easy rangeFloat":
|
||||
getSetters(node)
|
||||
break
|
||||
case "easy wildcards":
|
||||
@@ -509,7 +524,6 @@ app.registerExtension({
|
||||
accept: "text/csv",
|
||||
style: "display: none",
|
||||
onchange: async (event) => {
|
||||
|
||||
if (fileInput.files.length) {
|
||||
await uploadFile(fileInput.files[0], true);
|
||||
event.target.value = ''
|
||||
@@ -646,6 +660,68 @@ app.registerExtension({
|
||||
},1)
|
||||
}
|
||||
}
|
||||
|
||||
if(nodeData.name == 'easy showAnything'){
|
||||
function populate(text) {
|
||||
if (this.widgets) {
|
||||
const pos = this.widgets.findIndex((w) => w.name === "text");
|
||||
if (pos !== -1) {
|
||||
for (let i = pos; i < this.widgets.length; i++) {
|
||||
this.widgets[i].onRemove?.();
|
||||
}
|
||||
this.widgets.length = pos;
|
||||
}
|
||||
}
|
||||
|
||||
for (const list of text) {
|
||||
const w = ComfyWidgets["STRING"](this, "text", ["STRING", { multiline: true }], app).widget;
|
||||
w.inputEl.readOnly = true;
|
||||
w.inputEl.style.opacity = 0.6;
|
||||
w.value = list;
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const sz = this.computeSize();
|
||||
if (sz[0] < this.size[0]) {
|
||||
sz[0] = this.size[0];
|
||||
}
|
||||
if (sz[1] < this.size[1]) {
|
||||
sz[1] = this.size[1];
|
||||
}
|
||||
this.onResize?.(sz);
|
||||
app.graph.setDirtyCanvas(true, false);
|
||||
});
|
||||
}
|
||||
|
||||
// When the node is executed we will be sent the input text, display this in the widget
|
||||
const onExecuted = nodeType.prototype.onExecuted;
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments);
|
||||
populate.call(this, message.text);
|
||||
};
|
||||
|
||||
const onConfigure = nodeType.prototype.onConfigure;
|
||||
nodeType.prototype.onConfigure = function () {
|
||||
onConfigure?.apply(this, arguments);
|
||||
if (this.widgets_values?.length) {
|
||||
populate.call(this, this.widgets_values);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if(nodeData.name == 'easy convertAnything'){
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = async function () {
|
||||
onNodeCreated ? onNodeCreated.apply(this, []) : undefined;
|
||||
const type_control = this.widgets[this.widgets.findIndex((w) => w.name === "output_type")]
|
||||
let _this = this
|
||||
type_control.callback = _ => {
|
||||
_this.outputs[0].type = (type_control.value).toUpperCase()
|
||||
_this.outputs[0].name = type_control.value
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -655,7 +731,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"]
|
||||
'num_loras', 'mode', 'toggle', 'resolution', 'target_parameter', 'input_count', 'replace_count', 'downscale_mode', 'range_mode']
|
||||
|
||||
function getSetters(node) {
|
||||
if (node.widgets)
|
||||
|
||||
@@ -54,15 +54,16 @@ app.registerExtension({
|
||||
name: 'comfy.easyUse.quick',
|
||||
init() {
|
||||
const keybindListener = async function (event) {
|
||||
const modifierPressed = event.altKey;
|
||||
let modifierPressed = event.altKey;
|
||||
const isEnabled = getEnableNodeTemplateShortcut()
|
||||
if(isEnabled){
|
||||
const mac_alt_nums = ['¡','™','£','¢','∞','§','¶','•','ª']
|
||||
const nums = ['1','2','3','4','5','6','7','8','9']
|
||||
let key = event.key
|
||||
if(mac_alt_nums.includes(key)){
|
||||
const idx = mac_alt_nums.findIndex(key)
|
||||
const idx = mac_alt_nums.findIndex(cate=> cate == key)
|
||||
key = nums[idx]
|
||||
modifierPressed = true
|
||||
}
|
||||
if(['1','2','3','4','5','6','7','8','9'].includes(key) && modifierPressed) {
|
||||
const template = loadTemplate()
|
||||
|
||||
+4
-4
@@ -210,7 +210,7 @@ app.registerExtension({
|
||||
|
||||
this.addOutput("*", '*');
|
||||
|
||||
|
||||
|
||||
this.onConnectionsChange = function(
|
||||
slotType, //0 = output, 1 = input
|
||||
slot, //self-explanatory
|
||||
@@ -224,13 +224,13 @@ app.registerExtension({
|
||||
},1)
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.setName = function(name) {
|
||||
node.widgets[0].value = name;
|
||||
node.onRename();
|
||||
node.serialize();
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.onRename = function() {
|
||||
const setter = this.findSetter(node.graph);
|
||||
@@ -256,7 +256,7 @@ app.registerExtension({
|
||||
node.graph.removeLink(linkId)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.setType = function(type) {
|
||||
|
||||
Reference in New Issue
Block a user