自定义节点的构造
简单示例
以下是图像反转节点的代码,概述了自定义节点开发中的关键概念。
class InvertImageNode:@classmethoddef 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_inreturn (image_out,)
主要属性
每个自定义节点都是一个 Python 类,具有以下关键属性:
INPUT_TYPES
INPUT_TYPES,顾名思义,定义了节点的输入。该方法返回一个 dict,其中 必须 包含键 required,并且 可以 还包含键 optional 和/或 hidden。required 和 optional 输入之间的唯一区别在于,optional 输入可以不连接。有关 hidden 输入的更多信息,请参见 隐藏输入。
每个键的值是另一个 dict,其中键值对指定输入的名称和类型。这些类型由一个 tuple 定义,第一个元素定义数据类型,第二个元素是一个包含附加参数的 dict。
这里我们只有一个必需输入,名为 image_in,类型为 IMAGE,没有附加参数。
请注意,与接下来的几个属性不同,INPUT_TYPES 是一个 @classmethod。这是为了让 Comfy 在运行时计算下拉小部件中的选项(例如要加载的检查点的名称)。我们稍后会详细介绍这一点。
RETURN_TYPES
一个包含 str 的 tuple,定义节点返回的数据类型。如果节点没有输出,仍然必须提供 RETURN_TYPES = ()。
如果您只有一个输出,请记得尾随逗号:RETURN_TYPES = ("IMAGE",)。这是 Python 将其视为 tuple 的必要条件。
RETURN_NAMES
用于标记输出的名称。这是可选的;如果省略,名称将是小写的 RETURN_TYPES。
CATEGORY
节点将在 ComfyUI 添加节点 菜单中找到的位置。可以将子菜单指定为路径,例如 examples/trivial。
FUNCTION
在节点执行时应调用的类中 Python 函数的名称。
该函数使用命名参数调用。所有 required(和 hidden)输入将包含在内;只有连接的 optional 输入才会包含,因此您应该在函数定义中为它们提供默认值(或使用 **kwargs 捕获它们)。
该函数返回一个与 RETURN_TYPES 对应的元组。这是必要的,即使没有返回任何内容(return ())。同样,如果只有一个输出,请记得尾随逗号 return (image_out,)!
执行控制附加功能
Comfy 的一个很棒的功能是它会缓存输出,仅执行可能与上次运行的结果不同的节点。这可以大大加快许多工作流的速度。
基本上,它通过识别哪些节点生成输出来工作(这些节点,特别是图像预览和保存图像节点,总是会执行),然后向后工作以识别自上次运行以来可能已更改的提供数据的节点。
自定义节点的两个可选特性有助于此过程。
OUTPUT_NODE
默认情况下,节点不被视为输出。设置 OUTPUT_NODE = True 可指定它是。
IS_CHANGED
默认情况下,如果节点的任何输入或小部件已更改,则 Comfy 认为该节点已更改。这通常是正确的,但您可能需要重写此功能,例如,当节点使用随机数时(并且未指定种子——在这种情况下最好有一个种子输入,以便用户可以控制可重复性并避免不必要的执行),或加载可能已外部更改的输入,或有时忽略输入(因此仅因为这些输入已更改而不需要执行)。
尽管名称如此,IS_CHANGED 不应返回 bool。
IS_CHANGED 传递与 FUNCTION 定义的主函数相同的参数,可以返回任何 Python 对象。该对象将与上一次运行中返回的对象进行比较(如果有),如果 is_changed != is_changed_old,则该节点将被视为已更改(如果需要深入了解,可以查看 execution.py 中的代码)。
由于 True == True,返回 True 以表示已更改的节点将被视为未更改!我敢肯定,如果不是因为可能会破坏现有节点,Comfy 代码会更改这一点。
要指定节点始终被视为已更改(最好避免这样做,因为这会阻止 Comfy 优化运行内容),请返回 float("NaN")。这返回一个 NaN 值,哪个也不等于任何值,甚至是另一个 NaN。
实际检查更改的一个好示例是内置的 LoadImage 节点的代码,该节点加载图像并返回哈希值。
@classmethoddef IS_CHANGED(s, image):image_path = folder_paths.get_annotated_filepath(image)m = hashlib.sha256()with open(image_path, 'rb') as f:m.update(f.read())return m.digest().hex()
其他属性
还有三个其他属性可以用来修改 Comfy 对节点的默认处理。
INPUT_IS_LIST, OUTPUT_IS_LIST
这些用于控制数据的顺序处理,稍后会描述 处理列表。
VALIDATE_INPUTS
如果定义了类方法 VALIDATE_INPUTS,则在工作流开始执行之前将调用该方法。VALIDATE_INPUTS 应返回 True(如果输入有效),或描述错误的消息(作为 str),这将阻止执行。
验证常量
请注意,VALIDATE_INPUTS 只会收到定义为工作流中的常量的输入。任何从其他节点接收的输入在 VALIDATE_INPUTS 中 将 不可用。
VALIDATE_INPUTS 只会使用其签名请求的输入进行调用(即由 inspect.getfullargspec(obj_class.VALIDATE_INPUTS).args 返回的那些)。通过这种方式接收的任何输入将 不会 遵循默认验证规则。例如,在以下代码段中,前端将使用指定的 min 和 max 值来处理 foo 输入,但后端不会强制执行。
class CustomNode:@classmethoddef INPUT_TYPES(cls):return {"required": { "foo": ("INT", {"min": 0, "max": 10}) },}@classmethoddef VALIDATE_INPUTS(cls, foo):# YOLO, anything goes!return True
此外,如果函数接受一个 **kwargs 输入,它将接收 所有 可用输入,并且所有输入都将跳过验证,就像显式指定的一样。
验证类型
如果 VALIDATE_INPUTS 方法接收到一个名为 input_types 的参数,它将接收一个字典,其中键是连接到其他节点输出的每个输入的名称,值是该输出的类型。
当此参数存在时,将跳过所有输入类型的默认验证。以下示例利用了前端允许指定多种类型的事实:
class AddNumbers:@classmethoddef INPUT_TYPES(cls):return {"required": {"input1": ("INT,FLOAT", {"min": 0, "max": 1000}),"input2": ("INT,FLOAT", {"min": 0, "max": 1000}),},}@classmethoddef VALIDATE_INPUTS(cls, input_types):# input1 和 input2 的最小值和最大值仍然会被验证,因为# 我们没有将 `input1` 或 `input2` 作为参数。if input_types["input1"] not in ("INT", "FLOAT"):return "input1 must be an INT or FLOAT type"if input_types["input2"] not in ("INT", "FLOAT"):return "input2 must be an INT or FLOAT type"return True
