add FilterSortDependSubGraphs and SortDependSubGraphs
This commit is contained in:
@@ -88,6 +88,8 @@ Tips: base64格式字符串比较长,会导致界面卡顿,接口请求带
|
||||
| × | TryFreeMemory | 回收内(显)存 |
|
||||
| × | IfElseForEmptyObject | 可以对list类型进行判断 |
|
||||
| × | ImageSizeGetter | 获取图片尺寸(宽、高、最大边、最小边、批次) |
|
||||
| × | FilterSortDependSubGraphs | 使前置依赖子图按指定顺序执行(且只执行配置的前置依赖),如:配置filter_sort为1,4,3 表示按 depend_1 => depend_4 => depend_3 依次执行, 而depend_2不会被执行。 |
|
||||
| × | SortDependSubGraphs | 使前置依赖子图按指定顺序执行(未配置的依赖输入项在指定项后按默认顺序执行),如:配置sort为1,4 表示先按 depend_1 => depend_4 依次执行, 然后depend_3和depend_2按默认顺序执行。 |
|
||||
|
||||
### 示例
|
||||

|
||||
@@ -101,6 +103,10 @@ Tips: base64格式字符串比较长,会导致界面卡顿,接口请求带
|
||||

|
||||
|
||||
## 更新记录
|
||||
### 2025-03-28 (v1.1.2)
|
||||
- 新增节点 ImageSizeGetter、FilterSortDependSubGraphs、SortDependSubGraphs
|
||||

|
||||

|
||||
### 2025-02-19 (v1.1.1)
|
||||
- 新增节点:IsNoneOrEmptyOptional
|
||||
### 2024-12-01 (v1.1.0)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 439 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 432 KiB |
+2
-14
@@ -1,5 +1,5 @@
|
||||
from comfy_execution.graph_utils import GraphBuilder, is_link
|
||||
from .util import any_type
|
||||
from .util import any_type, find_max_suffix_number
|
||||
|
||||
# 支持的最大参数个数
|
||||
NUM_FLOW_SOCKETS = 20
|
||||
@@ -165,19 +165,7 @@ class InnerLoopClose:
|
||||
|
||||
|
||||
def find_max_initial_value_number(kwargs, substring):
|
||||
# 提取所有键
|
||||
keys = list(kwargs.keys())
|
||||
|
||||
# 筛选出形如 'initial_valueX' 的键
|
||||
matching_keys = [key for key in keys if key.startswith('initial_value')]
|
||||
|
||||
# 从匹配的键中提取数字部分
|
||||
numbers = [int(key[len('initial_value'):]) for key in matching_keys]
|
||||
|
||||
# 找到最大数字
|
||||
max_number = max(numbers) if numbers else 1
|
||||
|
||||
return max_number
|
||||
return find_max_suffix_number(kwargs, substring)
|
||||
|
||||
|
||||
class ForEachOpen:
|
||||
|
||||
+119
-16
@@ -12,7 +12,7 @@ from comfy.model_patcher import ModelPatcher
|
||||
import comfy.model_base
|
||||
import comfy.model_management as mm
|
||||
from server import PromptServer
|
||||
from .util import tensor_to_pil, hex_to_rgba, any_type
|
||||
from .util import hex_to_rgba, any_type, find_max_suffix_number
|
||||
|
||||
|
||||
class GetImageBatchSize:
|
||||
@@ -178,10 +178,8 @@ class ListMerge:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"list_a": ('LIST', {"forceInput": True}),
|
||||
},
|
||||
"optional": {
|
||||
"list_a": ('LIST', {"forceInput": True}),
|
||||
"list_b": ('LIST', {"forceInput": True}),
|
||||
}
|
||||
}
|
||||
@@ -193,9 +191,11 @@ class ListMerge:
|
||||
OUTPUT_NODE = False
|
||||
CATEGORY = "EasyApi/List"
|
||||
|
||||
DESCRIPTION = "合并两个列表。如 [1,2] 和 [3,4] => [1,2,3,4]"
|
||||
DESCRIPTION = "合并两个列表。如 [1,2] 和 [3,4] => [1,2,3,4], 都不存在时返回空列表"
|
||||
|
||||
def convert(self, list_a, list_b=None):
|
||||
def convert(self, list_a=None, list_b=None):
|
||||
if list_a is None:
|
||||
list_a = []
|
||||
list = [] + list_a
|
||||
if list_b:
|
||||
list = list + list_b
|
||||
@@ -416,10 +416,9 @@ class SDBaseVerNumber:
|
||||
class ListWrapper:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {"required": {
|
||||
"any_1": (any_type, {"forceInput": True}),
|
||||
},
|
||||
return {
|
||||
"optional": {
|
||||
"any_1": (any_type, {"forceInput": True}),
|
||||
"any_2": (any_type, {"forceInput": True}),
|
||||
},
|
||||
}
|
||||
@@ -434,14 +433,16 @@ class ListWrapper:
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False, )
|
||||
DESCRIPTION = "把输入放到一个列表中,如 [a,b]和[c] => [[a,b],[c]]"
|
||||
DESCRIPTION = "把输入放到一个列表中,如 [a,b]和[c] => [[a,b],[c]], 都不存在时返回None"
|
||||
|
||||
def wrapper(self, any_1, any_2=None):
|
||||
if any_1 is None:
|
||||
def wrapper(self, any_1=None, any_2=None):
|
||||
if any_1 is None and any_2 is None:
|
||||
return None,
|
||||
else:
|
||||
if any_2 is None:
|
||||
return ([any_1,],)
|
||||
elif any_1 is None:
|
||||
return ([any_2,],)
|
||||
else:
|
||||
return ([any_1, any_2],)
|
||||
|
||||
@@ -598,18 +599,19 @@ class LoadJsonStrToList:
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
RETURN_TYPES = ("LIST", "INT", )
|
||||
RETURN_NAMES = ("LIST", "length", )
|
||||
CATEGORY = "EasyApi/Utils"
|
||||
FUNCTION = "load_json"
|
||||
|
||||
def load_json(self, json_str: str):
|
||||
if len(json_str.strip()) == 0:
|
||||
return ([],)
|
||||
return [], 0,
|
||||
json = simplejson.loads(json_str)
|
||||
if isinstance(json, (list, tuple)):
|
||||
return (json,)
|
||||
return json, len(json)
|
||||
else:
|
||||
return ([json],)
|
||||
return [json], 1
|
||||
|
||||
|
||||
class ConvertToJsonStr:
|
||||
@@ -1084,6 +1086,103 @@ class TryFreeMemory:
|
||||
return (a,)
|
||||
|
||||
|
||||
NUM_FLOW_SOCKETS = 20
|
||||
class FilterSortDependSubGraphs:
|
||||
def __init__(self):
|
||||
self.count = 0
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"filter_sort": ("STRING", {"default": "1", "tooltip": "按顺序执行指定的前置依赖子图,为空时都不执行,返回结果都为None"
|
||||
"\n如:1,4,3 表示按 depend_1 => depend_4 => depend_3 依次执行, 而depend_2不会被执行"}),
|
||||
},
|
||||
"optional": {
|
||||
"depend_%d" % i: (any_type, {"lazy": True}) for i in range(1, NUM_FLOW_SOCKETS)
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = tuple([any_type] * (NUM_FLOW_SOCKETS - 1))
|
||||
RETURN_NAMES = tuple(["value%d" % i for i in range(1, NUM_FLOW_SOCKETS)])
|
||||
FUNCTION = "sort_graph"
|
||||
|
||||
CATEGORY = "EasyApi/Logic"
|
||||
DESCRIPTION = ("使前置依赖子图按指定顺序执行(且只执行配置的前置依赖)\n"
|
||||
"如:配置filter_sort为1,4,3 表示按 depend_1 => depend_4 => depend_3 依次执行, 而depend_2不会被执行。")
|
||||
|
||||
def sort_graph(self, filter_sort, **kwargs):
|
||||
self.count = 0
|
||||
max_num = find_max_suffix_number(kwargs, "depend_")
|
||||
indexes = [int(item.strip()) for item in filter_sort.split(",") if len(item.strip()) > 0]
|
||||
|
||||
outputs = [kwargs.get("depend_%d" % i, None) if i in indexes else None for i in range(1, max_num + 1)]
|
||||
return outputs
|
||||
|
||||
def check_lazy_status(self, filter_sort, **kwargs):
|
||||
if filter_sort and len(filter_sort.strip()) > 0:
|
||||
indexes = [int(item.strip()) for item in filter_sort.split(",") if "depend_%d" % int(item.strip()) in kwargs.keys()]
|
||||
for i in indexes:
|
||||
if self.count < len(indexes):
|
||||
index = indexes[self.count]
|
||||
input_name = "depend_%d" % index
|
||||
self.count += 1
|
||||
if kwargs.get(input_name, None) is None:
|
||||
# 找到没有缓存的输入项,解决靠后的依赖节点变化后没有被执行的问题
|
||||
return [input_name]
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
class SortDependSubGraphs:
|
||||
def __init__(self):
|
||||
self.count = 0
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"sort": ("STRING", {"default": "", "tooltip": "按顺序执行指定的前置依赖子图,为空时按默认顺序执行"
|
||||
"\n如:1,4 表示先按 depend_1 => depend_4 依次执行, 而depend_3和depend_2按默认顺序执行"}),
|
||||
},
|
||||
"optional": {
|
||||
"depend_%d" % i: (any_type, {"lazy": True}) for i in range(1, NUM_FLOW_SOCKETS)
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = tuple([any_type] * (NUM_FLOW_SOCKETS - 1))
|
||||
RETURN_NAMES = tuple(["value%d" % i for i in range(1, NUM_FLOW_SOCKETS)])
|
||||
FUNCTION = "sort_graph"
|
||||
|
||||
CATEGORY = "EasyApi/Logic"
|
||||
DESCRIPTION = ("使前置依赖子图按指定顺序执行(未配置的依赖输入项在指定项后按默认顺序执行)"
|
||||
"\n如:配置sort为1,4 表示先按 depend_1 => depend_4 依次执行, 而depend_3和depend_2按默认顺序执行。")
|
||||
|
||||
def sort_graph(self, sort, **kwargs):
|
||||
self.count = 0
|
||||
max_num = find_max_suffix_number(kwargs, "depend_")
|
||||
|
||||
outputs = [kwargs.get("depend_%d" % i, None) for i in range(1, max_num + 1)]
|
||||
return outputs
|
||||
|
||||
def check_lazy_status(self, sort, **kwargs):
|
||||
if sort and len(sort.strip()) > 0:
|
||||
indexes = [int(item.strip()) for item in sort.split(",") if "depend_%d" % int(item.strip()) in kwargs.keys()]
|
||||
for i in indexes:
|
||||
if self.count < len(indexes):
|
||||
index = indexes[self.count]
|
||||
input_name = "depend_%d" % index
|
||||
self.count += 1
|
||||
if kwargs.get(input_name, None) is None:
|
||||
# 找到没有缓存的输入项,解决靠后的依赖节点变化后没有被执行的问题
|
||||
return [input_name]
|
||||
|
||||
max_num = find_max_suffix_number(kwargs, "depend_")
|
||||
return ["depend_%d" % j for j in range(1, max_num + 1) if j not in indexes and "depend_%d" % j in kwargs.keys() and kwargs.get("depend_%d" % j, None) is None]
|
||||
else:
|
||||
return [i for i in kwargs.keys() if kwargs.get(i, None) is None]
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"GetImageBatchSize": GetImageBatchSize,
|
||||
"JoinList": JoinList,
|
||||
@@ -1121,6 +1220,8 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ReadTextFromLocalFile": ReadTextFromLocalFile,
|
||||
"TryFreeMemory": TryFreeMemory,
|
||||
"IfElseForEmptyObject": IfElseForEmptyObject,
|
||||
"FilterSortDependSubGraphs": FilterSortDependSubGraphs,
|
||||
"SortDependSubGraphs": SortDependSubGraphs,
|
||||
}
|
||||
|
||||
# A dictionary that contains the friendly/humanly readable titles for the nodes
|
||||
@@ -1161,4 +1262,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ReadTextFromLocalFile": "ReadTextFromLocalFile",
|
||||
"TryFreeMemory": "TryFreeMemory",
|
||||
"IfElseForEmptyObject": "If Else For Empty Object",
|
||||
"FilterSortDependSubGraphs": "Filter And Sort Depend SubGraphs",
|
||||
"SortDependSubGraphs": "Sort Depend SubGraphs",
|
||||
}
|
||||
|
||||
@@ -86,6 +86,22 @@ def hex_to_rgba(hex_color):
|
||||
return r, g, b, a
|
||||
|
||||
|
||||
def find_max_suffix_number(kwargs, substring):
|
||||
# 提取所有键
|
||||
keys = list(kwargs.keys())
|
||||
|
||||
# 筛选出形如 'initial_valueX' 的键
|
||||
matching_keys = [key for key in keys if key.startswith(substring)]
|
||||
|
||||
# 从匹配的键中提取数字部分
|
||||
numbers = [int(key[len(substring):]) for key in matching_keys]
|
||||
|
||||
# 找到最大数字
|
||||
max_number = max(numbers) if numbers else 1
|
||||
|
||||
return max_number
|
||||
|
||||
|
||||
class AnyType(str):
|
||||
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-easyapi-nodes"
|
||||
description = "Provides some features and nodes related to API calls. 开发独立应用调用ComfyUI服务的一些补充节点。"
|
||||
version = "1.1.1"
|
||||
version = "1.1.2"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = ["segment_anything", "simple_lama_inpainting", "insightface", "simplejson"]
|
||||
|
||||
|
||||
+18
-17
@@ -591,19 +591,20 @@ function removeOutSoltAndLink(node, out_slot_i) {
|
||||
}
|
||||
|
||||
|
||||
let filter_node_type = ['ForEachOpen', 'ForEachClose']
|
||||
let output_fixed_num_for_filter_node_type = [3, 0]
|
||||
let filter_node_type = ['ForEachOpen', 'ForEachClose', 'SortDependSubGraphs', 'FilterSortDependSubGraphs']
|
||||
let output_fixed_num_for_filter_node_type = [3, 0, 0, 0]
|
||||
let filter_node_type_input_prefix = ['initial_value', 'initial_value', 'depend_', 'depend_']
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.EasyApi.ForNode",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
|
||||
if (filter_node_type.indexOf(nodeData.name) > -1) {
|
||||
let input_name = "initial_value";
|
||||
let input_name = filter_node_type_input_prefix[filter_node_type.indexOf(nodeData.name)];
|
||||
let output_name = "value";
|
||||
let fixed_head_input_names = ["flow_control"];
|
||||
let fixed_tail_input_names = ["total"];
|
||||
// 比py代码中定义少1,因为不含initial_value0
|
||||
let fixed_tail_input_names = ["total", "filter_sort", 'sort'];
|
||||
// 不含initial_value0
|
||||
let max_number_of_inputs = 19;
|
||||
let out_fixed_num = output_fixed_num_for_filter_node_type[filter_node_type.indexOf(nodeData.name)];
|
||||
|
||||
@@ -643,15 +644,15 @@ app.registerExtension({
|
||||
if (fixed_tail_input_names.indexOf(this.inputs[index].name) > -1 || fixed_head_input_names.indexOf(this.inputs[index].name) > -1)
|
||||
return;
|
||||
|
||||
if (this.inputs[0].type == '*') {
|
||||
const node = app.graph.getNodeById(link_info.origin_id);
|
||||
let origin_type = node.outputs[link_info.origin_slot].type;
|
||||
|
||||
if (origin_type == '*') {
|
||||
this.disconnectInput(link_info.target_slot);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// if (this.inputs[0].type == '*') {
|
||||
// const node = app.graph.getNodeById(link_info.origin_id);
|
||||
// let origin_type = node.outputs[link_info.origin_slot].type;
|
||||
//
|
||||
// if (origin_type == '*') {
|
||||
// this.disconnectInput(link_info.target_slot);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
let fixed_head_solts = this.inputs.filter(x => fixed_head_input_names.indexOf(x.name) > -1);
|
||||
let fixed_head_solt_count = fixed_head_solts ? fixed_head_solts.length : 0;
|
||||
@@ -731,10 +732,10 @@ app.registerExtension({
|
||||
async nodeCreated(node, app) {
|
||||
// Fires every time a node is constructed
|
||||
// You can modify widgets/add handlers/etc here
|
||||
if (filter_node_type.indexOf(node.title) > -1) {
|
||||
let out_fixed_num = output_fixed_num_for_filter_node_type[filter_node_type.indexOf(node.title)];
|
||||
if (filter_node_type.indexOf(node.comfyClass) > -1) {
|
||||
let out_fixed_num = output_fixed_num_for_filter_node_type[filter_node_type.indexOf(node.comfyClass)];
|
||||
if (node.id == -1) {
|
||||
let input_name = "initial_value";
|
||||
let input_name = filter_node_type_input_prefix[filter_node_type.indexOf(node.comfyClass)];
|
||||
for (let i = node.inputs.length - 1; i >= 0; i--) {
|
||||
let index = node.inputs[i].name.indexOf(input_name);
|
||||
if (index == 0) {
|
||||
|
||||
Reference in New Issue
Block a user