ComfyUI 自定义插件

704 阅读3分钟

介绍

ComfyUI 是一个基于 Node 搭建 workflow 实现具体功能的软件。常见于 stable diffusion 扩散模型生图领域。同时其提供了自定义 Node 能力的机制,支持扩展生图之外的功能。比如生成视频,生成音频,大语言模型使用等等。

Github: GitHub - comfyanonymous/ComfyUI: The most powerful and modular diffusion model GUI, api and backend with a graph/nodes interface.

python main.py --listen

整体架构

ComfyUI 是 C/S 架构,分为 Server 和 Client 两部分。 Server 部分使用 Python 语言编码,负责数据处理,模型加载,具体的功能逻辑。Client 部分使用 JS 语言编写,负责 UI 展示和用户交互逻辑。ComfyUI 完美的将 View 和 Controller 进行了解耦,所以 ComfyUI 同时支持 API 模式,可以另外实现 UI 交互或者纯命令交互,以及编写脚本自动执行任务。

erDiagram
ComfyUI-Server ||--|{ ComfyUI-Client : API
ComfyUI-Server ||..|{ comfy-cli : API
ComfyUI-Server }|..|{ ComfyUI_frontend : API

Node

Node 是由输入,输出,以及处理逻辑组成,处理逻辑可以是简单任务,也可以是较复杂的任务。

基于 ComfyUI 的架构,Node 可以分为三类: docs.comfy.org/essentials/…

  1. Server Side Only

The majority of Nodes run purely on the server side, by defining a Python class that specifies the input and output types, and provides a function that can be called to process inputs and produce an output.

  1. Client Side Only

A few Nodes provide a modification to the client UI, but do not add core functionality. Despite the name, they may not even add new nodes to the system.

  1. Independent Client and Server

Nodes may provide additional server features, and additional (related) UI features (such as a new widget to deal with a new data type). In most cases, communication between the client and server can be handled by the Comfy data flow control.

Server Side

下面是一个自定义 Node 官方示例: docs.comfy.org/custom-node…

class InvertImageNode:
    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": { "image_in" : ("IMAGE", {}) },
        }

    RETURN_TYPES = ("IMAGE",)
    RETURN_NAMES = ("image_out",)
    CATEGORY = "examples"
    FUNCTION = "invert"

    def invert(self, image_in):
        image_out = 1 - image_in
        return (image_out,)

其中有一下元素:

  • INPUT_TYPES: 定义 Node 的输入,支持必要,可选,隐藏三类输入。每一个输入由 name 和 类型,以及一组 key-value 的参数。且 INPUT_TYPES 是一个类方法,因为 ComfyUI 加载时,调此方法或者输入定义,生成对应 UI 。
  • RETURN_TYPES: 定义 Node 的输出, 类型为元组,可以定义多个输出。RETURN_TYPES = ("IMAGE",) 表示无输出。
  • RETURN_NAMES: 给输出命名,可选,默认使用类型小写。
  • CATEGORY: 在菜单中分类项入口名
  • FUNCTION: Python 方法名,此 Node 执行时被调用的方法。参数为 INPUT_TYPES 中定义的输入;返回则是 RETURN_TYPES 中定义的输出。

Client Node

docs.comfy.org/essentials/…

ComfyUI 的前端页面基于 LiteGraph , ComfyNode 则是 LGraphNode 的子类, 对 LGraphNode 进行了功能扩展,对应一个 Node 在 UI 上的表示。

ComfyNode 中包含很多 properties 和 functions, 这些支持了对 Node 进行 hijack to modify 默认行为

async nodeCreated(node) {
    console.log("nodeCreated")
}
Widgets
addWidgetAdd a standard Comfy widget
addCustomWidgetAdd a custom widget (defined in the getComfyWidgets hook)
addDOMWidgetAdd a widget defined by a DOM element
convertWidgetToInputConvert a widget to an input if allowed by isConvertableWidget (in widgetInputs.js)

如何注入 Custom Node

docs.comfy.org/custom-node…

How Comfy loads custom nodes

Custom Node 需要放置在 custom_nodes 目录中, ComfyUI 启动时,会遍历此目录中的 python moduels,导入其中的 NODE_CLASS_MAPPINGS

python module 一个包含 __init__.py 文件的目录. module 导出的符合都定义在  __init__.py 文件中的变量 __all__ 

一个简单 __init__.py 文件示例:

from .python_file import MyCustomNode
NODE_CLASS_MAPPINGS = { "My Custom Node" : MyCustomNode }
__all__ = ["NODE_CLASS_MAPPINGS"]
NODE_CLASS_MAPPINGS

NODE_CLASS_MAPPINGS must be a dict mapping custom node names (unique across the Comfy install) to the corresponding node class.

NODE_DISPLAY_NAME_MAPPINGS

__init__.py may also export NODE_DISPLAY_NAME_MAPPINGS, which maps the same unique name to a display name for the node. If NODE_DISPLAY_NAME_MAPPINGS is not provided, Comfy will use the unique name as the display name.

WEB_DIRECTORY

docs.comfy.org/custom-node… Client side Node 的代码通过 WEB_DIRECTORY 变量进行导出,

WEB_DIRECTORY = "./js"
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]

  • 在你的 Python module 中定义 WEB_DIRECTORY 变量,指定目录相对 path
  • 你将需要的 .js 文件放入此目录中,ComfyUI 启动时将加载此目录中的 .js 文件。
  • 你的代码中在 app.registerExtension 方法中注册自己的 Custom Node 。
import { app } from "../../scripts/app.js";
app.registerExtension({ 
	name: "a.unique.name.for.a.useless.extension",
	async setup() { 
		alert("Setup complete!")
	},
})

链接

comfy: docs.comfy.org/custom-node…

litegraph: gausszhou.github.io/litegraph/d…

三方 guide: github.com/Suzie1/Comf…

github.com/chrisgoring…