- 新增 Grok Image 节点及客户端 - 新增 save_image_format 节点 - 新增前端 JS 扩展:画笔工具、点阵网格、侧边栏隐藏、资源切换、重命名等 - 重构 nano-banana 节点,移除 pro 版本 - 移除 multi_res_preview 节点 - 新增 http_error 工具模块 - 各客户端和节点优化改进 Co-Authored-By: Claude Opus 4.6 <[email protected]>
149 lines
5.2 KiB
Python
149 lines
5.2 KiB
Python
"""
|
||
ComfyUI V3 节点开发参考
|
||
========================
|
||
|
||
本文件记录了将 V1 节点迁移到 V3 的关键经验,供后续节点开发快速参考。
|
||
基于 nano_banana.py 的实际迁移总结。
|
||
|
||
核心发现:V3 节点可以直接放入 V1 的 NODE_CLASS_MAPPINGS 中注册,
|
||
ComfyUI 通过 issubclass(obj_class, _ComfyNodeInternal) 自动识别并
|
||
调用 GET_NODE_INFO_V1() 生成前端所需的节点信息。无需 comfy_entrypoint。
|
||
|
||
=== 最小 V3 节点模板 ===
|
||
|
||
from comfy_api.latest import io
|
||
|
||
class MyNode(io.ComfyNode):
|
||
|
||
@classmethod
|
||
def define_schema(cls):
|
||
return io.Schema(
|
||
node_id="MyNode", # 必须与 NODE_CLASS_MAPPINGS 的 key 一致
|
||
display_name="我的节点",
|
||
category="image/generation",
|
||
inputs=[...],
|
||
outputs=[io.Image.Output(display_name="输出")],
|
||
)
|
||
|
||
@classmethod
|
||
def execute(cls, input1, input2, ...) -> io.NodeOutput:
|
||
# 业务逻辑
|
||
return io.NodeOutput(result)
|
||
|
||
=== V1 → V3 对照表 ===
|
||
|
||
V1 V3
|
||
─────────────────────────────────────────────────────
|
||
INPUT_TYPES() classmethod define_schema() → io.Schema
|
||
RETURN_TYPES = ("IMAGE",) outputs=[io.Image.Output()]
|
||
RETURN_NAMES = ("输出",) io.Image.Output(display_name="输出")
|
||
FUNCTION = "generate" 固定为 execute
|
||
CATEGORY = "xxx" Schema(category="xxx")
|
||
generate(self, ...) execute(cls, ...) classmethod
|
||
self.xxx 实例状态 模块级单例函数
|
||
|
||
=== DynamicCombo(动态联动下拉框)===
|
||
|
||
场景:一个 combo 的选项决定其他 combo 显示哪些值。
|
||
|
||
io.DynamicCombo.Input("模型", options=[
|
||
io.DynamicCombo.Option("选项A", [
|
||
io.Combo.Input("子参数1", options=["x", "y"]),
|
||
io.Combo.Input("子参数2", options=["1K", "2K"]),
|
||
]),
|
||
io.DynamicCombo.Option("选项B", [
|
||
io.Combo.Input("子参数1", options=["x", "y", "z", "w"]),
|
||
io.Combo.Input("子参数2", options=["512px", "1K", "2K", "4K"]),
|
||
]),
|
||
])
|
||
|
||
execute 中接收为 dict:
|
||
def execute(cls, 模型, ...):
|
||
selected = 模型["模型"] # "选项A" 或 "选项B"
|
||
sub1 = 模型["子参数1"] # 对应选项下的子输入值
|
||
sub2 = 模型["子参数2"]
|
||
|
||
注意:dict 的 key 是 DynamicCombo.Input 的 id("模型"),
|
||
子输入的 key 是各 Combo.Input 的 id。
|
||
|
||
=== Autogrow(自动增长输入槽)===
|
||
|
||
场景:用户连接一个槽后自动出现下一个,最多 N 个。
|
||
|
||
io.Autogrow.Input("参考图",
|
||
template=io.Autogrow.TemplatePrefix(
|
||
input=io.Image.Input("img"),
|
||
prefix="参考图", # 生成 参考图0, 参考图1, ...
|
||
min=0, # 最少显示几个槽
|
||
max=9, # 最多几个槽
|
||
),
|
||
)
|
||
|
||
execute 中接收为 dict(或 io.Autogrow.Type):
|
||
def execute(cls, 参考图=None, ...):
|
||
if 参考图:
|
||
for key, tensor in 参考图.items():
|
||
# key = "参考图0", "参考图1", ...
|
||
# tensor = IMAGE tensor 或 None
|
||
|
||
=== 实例状态处理 ===
|
||
|
||
V3 的 execute 是 classmethod,无法用 self。
|
||
用模块级单例替代:
|
||
|
||
_client = None
|
||
|
||
def _get_client():
|
||
global _client
|
||
if _client is None:
|
||
_client = MyAPIClient()
|
||
return _client
|
||
|
||
=== 注册方式(与 V1 共存)===
|
||
|
||
在 __init__.py 中照常注册,无需任何特殊处理:
|
||
|
||
NODE_CLASS_MAPPINGS = {
|
||
"MyV1Node": MyV1Node, # V1 节点
|
||
"MyV3Node": MyV3Node, # V3 节点,自动识别
|
||
}
|
||
|
||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||
"MyV1Node": "V1 节点",
|
||
"MyV3Node": "V3 节点", # 也可省略,V3 用 Schema.display_name
|
||
}
|
||
|
||
=== 注意事项 ===
|
||
|
||
1. node_id 必须与 NODE_CLASS_MAPPINGS 的 key 完全一致
|
||
2. V3 execute 返回 io.NodeOutput(tensor),不是 tuple
|
||
3. _wrap_generate_for_error_display 等 V1 包装器对 V3 无效
|
||
(找不到 generate 方法会安全跳过)
|
||
4. V3 支持 async execute(直接加 async 即可)
|
||
5. 输入参数名必须与 Schema inputs 的 id 一致
|
||
6. DynamicCombo 的子输入在前端会随选项切换动态显示/隐藏
|
||
7. Autogrow 的 widget 输入会被强制为 force_input(仅连接,无控件)
|
||
|
||
=== 可用输入类型速查 ===
|
||
|
||
io.String.Input(id, default="", multiline=False)
|
||
io.Int.Input(id, default=0, min=0, max=N, step=1)
|
||
io.Float.Input(id, default=0.0, min=0.0, max=N, step=0.01)
|
||
io.Combo.Input(id, options=[...], default="...")
|
||
io.Boolean.Input(id, default=False)
|
||
io.Image.Input(id)
|
||
io.Mask.Input(id)
|
||
io.Latent.Input(id)
|
||
io.DynamicCombo.Input(id, options=[DynamicCombo.Option(...)])
|
||
io.Autogrow.Input(id, template=TemplatePrefix/TemplateNames)
|
||
|
||
=== 可用输出类型速查 ===
|
||
|
||
io.Image.Output(display_name="...")
|
||
io.String.Output(display_name="...")
|
||
io.Int.Output()
|
||
io.Float.Output()
|
||
io.Latent.Output()
|
||
io.Mask.Output()
|
||
"""
|