update README en/ja and description & tooltips

This commit is contained in:
godmt
2026-06-23 10:55:42 +09:00
parent d81627d98e
commit be13cd817b
4 changed files with 504 additions and 64 deletions
+166
View File
@@ -0,0 +1,166 @@
# ComfyUI-List-Utils
[English](README.md)
ComfyUI で「複数の値を順番に処理する」「Python の list 値としてまとめて扱う」「型つきの値セットを受け渡す」ためのプリミティブなカスタムノード集です。
この拡張では、似ているけれど役割が違う 3 種類のコンテナを扱います。
| 名前 | 意味 | 主な用途 |
| --- | --- | --- |
| `List` | ComfyUI 公式の Data list / List processing | 複数の値を ComfyUI の逐次処理として流す |
| `PyList` | 生の Python `list` 値 | 値の配列を 1 つの値として保持・加工する |
| `Pack` | List-Utils 独自の、型情報つきコンテナ | 複数の異なる型の値をまとめて渡し、後で元の型で取り出す |
## Installation
`ComfyUI/custom_nodes` ディレクトリで clone して、ComfyUI を再起動してください。
```bash
git clone https://github.com/godmt/ComfyUI-List-Utils.git
```
ノードは `list_utils` カテゴリに追加されます。
## GUI Help
各ノードには ComfyUI 上で確認できる説明を追加しています。
- ノード自体の説明は、ノードにマウスを重ねたときの説明として表示されます。
- widget / input には、用途を説明する tooltip を追加しています。
- output には、何が出力されるかを説明する tooltip を追加しています。
- `Create List`、`Create PyList`、`Merge List`、`Merge PyList`、`Pack`、`Exec` の動的入力にも tooltip が付きます。
## List / PyList / Pack の使い分け
### List
`List` は ComfyUI の List processing 用です。たとえば 10 個の prompt、seed、画像、パスなどを順番に処理したいときに使います。
List-Utils の `List` 系ノードは、ComfyUI の `OUTPUT_IS_LIST` / `INPUT_IS_LIST` を使います。出力ソケットの型は `STRING`、`INT`、`IMAGE` など元の型として扱われます。
よく使う場面:
- 複数の文字列を 1 件ずつ downstream ノードへ流す
- `Create Range` で連番 seed を作る
- `List Dir` でファイルパスを列挙して順番に処理する
- `List: Get By Index` / `List: Slice` で ComfyUI List を取り出す
### PyList
`PyList` は Python の `list` そのものを 1 つの値として受け渡す型です。ComfyUI の逐次処理にはならず、配列をまとめたまま扱いたいときに使います。
よく使う場面:
- 文字列や数値の配列をまとめて保持する
- Python list として index / slice / merge / cast する
- `List To PyList` で ComfyUI List を 1 つの Python list にまとめる
- `PyList To List` で Python list を ComfyUI の逐次処理へ戻す
### Pack
`Pack` は複数の値を 1 本の `PACK` ソケットにまとめるためのコンテナです。各入力の値だけでなく、入力名と ComfyUI の型も保持します。
`Pack` / `Unpack` は、型の違う値をまとめて受け渡したいときに便利です。たとえば `MODEL`、`CLIP`、`VAE`、`CONDITIONING`、`IMAGE` のように、通常は 1 つの list に入れづらい値をまとめて運べます。
## Typical Workflows
### 文字列を分割して順番に処理する
1. `Split String` に複数行テキストや区切り文字つきテキストを入力します。
2. `STRING` 出力を使うと、ComfyUI の `List` として順番に流れます。
3. `PYLIST` 出力を使うと、Python list 値としてまとめて扱えます。
4. `length` 出力で要素数を取得できます。
### PyList を ComfyUI List に変換する
1. `Create PyList`、`Split String`、`Create Range` などで `PYLIST` を作ります。
2. `PyList To List` に接続します。
3. 出力は ComfyUI の `List` になり、下流で逐次処理できます。
逆に、ComfyUI の `List` を Python list としてまとめたい場合は `List To PyList` を使います。
### 複数の型をまとめて渡す
1. `Pack` に値を接続します。
2. 最後の空ソケットに接続すると、次の入力ソケットが自動で追加されます。
3. `Unpack` に接続すると、`Pack` 側の入力数と型に合わせて出力が作られます。
`Unpack` は一度 `Pack` に接続すると出力型を確定できます。`Switch` ノードや `Anything Everywhere` 系ノードを間に挟んだ構成でも、上流の `Pack` をたどれる場合は型を復元できます。
## Nodes
### Create / Generate
| Node | Output | Description |
| --- | --- | --- |
| `Split String` | `STRING` List, `PYLIST`, `length` | 文字列を delimiter または行で分割します。空要素は除去されます。 |
| `Create List` | `List` | 複数入力を ComfyUI List として作成します。 |
| `Create PyList` | `PYLIST` | 複数入力を Python list として作成します。 |
| `Create Range` | `INT` List, `PYLIST`, `length` | Python `range(start, stop, step)` で整数列を作ります。 |
| `Create Arange` | `FLOAT` List, `PYLIST`, `length` | `numpy.arange(start, stop, step)` で数値列を作ります。 |
| `Create Linspace` | `FLOAT` List, `PYLIST`, `length` | `numpy.linspace(start, stop, num)` で等間隔の数値列を作ります。 |
| `List Dir` | `STRING` List, `PYLIST`, `length` | 指定フォルダから `glob` pattern に一致する項目を列挙します。 |
### Merge / Convert
| Node | Output | Description |
| --- | --- | --- |
| `Merge List` | `List` | 複数の ComfyUI List を連結します。 |
| `Merge PyList` | `PYLIST` | 複数の PyList を連結します。 |
| `List To PyList` | `PYLIST` | ComfyUI List を Python list 値に変換します。 |
| `PyList To List` | `List` | PyList を ComfyUI List processing 用に展開します。 |
### Get / Slice
| Node | Output | Description |
| --- | --- | --- |
| `List: Get By Index` | item | ComfyUI List から指定 index の要素を取り出します。 |
| `PyList: Get By Index` | item | PyList から指定 index の要素を取り出します。 |
| `List: Slice` | `List` | ComfyUI List を `start:end` で切り出します。 |
| `PyList: Slice` | `PYLIST` | PyList を `start:end` で切り出します。 |
### Pack / Unpack
| Node | Output | Description |
| --- | --- | --- |
| `Pack` | `PACK` | 複数入力を、名前・型・値つきで 1 つの Pack にまとめます。 |
| `Unpack` | original types | Pack から元の値を取り出します。出力型は接続元の Pack に合わせて動的に変わります。 |
### Inspect / Utility
| Node | Output | Description |
| --- | --- | --- |
| `Get Length` | `INT` | `len(value)` を返し、ノード上にも表示します。 |
| `Get Shape` | `width`, `height`, `PyList_size`, `channels` | `IMAGE`、`LATENT`、`MASK` の形状を取得します。 |
| `Any To Dict` | `DICT`, `str()` | オブジェクトを dict または文字列に変換して確認します。 |
| `Get Widgets Values` | `LIST` | 接続元ノードの widget values を取得して表示します。 |
| `Any Cast` | selected type | 値を指定型として出力します。基本的な `STRING` / `INT` / `FLOAT` / `BOOLEAN` 変換にも使えます。 |
| `PyList Item Cast` | `PYLIST` | PyList 内の各要素を `STRING` / `INT` / `FLOAT` / `BOOLEAN` などへ変換します。 |
| `Exec` | any | 入力値 `x[0]`, `x[1]`, ... を使って Python コードを実行し、`result` を返します。 |
## Dynamic Inputs
一部のノードは入力ソケットを動的に増やします。
- `Create List`
- `Create PyList`
- `Merge List`
- `Merge PyList`
- `Pack`
- `Exec`
最後の空ソケットに接続すると次のソケットが追加されます。接続を外すと未使用ソケットは整理されます。
`Create List` / `Merge List` は、最初に接続した入力の型に合わせて出力型が変わります。`Pack` は入力ごとの型を保持します。
## Notes
- `List` と `PyList` は別物です。接続できない、または期待どおりに逐次処理されない場合は `List To PyList` / `PyList To List` で変換してください。
- `List: Get By Index` と `PyList: Get By Index` は、index が範囲外の場合 `None` を返します。
- Python の slice と同じく、`start:end` の `end` は含まれません。
- `Exec` は任意の Python コードを実行します。信頼できる workflow でだけ使ってください。
## Example
![Example workflow](https://github.com/user-attachments/assets/d1543cf3-84db-4b94-b326-88d48f46bd49)
+165 -12
View File
@@ -1,14 +1,167 @@
# ComfyUI-List-Utils
Comfy List and Python list (PyList) utilities which support: create, merge, convert, get or slice
## Installation
goto ComfyUI/custom_nodes dir in terminal(cmd)
git clone https://github.com/godmt/ComfyUI-List-Utils.git
Restart ComfyUI
## Usage
### Pack/Unpack
Unpack node must be connected to Pack node at once to determine the output types.
Once connected, Unpack node maintains its output types even when disconnected.
(You can connect Pack and Unpack through Switch nodes or Anything Everywhere nodes!)
## Examples
![image](https://github.com/user-attachments/assets/d1543cf3-84db-4b94-b326-88d48f46bd49)
[Japanese](README.ja.md)
Primitive ComfyUI custom nodes for working with ComfyUI List processing, raw Python list values, and typed value bundles.
This extension uses three similar-looking but very different container concepts:
| Name | Meaning | Main Use |
| --- | --- | --- |
| `List` | ComfyUI's official Data list / List processing | Run downstream nodes once per item |
| `PyList` | A raw Python `list` value | Keep an array as one value and manipulate it directly |
| `Pack` | A List-Utils typed container | Bundle values of different ComfyUI types and restore them later |
## Installation
Clone this repository into `ComfyUI/custom_nodes`, then restart ComfyUI.
```bash
git clone https://github.com/godmt/ComfyUI-List-Utils.git
```
Nodes are added under the `list_utils` category.
## GUI Help
The nodes include inline help for the ComfyUI interface:
- Node-level descriptions explain what each node is for.
- Inputs and widgets include tooltips for expected values.
- Outputs include tooltips for what they return.
- Dynamic inputs created by `Create List`, `Create PyList`, `Merge List`, `Merge PyList`, `Pack`, and `Exec` also receive tooltips.
## Choosing List, PyList, Or Pack
### List
`List` means ComfyUI List processing. Use it when you want ComfyUI to process several prompts, seeds, images, paths, or other values one item at a time.
List-Utils `List` nodes use ComfyUI's `OUTPUT_IS_LIST` / `INPUT_IS_LIST` behavior. The output socket type remains the original item type, such as `STRING`, `INT`, or `IMAGE`.
Common uses:
- Send several strings downstream one by one
- Generate sequential seeds with `Create Range`
- Enumerate files with `List Dir`
- Extract or slice a ComfyUI List with `List: Get By Index` / `List: Slice`
### PyList
`PyList` is a raw Python `list` passed as one ComfyUI value. It does not trigger ComfyUI List processing by itself.
Common uses:
- Keep strings, numbers, paths, or other values as one array
- Use Python-style index, slice, merge, or cast operations
- Convert a ComfyUI List into one Python list with `List To PyList`
- Convert a Python list back into ComfyUI List processing with `PyList To List`
### Pack
`Pack` bundles multiple values into one `PACK` socket while preserving each input's name, type, and value.
Use `Pack` / `Unpack` when you need to pass different ComfyUI types together. For example, a single Pack can carry `MODEL`, `CLIP`, `VAE`, `CONDITIONING`, and `IMAGE` values, then restore them as separate typed outputs later.
## Typical Workflows
### Split Text And Process Each Item
1. Put multiline or delimiter-separated text into `Split String`.
2. Use the `STRING` output when you want ComfyUI List processing.
3. Use the `PYLIST` output when you want one raw Python list value.
4. Use `length` when you need the item count.
### Convert PyList To ComfyUI List
1. Create a `PYLIST` with `Create PyList`, `Split String`, `Create Range`, or a similar node.
2. Connect it to `PyList To List`.
3. The output becomes a ComfyUI List-processing output and downstream nodes run once per item.
Use `List To PyList` for the opposite direction, when you want to collect a ComfyUI List into one Python list value.
### Bundle Different Types
1. Connect values to `Pack`.
2. Connecting the last empty socket adds another input automatically.
3. Connect `Pack` to `Unpack`.
4. `Unpack` creates outputs matching the input count and types from `Pack`.
`Unpack` can determine its output types after being connected to `Pack` once. It can also trace through some intermediate nodes such as switch-style nodes or Anything Everywhere-style nodes when the upstream `Pack` is discoverable.
## Nodes
### Create / Generate
| Node | Output | Description |
| --- | --- | --- |
| `Split String` | `STRING` List, `PYLIST`, `length` | Splits text by delimiter and/or lines. Empty items are removed. |
| `Create List` | `List` | Creates a ComfyUI List from multiple dynamic inputs. |
| `Create PyList` | `PYLIST` | Creates a raw Python list value from multiple dynamic inputs. |
| `Create Range` | `INT` List, `PYLIST`, `length` | Creates integer values with Python `range(start, stop, step)`. |
| `Create Arange` | `FLOAT` List, `PYLIST`, `length` | Creates values with `numpy.arange(start, stop, step)`. |
| `Create Linspace` | `FLOAT` List, `PYLIST`, `length` | Creates evenly spaced values with `numpy.linspace(start, stop, num)`. |
| `List Dir` | `STRING` List, `PYLIST`, `length` | Lists path matches from a folder using a `glob` pattern. |
### Merge / Convert
| Node | Output | Description |
| --- | --- | --- |
| `Merge List` | `List` | Concatenates multiple ComfyUI Lists. |
| `Merge PyList` | `PYLIST` | Concatenates multiple PyLists. |
| `List To PyList` | `PYLIST` | Collects a ComfyUI List into one Python list value. |
| `PyList To List` | `List` | Expands a PyList into ComfyUI List processing. |
### Get / Slice
| Node | Output | Description |
| --- | --- | --- |
| `List: Get By Index` | item | Reads one item from a ComfyUI List by index. |
| `PyList: Get By Index` | item | Reads one item from a PyList by index. |
| `List: Slice` | `List` | Slices a ComfyUI List with Python-style `start:end`. |
| `PyList: Slice` | `PYLIST` | Slices a PyList with Python-style `start:end`. |
### Pack / Unpack
| Node | Output | Description |
| --- | --- | --- |
| `Pack` | `PACK` | Bundles multiple inputs with their names, types, and values. |
| `Unpack` | original types | Restores values from a Pack. Output types change dynamically to match the connected Pack. |
### Inspect / Utility
| Node | Output | Description |
| --- | --- | --- |
| `Get Length` | `INT` | Returns `len(value)` and displays it on the node. |
| `Get Shape` | `width`, `height`, `PyList_size`, `channels` | Reads shape information from `IMAGE`, `LATENT`, or `MASK`. |
| `Any To Dict` | `DICT`, `str()` | Converts an object to a dict when possible and also returns `str(value)`. |
| `Get Widgets Values` | `LIST` | Reads and displays widget values from the connected upstream node. |
| `Any Cast` | selected type | Relabels a value as a selected type and converts basic scalar types when possible. |
| `PyList Item Cast` | `PYLIST` | Converts each item inside a PyList to `STRING`, `INT`, `FLOAT`, `BOOLEAN`, and similar types. |
| `Exec` | any | Runs Python code using inputs as `x[0]`, `x[1]`, ... and returns `result`. |
## Dynamic Inputs
These nodes add input sockets dynamically:
- `Create List`
- `Create PyList`
- `Merge List`
- `Merge PyList`
- `Pack`
- `Exec`
Connect the last empty socket to add the next socket. Disconnecting sockets cleans up unused inputs.
`Create List` / `Merge List` adapt their output type from the first connected input. `Pack` preserves the type of each input separately.
## Notes
- `List` and `PyList` are different. If a connection does not work, or execution is not happening in the expected mode, use `List To PyList` or `PyList To List`.
- `List: Get By Index` and `PyList: Get By Index` return `None` when the index is out of range.
- Slices use Python behavior: `start:end` does not include the item at `end`.
- `Exec` runs arbitrary Python code. Use it only in workflows you trust.
## Example
![Example workflow](https://github.com/user-attachments/assets/d1543cf3-84db-4b94-b326-88d48f46bd49)
+60 -15
View File
@@ -15,7 +15,39 @@ const TypeSlotEvent = {
Disconnect: false,
}
const dynamic_connection = (node, index, event, prefix = 'in_', type = '*', names = []
const TOOLTIP_VALUE_INPUT = "Connect a value. Connecting the last empty socket adds another input."
const TOOLTIP_PACK_VALUE_INPUT = "Value to store in the PACK. The input name, type, and value are preserved for Unpack."
const TOOLTIP_PYLIST_INPUT = "Connect a PyList. Connecting the last empty socket adds another input."
const TOOLTIP_EXEC_INPUT = "Input value available inside FUNC as x[index]. Connecting the last empty socket adds another input."
const TOOLTIP_UNPACK_OUTPUT = "Value restored from the connected PACK. Output type is copied from the original Pack input."
const setTooltip = (slot, tooltip) => {
if (slot && tooltip) {
slot.tooltip = tooltip
}
return slot
}
const addInputWithTooltip = (node, name, type, tooltip) => {
const index = node.inputs?.length ?? 0
node.addInput(name, type)
return setTooltip(node.inputs?.[index], tooltip)
}
const addOutputWithTooltip = (node, name, type, tooltip) => {
const index = node.outputs?.length ?? 0
node.addOutput(name, type)
return setTooltip(node.outputs?.[index], tooltip)
}
const dynamic_connection = (
node,
index,
event,
prefix = 'in_',
type = '*',
names = [],
tooltip = ""
) => {
if (!node.inputs[index].name.startsWith(prefix)) {
return
@@ -37,6 +69,7 @@ const dynamic_connection = (node, index, event, prefix = 'in_', type = '*', name
const name = i < names.length ? names[i] : `${prefix}${i + 1}`
node.inputs[i].label = name
node.inputs[i].name = name
setTooltip(node.inputs[i], tooltip)
}
}
@@ -46,7 +79,7 @@ const dynamic_connection = (node, index, event, prefix = 'in_', type = '*', name
const name = nextIndex < names.length
? names[nextIndex]
: `${prefix}${nextIndex + 1}`
node.addInput(name, type)
addInputWithTooltip(node, name, type, tooltip)
}
}
@@ -93,10 +126,13 @@ app.registerExtension({
name: "godmt.ListUtils",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "GODMT_Pack" || nodeData.name === "GODMT_CreatePyList") {
const inputTooltip = nodeData.name === "GODMT_Pack"
? TOOLTIP_PACK_VALUE_INPUT
: TOOLTIP_VALUE_INPUT
const onNodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined
this.addInput(`${_prefix}_1`, '*')
addInputWithTooltip(this, `${_prefix}_1`, '*', inputTooltip)
return r
}
@@ -109,7 +145,7 @@ app.registerExtension({
for (let i = length - 1; i >= 0; i--) {
this.removeInput(i)
}
this.addInput(`${_prefix}_1`, '*')
addInputWithTooltip(this, `${_prefix}_1`, '*', inputTooltip)
}
return r
}
@@ -118,16 +154,18 @@ app.registerExtension({
nodeType.prototype.onConnectionsChange = function (slotType, slot, event, link_info, data) {
const r = onConnectionsChange ? onConnectionsChange.apply(this, arguments) : undefined
if (slotType === TypeSlot.Input) {
dynamic_connection(this, slot, event, `${_prefix}_`, '*')
dynamic_connection(this, slot, event, `${_prefix}_`, '*', [], inputTooltip)
if (event === TypeSlotEvent.Connect && link_info) {
const fromNode = this.graph._nodes.find(
(otherNode) => otherNode.id == link_info.origin_id
)
const type = fromNode.outputs[link_info.origin_slot].type
this.inputs[slot].type = type
setTooltip(this.inputs[slot], inputTooltip)
} else if (event === TypeSlotEvent.Disconnect) {
this.inputs[slot].type = '*'
this.inputs[slot].label = `${_prefix}_${slot + 1}`
setTooltip(this.inputs[slot], inputTooltip)
}
}
return r
@@ -136,7 +174,7 @@ app.registerExtension({
const onNodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined
this.addInput(`x[0]`, '*')
addInputWithTooltip(this, `x[0]`, '*', TOOLTIP_EXEC_INPUT)
return r
}
@@ -149,7 +187,7 @@ app.registerExtension({
for (let i = length - 1; i > 0; i--) {
this.removeInput(i)
}
this.addInput(`x[0]`, '*')
addInputWithTooltip(this, `x[0]`, '*', TOOLTIP_EXEC_INPUT)
}
return r
}
@@ -173,6 +211,7 @@ app.registerExtension({
const name = `x[${i-1}]`
this.inputs[i].label = name
this.inputs[i].name = name
setTooltip(this.inputs[i], TOOLTIP_EXEC_INPUT)
}
}
@@ -182,7 +221,7 @@ app.registerExtension({
if (this.inputs[this.inputs.length - 1].link != undefined) {
const nextIndex = this.inputs.length - 1 // string widget is regarded as input also
const name = `x[${nextIndex}]`
this.addInput(name, type)
addInputWithTooltip(this, name, type, TOOLTIP_EXEC_INPUT)
}
if (event === TypeSlotEvent.Connect && link_info) {
@@ -191,6 +230,7 @@ app.registerExtension({
)
const type = fromNode.outputs[link_info.origin_slot].type
this.inputs[slot].type = type
setTooltip(this.inputs[slot], TOOLTIP_EXEC_INPUT)
} else if (event === TypeSlotEvent.Disconnect) {
// this.inputs[slot].type = '*'
// this.inputs[slot].label = `x[${slot}]`
@@ -207,7 +247,7 @@ app.registerExtension({
const onNodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined
this.addInput(`${_prefix}_1`, '*')
addInputWithTooltip(this, `${_prefix}_1`, '*', TOOLTIP_VALUE_INPUT)
return r
}
@@ -221,7 +261,7 @@ app.registerExtension({
for (let i = length - 1; i >= 0; i--) {
this.removeInput(i)
}
this.addInput(`${_prefix}_1`, '*')
addInputWithTooltip(this, `${_prefix}_1`, '*', TOOLTIP_VALUE_INPUT)
}
return r
}
@@ -259,13 +299,14 @@ app.registerExtension({
for (let i = 0; i < this.inputs.length; i++) {
this.inputs[i].label = `${_prefix}_${i + 1}`
this.inputs[i].name = `${_prefix}_${i + 1}`
setTooltip(this.inputs[i], TOOLTIP_VALUE_INPUT)
}
}
// add an extra input
if (this.inputs[this.inputs.length - 1].link != undefined) {
const nextIndex = this.inputs.length
this.addInput(`${_prefix}_${nextIndex + 1}`, this.inputs[0].type)
addInputWithTooltip(this, `${_prefix}_${nextIndex + 1}`, this.inputs[0].type, TOOLTIP_VALUE_INPUT)
}
}
return r
@@ -274,7 +315,7 @@ app.registerExtension({
const onNodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined
this.addInput(`${_prefix}_1`, 'PYLIST')
addInputWithTooltip(this, `${_prefix}_1`, 'PYLIST', TOOLTIP_PYLIST_INPUT)
return r
}
@@ -287,7 +328,7 @@ app.registerExtension({
for (let i = length - 1; i >= 0; i--) {
this.removeInput(i)
}
this.addInput(`${_prefix}_1`, 'PYLIST')
addInputWithTooltip(this, `${_prefix}_1`, 'PYLIST', TOOLTIP_PYLIST_INPUT)
}
return r
}
@@ -296,16 +337,18 @@ app.registerExtension({
nodeType.prototype.onConnectionsChange = function (slotType, slot, event, link_info, data) {
const r = onConnectionsChange ? onConnectionsChange.apply(this, arguments) : undefined
if (slotType === TypeSlot.Input) {
dynamic_connection(this, slot, event, `${_prefix}_`, 'PYLIST')
dynamic_connection(this, slot, event, `${_prefix}_`, 'PYLIST', [], TOOLTIP_PYLIST_INPUT)
if (event === TypeSlotEvent.Connect && link_info) {
const fromNode = this.graph._nodes.find(
(otherNode) => otherNode.id == link_info.origin_id
)
const type = fromNode.outputs[link_info.origin_slot].type
this.inputs[slot].type = type
setTooltip(this.inputs[slot], TOOLTIP_PYLIST_INPUT)
} else if (event === TypeSlotEvent.Disconnect) {
this.inputs[slot].type = 'PYLIST'
this.inputs[slot].label = `${_prefix}_${slot + 1}`
setTooltip(this.inputs[slot], TOOLTIP_PYLIST_INPUT)
}
}
return r
@@ -316,6 +359,7 @@ app.registerExtension({
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined
// shrink outputs to 1
this.outputs[0].type = "*"
setTooltip(this.outputs[0], TOOLTIP_UNPACK_OUTPUT)
const output_len = this.outputs.length
for (let i = output_len - 1; i > 0; i--) {
this.removeOutput(i)
@@ -373,10 +417,11 @@ app.registerExtension({
this.removeOutput(i)
}
for (let i = cur_len; i < output_len; i++) {
this.addOutput(`${_prefix}_${i + 1}`, origin_inputs[i].type)
addOutputWithTooltip(this, `${_prefix}_${i + 1}`, origin_inputs[i].type, TOOLTIP_UNPACK_OUTPUT)
}
for (let i = 0; i < cur_len && i < output_len; i++) {
this.outputs[i].type = origin_inputs[i].type
setTooltip(this.outputs[i], TOOLTIP_UNPACK_OUTPUT)
}
}
}
+113 -37
View File
@@ -62,16 +62,22 @@ class SplitString:
def INPUT_TYPES(self):
return {
"required": {
"STRING": ("STRING", {"multiline": True}),
"delimiter": ("STRING", {"default": ","}),
"splitlines": ("BOOLEAN", {"default": False}),
"strip": ("BOOLEAN", {"default": False})
"STRING": ("STRING", {"multiline": True, "tooltip": "Text to split. Multiline text is supported."}),
"delimiter": ("STRING", {"default": ",", "tooltip": "Delimiter used inside each line. Leave empty to skip delimiter splitting."}),
"splitlines": ("BOOLEAN", {"default": False, "tooltip": "Split the input into lines before applying the delimiter."}),
"strip": ("BOOLEAN", {"default": False, "tooltip": "Trim leading and trailing whitespace from each item."})
}
}
TITLE = "Split String"
DESCRIPTION = "Split text into both a ComfyUI List-processing output and a PyList. Useful for prompt candidates, filenames, seed lists, and other simple batches."
RETURN_TYPES = ("STRING", "PYLIST", "INT")
RETURN_NAMES = ("STRING", "PYLIST", "length")
OUTPUT_TOOLTIPS = (
"Split items as a ComfyUI List-processing output.",
"Split items as one raw Python list value.",
"Number of split items.",
)
OUTPUT_IS_LIST = (True, False, False, )
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -105,13 +111,15 @@ class ListGetByIndex:
def INPUT_TYPES(self):
return {
"required": {
"ANY": (ANY_TYPE, {"forceInput": True}),
"index": ("INT", {"forceInput": False, "default": 0, "min": -9007199254740992, "max": 9007199254740992}),
"ANY": (ANY_TYPE, {"forceInput": True, "tooltip": "Connect a ComfyUI List-processing value."}),
"index": ("INT", {"forceInput": False, "default": 0, "min": -9007199254740992, "max": 9007199254740992, "tooltip": "Item index to read. 0 is the first item, -1 is the last item."}),
}
}
TITLE = "List: Get By Index"
DESCRIPTION = "Read one item by index from a ComfyUI List-processing input. Use this for List values, not PyList values."
RETURN_TYPES = (ANY_TYPE, )
OUTPUT_TOOLTIPS = ("The item at the selected index. Returns None when the index is out of range.",)
INPUT_IS_LIST = True
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -132,13 +140,15 @@ class PyListGetByIndex:
def INPUT_TYPES(self):
return {
"required": {
"PYLIST": ("PYLIST", {"forceInput": True}),
"index": ("INT", {"default": 0, "min": -9007199254740992, "max": 9007199254740992}),
"PYLIST": ("PYLIST", {"forceInput": True, "tooltip": "Connect a PyList, which is a raw Python list value."}),
"index": ("INT", {"default": 0, "min": -9007199254740992, "max": 9007199254740992, "tooltip": "Item index to read. 0 is the first item, -1 is the last item."}),
}
}
TITLE = "PyList: Get By Index"
DESCRIPTION = "Read one item by index from a PyList. Use this for raw Python list values, not ComfyUI List-processing values."
RETURN_TYPES = (ANY_TYPE, )
OUTPUT_TOOLTIPS = ("The item at the selected index. Returns None when the index is out of range.",)
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -157,14 +167,16 @@ class ListSlice:
def INPUT_TYPES(self):
return {
"required": {
"ANY": (ANY_TYPE, {"forceInput": True}),
"start": ("INT", {"default": 0, "min": -9007199254740992, "max": 9007199254740992}),
"end": ("INT", {"default": 0, "min": -9007199254740992, "max": 9007199254740992}),
"ANY": (ANY_TYPE, {"forceInput": True, "tooltip": "Connect the ComfyUI List-processing value to slice."}),
"start": ("INT", {"default": 0, "min": -9007199254740992, "max": 9007199254740992, "tooltip": "Slice start index, matching Python list[start:end]."}),
"end": ("INT", {"default": 0, "min": -9007199254740992, "max": 9007199254740992, "tooltip": "Slice end index. The item at this index is not included."}),
}
}
TITLE = "List: Slice"
DESCRIPTION = "Slice a ComfyUI List-processing input with Python-style start:end semantics and keep it as a List output."
RETURN_TYPES = (ANY_TYPE, )
OUTPUT_TOOLTIPS = ("The sliced ComfyUI List-processing output.",)
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True, )
FUNCTION = "run"
@@ -184,14 +196,16 @@ class PyListSlice:
def INPUT_TYPES(self):
return {
"required": {
"PYLIST": ("PYLIST", {"forceInput": True}),
"start": ("INT", {"default": 0, "min": -9007199254740992, "max": 9007199254740992}),
"end": ("INT", {"default": 0, "min": -9007199254740992, "max": 9007199254740992}),
"PYLIST": ("PYLIST", {"forceInput": True, "tooltip": "Connect the PyList to slice."}),
"start": ("INT", {"default": 0, "min": -9007199254740992, "max": 9007199254740992, "tooltip": "Slice start index, matching Python list[start:end]."}),
"end": ("INT", {"default": 0, "min": -9007199254740992, "max": 9007199254740992, "tooltip": "Slice end index. The item at this index is not included."}),
}
}
TITLE = "PyList: Slice"
DESCRIPTION = "Slice a PyList with Python-style start:end semantics and keep it as a PyList output."
RETURN_TYPES = ("PYLIST", )
OUTPUT_TOOLTIPS = ("The sliced PyList.",)
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -207,12 +221,14 @@ class PyListToList:
def INPUT_TYPES(self):
return {
"required": {
"PYLIST": ("PYLIST", {"forceInput": True}),
"PYLIST": ("PYLIST", {"forceInput": True, "tooltip": "Python list value to expand into ComfyUI List processing."}),
}
}
TITLE = "PyList To List"
DESCRIPTION = "Convert a PyList into ComfyUI List processing. Use this when downstream nodes should run once per item."
RETURN_TYPES = (ANY_TYPE, )
OUTPUT_TOOLTIPS = ("Each PyList item emitted as a ComfyUI List-processing item.",)
OUTPUT_IS_LIST = (True,)
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -229,13 +245,15 @@ class ListToPyList:
def INPUT_TYPES(self):
return {
"required": {
"ANY": (ANY_TYPE, {"forceInput": True}),
"ANY": (ANY_TYPE, {"forceInput": True, "tooltip": "ComfyUI List-processing input to collect into one Python list value."}),
}
}
TITLE = "List To PyList"
DESCRIPTION = "Collect a ComfyUI List-processing input into one PyList value. Use this when you want to stop per-item execution and treat the values as an array."
RETURN_TYPES = ("PYLIST", )
RETURN_NAMES = ("PYLIST", )
OUTPUT_TOOLTIPS = ("The whole input List collected as one Python list value.",)
INPUT_IS_LIST = True
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -261,7 +279,9 @@ class CreateList:
}
TITLE = "Create List"
DESCRIPTION = "Create a ComfyUI List-processing output from multiple inputs. Connect the last empty socket to add another input."
RETURN_TYPES = (ANY_TYPE, )
OUTPUT_TOOLTIPS = ("Input values emitted as a ComfyUI List-processing output.",)
OUTPUT_IS_LIST = (True, )
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -293,7 +313,9 @@ class CreatePyList:
}
TITLE = "Create PyList"
DESCRIPTION = "Create a PyList from multiple inputs. Use this when you want to keep an array as one value. Connect the last empty socket to add another input."
RETURN_TYPES = ("PYLIST", )
OUTPUT_TOOLTIPS = ("A raw Python list containing the input values.",)
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -324,8 +346,10 @@ class MergeList:
}
TITLE = "Merge List"
DESCRIPTION = "Concatenate multiple ComfyUI List-processing inputs into one List output."
INPUT_IS_LIST = True
RETURN_TYPES = (ANY_TYPE, )
OUTPUT_TOOLTIPS = ("The concatenated ComfyUI List-processing output.",)
OUTPUT_IS_LIST = (True, )
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -360,7 +384,9 @@ class MergePyList:
}
TITLE = "Merge PyList"
DESCRIPTION = "Concatenate multiple PyList inputs as raw Python lists."
RETURN_TYPES = ("PYLIST", )
OUTPUT_TOOLTIPS = ("The concatenated PyList.",)
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -382,15 +408,21 @@ class CreateRange:
def INPUT_TYPES(self):
return {
"required": {
"start": ("INT", {"default": 0, "min": -9007199254740992, "max": 9007199254740992}),
"stop": ("INT", {"default": 1, "min": -9007199254740992, "max": 9007199254740992}),
"step": ("INT", {"default": 1, "min": -9007199254740992, "max": 9007199254740992}),
"start": ("INT", {"default": 0, "min": -9007199254740992, "max": 9007199254740992, "tooltip": "First integer to generate."}),
"stop": ("INT", {"default": 1, "min": -9007199254740992, "max": 9007199254740992, "tooltip": "Stop boundary. This value is not included."}),
"step": ("INT", {"default": 1, "min": -9007199254740992, "max": 9007199254740992, "tooltip": "Increment between values, matching Python range()."}),
},
}
TITLE = "Create Range"
DESCRIPTION = "Generate integers with Python range(start, stop, step), output as both a ComfyUI List and a PyList. Useful for seeds and indexes."
RETURN_TYPES = ("INT", "PYLIST", "INT")
RETURN_NAMES = ("INT", "PYLIST", "length")
OUTPUT_TOOLTIPS = (
"Generated integers as a ComfyUI List-processing output.",
"Generated integers as one Python list value.",
"Number of generated items.",
)
OUTPUT_IS_LIST = (True, False, False)
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -408,15 +440,21 @@ class CreateArange:
def INPUT_TYPES(self):
return {
"required": {
"start": ("FLOAT", {"default": 0}),
"stop": ("FLOAT", {"default": 1}),
"step": ("FLOAT", {"default": 1}),
"start": ("FLOAT", {"default": 0, "tooltip": "First value to generate."}),
"stop": ("FLOAT", {"default": 1, "tooltip": "Stop boundary. This value is usually not included."}),
"step": ("FLOAT", {"default": 1, "tooltip": "Increment between values, matching numpy.arange()."}),
},
}
TITLE = "Create Arange"
DESCRIPTION = "Generate floating-point values with numpy.arange(start, stop, step), output as both a ComfyUI List and a PyList."
RETURN_TYPES = ("FLOAT", "PYLIST", "INT")
RETURN_NAMES = ("FLOAT", "PYLIST", "length")
OUTPUT_TOOLTIPS = (
"Generated values as a ComfyUI List-processing output.",
"Generated values as one Python list value.",
"Number of generated items.",
)
OUTPUT_IS_LIST = (True, False, False)
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -434,15 +472,21 @@ class CreateLinspace:
def INPUT_TYPES(self):
return {
"required": {
"start": ("FLOAT", {"default": 0}),
"stop": ("FLOAT", {"default": 1}),
"num": ("INT", {"default": 10, "min": 2}),
"start": ("FLOAT", {"default": 0, "tooltip": "First value."}),
"stop": ("FLOAT", {"default": 1, "tooltip": "Last value. numpy.linspace() includes this value."}),
"num": ("INT", {"default": 10, "min": 2, "tooltip": "Number of values to generate."}),
},
}
TITLE = "Create Linspace"
DESCRIPTION = "Generate evenly spaced values with numpy.linspace(start, stop, num), output as both a ComfyUI List and a PyList."
RETURN_TYPES = ("FLOAT", "PYLIST", "INT")
RETURN_NAMES = ("FLOAT", "PYLIST", "length")
OUTPUT_TOOLTIPS = (
"Generated values as a ComfyUI List-processing output.",
"Generated values as one Python list value.",
"Number of generated items.",
)
OUTPUT_IS_LIST = (True, False, False)
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -469,8 +513,10 @@ class Pack:
}
TITLE = "Pack"
DESCRIPTION = "Bundle multiple values, including different ComfyUI types, into one PACK with each input name, type, and value preserved. Use Unpack to restore them."
RETURN_TYPES = ("PACK", )
RETURN_NAMES = ("PACK", )
OUTPUT_TOOLTIPS = ("A List-Utils container that preserves input names, types, and values.",)
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -502,7 +548,7 @@ class Unpack:
def INPUT_TYPES(self):
return {
"required": {
"PACK": ("PACK", ),
"PACK": ("PACK", {"tooltip": "Connect a PACK created by the Pack node. Outputs are rebuilt from its saved names and types."}),
},
"hidden": {
"unique_id": "UNIQUE_ID",
@@ -512,8 +558,10 @@ class Unpack:
}
TITLE = "Unpack"
DESCRIPTION = "Extract the original values from a PACK. When connected to Pack, outputs are created from the Pack input names and types."
RETURN_TYPES = ByPassTypeTuple(("*", ))
RETURN_NAMES = ByPassTypeTuple(("value_1", ))
OUTPUT_TOOLTIPS = ByPassTypeTuple(("A value stored in the PACK. Outputs grow dynamically to match the connected Pack.",))
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -538,13 +586,15 @@ class GetLength:
def INPUT_TYPES(self):
return {
"required": {
"ANY" : (ANY_TYPE, {}),
"ANY" : (ANY_TYPE, {"tooltip": "Value to pass to len(). Works with List, PyList, strings, and other values that support len()."}),
},
}
TITLE = "Get Length"
DESCRIPTION = "Apply Python len() to the input and display the result on the node."
RETURN_TYPES = ("INT", )
RETURN_NAMES = ("length", )
OUTPUT_TOOLTIPS = ("The result of len(value).",)
FUNCTION = "run"
CATEGORY = "list_utils"
OUTPUT_NODE = True
@@ -562,7 +612,7 @@ class GetShape:
def INPUT_TYPES(self):
return {
"required": {
"tensor" : ("IMAGE,LATENT,MASK", {}),
"tensor" : ("IMAGE,LATENT,MASK", {"tooltip": "IMAGE, LATENT, or MASK whose shape you want to inspect."}),
},
"hidden": {
"unique_id": "UNIQUE_ID",
@@ -572,8 +622,15 @@ class GetShape:
}
TITLE = "Get Shape"
DESCRIPTION = "Inspect width, height, batch size, and channels for IMAGE, LATENT, or MASK inputs. LATENT dimensions are converted to image-space size."
RETURN_TYPES = ("INT", "INT", "INT", "INT")
RETURN_NAMES = ("width", "height", "PyList_size", "channels")
OUTPUT_TOOLTIPS = (
"Width. LATENT inputs are multiplied by 8 to report image-space width.",
"Height. LATENT inputs are multiplied by 8 to report image-space height.",
"Batch size. The output name is PyList_size for compatibility with existing workflows.",
"Channel count. MASK inputs may be reported as 1 channel.",
)
FUNCTION = "run"
CATEGORY = "list_utils"
OUTPUT_NODE = True
@@ -625,13 +682,18 @@ class AnyToDict:
def INPUT_TYPES(self):
return {
"required": {
"ANY" : (ANY_TYPE, {}),
"ANY" : (ANY_TYPE, {"tooltip": "Any value to inspect. dict values pass through; objects with __dict__ are converted with vars()."}),
},
}
TITLE = "Any To Dict"
DESCRIPTION = "Convert any value to a dict when possible and also return its string representation. Intended for debugging."
RETURN_TYPES = ("DICT", "STRING")
RETURN_NAMES = ("DICT", "str()")
OUTPUT_TOOLTIPS = (
"The value as a dict. Returns an empty dict when conversion is not available.",
"The str() representation of the input value.",
)
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -653,7 +715,7 @@ class GetWidgetsValues:
def INPUT_TYPES(self):
return {
"required": {
"ANY" : (ANY_TYPE, {}),
"ANY" : (ANY_TYPE, {"tooltip": "Connect any output from the node whose widget values you want to inspect. The value is used only to locate the upstream node."}),
},
"hidden": {
"unique_id": "UNIQUE_ID",
@@ -663,8 +725,10 @@ class GetWidgetsValues:
}
TITLE = "Get Widgets Values"
DESCRIPTION = "Read and display the upstream node's widgets_values. Intended for debugging node settings."
RETURN_TYPES = ("LIST", )
RETURN_NAMES = ("LIST", )
OUTPUT_TOOLTIPS = ("The upstream node's widgets_values list.",)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -716,13 +780,15 @@ class AnyCast:
def INPUT_TYPES(self):
return {
"required": {
"ANY" : (ANY_TYPE, {}),
"TYPE": (["*", "STRING", "INT", "FLOAT", "BOOLEAN", "IMAGE", "LATENT", "MASK", "NOISE", "SAMPLER", "SIGMAS", "GUIDER", "MODEL", "CLIP", "VAE", "CONDITIONING", "PYLIST"], {}),
"ANY" : (ANY_TYPE, {"tooltip": "Value to relabel or convert."}),
"TYPE": (["*", "STRING", "INT", "FLOAT", "BOOLEAN", "IMAGE", "LATENT", "MASK", "NOISE", "SAMPLER", "SIGMAS", "GUIDER", "MODEL", "CLIP", "VAE", "CONDITIONING", "PYLIST"], {"tooltip": "ComfyUI type shown on the output socket. STRING, INT, FLOAT, and BOOLEAN also attempt Python conversion."}),
},
}
TITLE = "Any Cast"
DESCRIPTION = "Output any value as the selected ComfyUI type. STRING, INT, FLOAT, and BOOLEAN also perform Python conversion."
RETURN_TYPES = (ANY_TYPE, )
OUTPUT_TOOLTIPS = ("The value output as the selected TYPE.",)
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -739,14 +805,16 @@ class PyListItemCast:
def INPUT_TYPES(self):
return {
"required": {
"PYLIST": ("PYLIST", {"forceInput": True}),
"TYPE": (["STRING", "INT", "FLOAT", "NUMBER", "BOOLEAN", "PYLIST"], {}),
"PYLIST": ("PYLIST", {"forceInput": True, "tooltip": "PyList whose items should be converted."}),
"TYPE": (["STRING", "INT", "FLOAT", "NUMBER", "BOOLEAN", "PYLIST"], {"tooltip": "Conversion applied to each PyList item. NUMBER is the same as FLOAT."}),
},
}
TITLE = "PyList Item Cast"
DESCRIPTION = "Convert each item inside a PyList to STRING, INT, FLOAT, BOOLEAN, and similar basic types."
RETURN_TYPES = ("PYLIST", )
RETURN_NAMES = ("PYLIST", )
OUTPUT_TOOLTIPS = ("The PyList after converting each item.",)
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -763,8 +831,8 @@ class ListDir:
def INPUT_TYPES(self):
return {
"required": {
"path": ("STRING", {"default": ""}),
"pattern": ("STRING", {"default": "*"})
"path": ("STRING", {"default": "", "tooltip": "Folder path to list."}),
"pattern": ("STRING", {"default": "*", "tooltip": "Pattern passed to Path.glob, for example *.png or **/*.json."})
},
"optional": {},
"hidden": {
@@ -775,8 +843,14 @@ class ListDir:
}
TITLE = "List Dir"
DESCRIPTION = "List files and folders under a path using a glob pattern, output as both a ComfyUI List and a PyList."
RETURN_TYPES = ("STRING", "PYLIST", "INT")
RETURN_NAMES = ("STRING", "PYLIST", "length")
OUTPUT_TOOLTIPS = (
"Matched items as a ComfyUI List-processing output. Values are Path objects.",
"Matched items as one Python list value.",
"Number of matched items.",
)
OUTPUT_IS_LIST = (True, False, False, )
FUNCTION = "run"
CATEGORY = "list_utils"
@@ -826,7 +900,7 @@ class Exec:
def INPUT_TYPES(self):
return {
"required": {
"FUNC": ("STRING", {"forceInput": False, "multiline": True, "default": "result = x[0]"})
"FUNC": ("STRING", {"forceInput": False, "multiline": True, "default": "result = x[0]", "tooltip": "Python code to execute. Input values are x[0], x[1], ... Assign the value to return to result."})
},
"optional": {},
"hidden": {
@@ -837,7 +911,9 @@ class Exec:
}
TITLE = "Exec"
DESCRIPTION = "Execute Python code with input values available as x[0], x[1], ... and return result. Use only in trusted workflows."
RETURN_TYPES = (ANY_TYPE, )
OUTPUT_TOOLTIPS = ("The value assigned to result inside FUNC.",)
FUNCTION = "run"
CATEGORY = "list_utils"