シンプル示例
これは「画像反転ノード」のコードで、カスタムノード開発における主要な概念の概要を示しています。
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,)
主要属性
すべてのカスタムノードは Python クラスであり、以下の主要なプロパティを持ちます:
INPUT_TYPES は、その名が示す通り、ノードの入力を定義します。このメソッドは dict を返しますが、required キーを_含んでいなければならず_、optional および/または hidden キーを_含んでもよい_ものです。required 入力と optional 入力の唯一の違いは、optional 入力は接続せずに残しておくことができる点です。
hidden 入力についての詳細は、隠し入力 を参照してください。
各キーの値は別の dict であり、その中のキーと値のペアが入力の名前と型を指定します。型は tuple によって定義され、その最初の要素がデータ型を定義し、2 番目の要素が追加パラメータの dict となります。
ここでは、image_in という名前の IMAGE 型の必須入力が 1 つあり、追加パラメータはありません。
次のいくつかの属性とは異なり、この INPUT_TYPES は @classmethod であることに注意してください。これは、ドロップダウンウィジェット内のオプション(例えば、読み込む checkpoint の名前など)を、Comfy によって実行時に計算できるようにするためです。これについては後で詳しく説明します。
RETURN_TYPES
ノードによって返されるデータ型を定義する str の tuple です。
ノードに出力がない場合でも、RETURN_TYPES = () として提供しなければなりません。
出力が 1 つだけの場合は、末尾のコンマを忘れないでください:RETURN_TYPES = ("IMAGE",)。これは Python がそれを tuple として認識するために必要です。
RETURN_NAMES
出力にラベルを付けるために使用される名前です。これはオプションです。省略された場合、名前は単に RETURN_TYPES の小文字形式になります。
CATEGORY
ノードが ComfyUI の ノード追加 メニューのどこに表示されるかを指定します。サブメニューはパスとして指定できます。例:examples/trivial。
FUNCTION
ノードが実行されたときに呼び出されるべき、クラス内の Python 関数の名前です。
この関数は名前付き引数で呼び出されます。すべての required(および hidden)入力が含まれます。optional 入力は接続されている場合にのみ含まれるため、関数定義内でそれらのデフォルト値を提供するか(または **kwargs でキャプチャする必要があります)。
この関数は RETURN_TYPES に対応する tuple を返します。何も返さない場合でも tuple を返す必要があります(return ())。繰り返しますが、出力が 1 つだけの場合は、末尾のコンマ return (image_out,) を忘れないでください!
実行制御拡張
Comfy の素晴らしい機能の 1 つは、出力をキャッシュし、前回の実行とは異なる結果を生む可能性のあるノードのみを実行することです。これにより、多くのワークフローの速度を大幅に向上させることができます。
本質的に、これはどのノードが出力を生成するかを識別すること(特に Image Preview や Save Image ノードなどは常に実行されます)によって機能し、その後、前回の実行以降に変更された可能性のあるデータを提供しているノードを特定するために逆方向に作業します。
カスタムノードには、このプロセスを支援する 2 つのオプション機能があります。
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 の実行最適化を阻害するため、可能であれば避けるべきです)、return float("NaN") としてください。これは NaN 値を返します。NaN は、別の NaN でさえも、どの値とも等しくありません。
実際の変更チェックの良い例は、組み込みの LoadImage ノードのコードで、画像を読み込んでハッシュを返します:
@classmethod
def 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()
SEARCH_ALIASES
オプション。ユーザーがこのノードを検索する際に使用する可能性のある代替名のリスト。
これは /object_info API レスポンスの search_aliases として含まれます。
SEARCH_ALIASES = ["text concat", "join text", "merge strings"]
その他の属性
他にも、ノードに対する Comfy のデフォルトの処理を変更するために使用できる属性が 3 つあります。
これらはデータの順次処理を制御するために使用され、後述 します。
クラスメソッド VALIDATE_INPUTS が定義されている場合、ワークフローの実行開始前に呼び出されます。
VALIDATE_INPUTS は、入力が有効であれば True を返すか、エラーを記述したメッセージ(str)を返す必要があります(これにより実行が防止されます)。
定数の検証
VALIDATE_INPUTS は、ワークフロー内で定数として定義された入力のみを受け取ることに注意してください。他のノードから受け取られる入力は VALIDATE_INPUTS では利用できません。
VALIDATE_INPUTS は、そのシグネチャで要求された入力のみを受け取ります(inspect.getfullargspec(obj_class.VALIDATE_INPUTS).args によって返されるもの)。このようにして受け取られる入力は、デフォルトの検証ルールを経由しません。例えば、以下のスニペットでは、フロントエンドは foo 入力に指定された min および max 値を使用しますが、バックエンドはそれを強制しません。
class CustomNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": { "foo" : ("INT", {"min": 0, "max": 10}) },
}
@classmethod
def VALIDATE_INPUTS(cls, foo):
# YOLO、何でもあり!
return True
さらに、関数が **kwargs 入力を受け取る場合、利用可能なすべての入力を受け取り、明示的に指定されたかのように、それらすべてが検証をスキップします。
型の検証
VALIDATE_INPUTS メソッドが input_types という名前の引数を受け取る場合、他のノードの出力に接続されている各入力の名前をキーとし、その出力の型を値とする辞書が渡されます。
この引数が存在する場合、入力型のすべてのデフォルト検証がスキップされます。これは、フロントエンドで複数の型の指定を許可している事実を利用した例です:
class AddNumbers:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input1" : ("INT,FLOAT", {"min": 0, "max": 1000})
"input2" : ("INT,FLOAT", {"min": 0, "max": 1000})
},
}
@classmethod
def VALIDATE_INPUTS(cls, input_types):
# input1 および input2 の min と max は依然として検証されます。
# `input1` または `input2` を引数として受け取っていないためです
if input_types["input1"] not in ("INT", "FLOAT"):
return "input1 は INT または FLOAT 型である必要があります"
if input_types["input2"] not in ("INT", "FLOAT"):
return "input2 は INT または FLOAT 型である必要があります"
return True