Extensions/ComfyUI-NTX-support-nodes
ComfyUI Extension

ComfyUI-NTX-support-nodes

A ComfyUI extension.

By nekotxt·Created 7 months ago·Updated 17 days ago· 0
nekotxt/ComfyUI-NTX-support-nodes
Nodes
On cloudLocal install
Stars0
Updated17 days ago
Readme

ComfyUI-NTX-support-nodes

A collection of custom nodes for ComfyUI. All nodes are registered with the NTX prefix (e.g. NTX Pipe Custom) and appear under the NTX-support-nodes category in the node menu. The right-click menu entries added by the addon carry the same prefix; it is omitted throughout this document.

Configuration and data files (prompt library, custom pipe templates, config.yaml) are read from input/ntx_data/ inside the ComfyUI folder (falling back to the ntx_data/ folder bundled with the addon).

Recurring data type:

  • LORA_STACK: a list of (lora_name, strength_model, strength_clip) tuples, passed between the LoRA nodes below. This is the same data type used by usual lora stacks in other extensions;
  • DICT: a dictionary of key:value pairs;

Global commands

Addon-wide commands that are not tied to a specific node.

Load template workflow

Inserts one or more template workflows — picked from a folder of ready-made workflows — into the current graph. Rather than replacing the open workflow, each template's nodes are pasted into it (at the mouse position) and left selected — when several templates are loaded at once, the last one's nodes remain selected — so they can be dragged into place immediately; any nested subgraph definitions the templates use come along with them.

The command is available from three places:

  • the command palette (and Settings → Keybinding, where its shortcut can be rebound), with a default keybinding of Alt+W — the nodes are dropped where the mouse hovers;
  • the canvas right-click menu (Load template workflow) — the nodes are dropped where the menu was opened.

Templates are read from a subfolder of the ComfyUI user workflows folder, named by the templates_subdir entry of input/ntx_data/config.yaml:

templates_subdir: my_templates

Every .json file found in that folder, at any depth, is offered. Leaving the entry unset (or empty) scans the whole workflows folder instead. The setting is read when ComfyUI starts, so pointing the picker elsewhere takes a restart — the Refresh button below only re-scans the folder currently configured.

Invoking the command opens a tree picker organised by subfolder:

  • a filter box narrows the list to files whose name matches the typed text;
  • folders can be expanded/collapsed; a file is chosen by clicking it, and confirmed with the Load button, a double-click, or Enter;
  • Ctrl+click (or Cmd+click) adds/removes files to a multi-selection instead. The selected templates are inserted in the order they were picked, side by side from left to right (aligned to the same top edge, with a small gap between them); when two or more files are selected, each row shows a numbered badge with its position in that order, and the Load button becomes Load (n). To change the order, Ctrl+click an entry off and back on; a plain click collapses the selection back to the single clicked file;
  • the multi-selection survives filter changes — files picked under one filter term stay selected (and counted on the Load button) while a different term is typed, so a selection can be built across several searches;
  • the Refresh button re-scans the templates folder on disk (rebuilding the cached list), so templates added, renamed or removed there show up without reloading the page; the current filter text and selection are kept (entries that no longer exist are dropped);
  • Cancel, Escape, or a click outside the dialog closes it without loading;
  • the most recently loaded template is remembered and pre-selected (with its folders expanded) the next time the picker is opened.

Two checkboxes sit at the bottom of the picker:

  • Automatically connect added templates — from the second template on, wires a pipe output of the previously inserted template to a pipe input of the one just inserted (the previous template's rightmost column and the new one's leftmost column are each scanned top to bottom for the first node exposing a DICT pipe slot). On by default, then remembered for the rest of the session;
  • Save as preset — see below. Always unchecked when the picker opens.

Presets

A preset is a named, ordered set of templates. Instead of picking the same files one by one every time, choosing a preset from the combobox at the bottom of the picker drops the current selection and selects the preset's templates in the order the preset lists them — which is also the order they are inserted and chained. The selection can then still be adjusted by hand (clicking any entry afterwards resets the combobox to — none —).

Presets are stored in input/ntx_data/workflow_template_presets.yaml: one entry per preset, the key being the name shown in the combobox and the list holding the template paths relative to the templates folder. The .json extension is optional, so the file stays readable:

Anima:
    - model Anima diffusion
    - prompt
    - image size
    - prepare conditionings
    - sampler
    - save images
  • templates listed in a preset but not found in the templates folder are skipped, and a warning toast names them — a preset keeps working after a template is renamed or removed;
  • the file is re-read by the Refresh button (together with the folder re-scan), so it can be hand-edited while ComfyUI is running;
  • ticking Save as preset before pressing Load stores the current selection as a preset: a name is asked for first (pre-filled with the selected preset's name, if any), and if that name is already taken the Save button becomes Overwrite, requiring a second click to replace it. Cancelling that prompt (Cancel, Escape, or a click outside it) aborts the whole action — nothing is saved and nothing is added to the canvas, and the picker stays open with the selection untouched. Once saved, the templates are loaded as usual.

Saving rewrites the yaml file in place, keeping its comments, its entry order and the spelling of an overwritten preset's name (names are matched case-insensitively, so saving anima over Anima updates that preset instead of adding a second one).


PipeCustom

PipeCustom node

A "pipe" node used to bundle many values into a single wire. The pipe itself is a dictionary (DICT type): every connected input is stored in the dictionary under the input's name, and every output reads the value with the same name back out of the dictionary. The set of custom inputs and outputs is defined per-node by the user through an editor dialog, and inputs and outputs are configured independently — a node may, for example, only add values to the pipe (inputs only) or only extract them (outputs only).

Inputs

| Input | Type | Description | |---|---|---| | pipe | DICT (optional) | An upstream pipe to extend. If omitted, a new empty pipe is created. The input pipe is cloned, so downstream changes never affect the upstream dictionary. | | inputs_data | STRING (hidden) | JSON produced by the editor dialog describing the configured inputs/outputs ({"inputs": [...], "outputs": [...]}). Managed entirely by the frontend; not edited by hand. | | strict | BOOLEAN | When enabled, a warning toast (and log entry) lists every configured output whose name is not found in the pipe — typically a typo between an upstream input and this output. The per-type default is returned either way; execution is not interrupted. Default false. | | custom inputs | user-defined | One slot per configured input, with the chosen name and type. Only connected (non-None) values are written into the pipe. |

Outputs

| Output | Type | Description | |---|---|---| | pipe | DICT | The merged pipe dictionary (input pipe + values from the connected custom inputs). | | custom outputs | user-defined | One slot per configured output; each returns pipe[name]. If the name is not present in the pipe, a per-type default is returned (0 for INT, 0.0 for FLOAT, "" for STRING, False for BOOLEAN, [] for LORA_STACK / CONTROL_NET_STACK / LIST, {} for DICT, None otherwise). |

Up to 30 custom inputs and 30 custom outputs per node. The names pipe, inputs_data and strict are reserved and cannot be used for custom entries.

Frontend

The node body shows two buttons, Edit inputs… and Edit outputs…, which open the editor dialog for the corresponding side. In the dialog:

  • + Add appends a new entry; each row has a name field and a type dropdown (IMAGE, MASK, LATENT, MODEL, CLIP, VAE, CONDITIONING, INT, FLOAT, STRING, BOOLEAN, LORA_STACK, CONTROL_NET_STACK, DICT, LIST, *).
  • The name field offers autocompletion: suggested names are the entries configured on the other side of the node, plus every key written into the pipe by the PipeCustom nodes found upstream (the graph is walked breadth-first through all DICT-typed inputs — so PipeMerge branches and pipe-passing nodes are traversed — up to 100 nodes). Picking a suggested name also presets the row's type to the type known for that key; it can still be changed manually.
  • Rows can be drag-reordered with the handle and removed with .
  • Renaming a row keeps its slot and any connected wires — only removing a row (or changing its type) drops the wire. A rename is also propagated to the entry with the same name on the other side (inputs ↔ outputs), so the pipe key keeps matching end to end; an info toast lists the propagated renames. Propagation is skipped if the new name is already taken on that side.
  • Copy from inputs/outputs replaces the list with the entries of the other side.
  • Load template… opens a picker with predefined property sets loaded from input/ntx_data/custom_pipe_presets.yaml; the chosen template's properties are appended, skipping names already present. Ticking Replace current entries in the picker clears the list before the template is applied instead of appending to it (nothing is committed until the editor dialog is confirmed with OK).
  • Save as template… stores the current list as a named template in the same file, so it can be reloaded later on any PipeCustom node. If the name is already taken, the button changes to Overwrite and a second click is required to replace the existing template.
  • Names are validated on OK (non-empty, no duplicates, no reserved names). If a name exists on both sides with different types, a warning toast is shown.
  • Enter (while editing a name) confirms, Escape cancels.

Templates are stored in input/ntx_data/custom_pipe_presets.yaml: one entry per template, the key being the name shown in the picker and its mapping holding the properties in order, as name: type pairs. A missing (empty) type, or the quoted '*' wildcard, means any type:

Image:
    width: INT
    height: INT
    latent: LATENT
    model_name: '*'

The file is re-read every time the picker is opened, so it can be hand-edited while ComfyUI is running; an entry that is not a mapping of properties is skipped with a warning in the log. Saving rewrites the whole file with the four standard comment lines at the top — any other comment it contained is dropped — and refuses to write if the file exists but cannot be parsed, rather than replacing what it failed to read.

Right-click menu options on the node:

  • Edit pipe inputs… / Edit pipe outputs… — same as the two buttons.
  • Split custom pipe — creates a second PipeCustom node to the right, moves all custom outputs (and their outgoing links) onto it, connects the original's pipe output to the new node's pipe input, and shifts the downstream nodes/groups to make room. The original node keeps only its inputs.
  • Merge custom pipes — the reverse: merges the right-clicked node back into the upstream PipeCustom it is connected to (the source takes over the target's outputs and outgoing links, the target is deleted and downstream nodes are shifted back). Requires the target to have no non-pipe inputs connected and the source no non-pipe outputs connected.

Right-click menu option on the canvas (only shown while the selection contains at least one PipeCustom node):

  • Merge all selected custom pipe nodes — runs Merge custom pipes on every selected PipeCustom node in a single pass. Nodes that do not meet the merge requirements above are skipped silently (no per-node warnings); a final toast reports how many of the selected nodes were merged. In a selected chain of three or more pipes a node skipped only because its source still had connected outputs can be picked up by running the command again.

LoraStack

LoraStack node

Builds a LORA_STACK from a list of LoRAs configured directly on the node through a custom widget (no model is loaded here — combine with ApplyLoraStack to actually apply the stack).

Inputs

| Input | Type | Description | |---|---|---| | loras_data | STRING (hidden) | JSON serialisation of the widget state ({"commonStrength": bool, "loras": [{enabled, name, modelStrength, clipStrength}, ...]}). Managed by the frontend widget. A bare JSON array (the old format) is still accepted. | | lora_stack | LORA_STACK (optional) | An upstream stack to extend; the configured LoRAs are appended to it. |

Outputs

| Output | Type | Description | |---|---|---| | lora_stack | LORA_STACK | The input stack (if any) plus one entry per enabled row with a real LoRA selected (rows set to none are skipped). When Common strength is on, the model strength is also used as the clip strength. |

Frontend

The loras_data widget is replaced by a custom LoRA list UI:

  • Common strength toggle in the header: when on, the clip strength column is hidden and the model strength is used for both.
  • Each row: a drag handle (⠿, drag to reorder), an on/off toggle, the LoRA name, and M (model) / C (clip) strength widgets. The strength pills step ±0.05 with the ◀ ▶ arrows (CTRL+click for ±0.01) and scrub on horizontal drag (CTRL for fine steps); a plain click on the value opens an input box to type it directly (Enter or clicking away confirms, Escape cancels).
  • Clicking the LoRA name opens a flat dropdown with a live filter box; the ◀ ▶ arrows next to the name step to the previous/next LoRA in the list.
  • The 📂 button (or Shift+click on the LoRA name) opens a tree selector organised by subfolder, with a search box, Refresh button (re-scans the loras folder on disk via the reload_loras_list backend route), OK/Cancel, double-click to confirm, and Enter/Escape keys.
  • Rows referencing a file that is missing from the loras folder get a red outline; rows that duplicate an earlier entry (which ApplyLoraStack would skip) get an amber outline. The tooltip on the name explains the warning.
  • + Add LoRA appends a row; right-click on a row offers Delete, Move up, Move down plus the stack-level actions; right-click elsewhere on the widget (header, add button) opens the stack-level menu directly: Enable all, Disable all, Remove disabled, Copy stack as text and Paste from text.
  • Copy stack as text puts the enabled rows on the clipboard in <lora:name:model[:clip]> format (one per line); Paste from text parses any text containing such tags and appends the entries, matching names against the known list (a missing extension defaults to .safetensors, and bare basenames are resolved against subfolders).

Right-click menu options on the node:

  • Rebuild LoraStack UI — recreates the custom widget in place (recovery for the rare case where the node deserialises with the raw-JSON fallback widget).
  • Reload Lora List from disk — re-scans the loras folder on the backend and rebuilds the widget so the fresh list is available immediately.

MergeLoraStacks

MergeLoraStacks node

Concatenates two LORA_STACKs into one.

Inputs

| Input | Type | Description | |---|---|---| | lora_stack_1 | LORA_STACK (optional) | First stack; its entries come first in the result. | | lora_stack_2 | LORA_STACK (optional) | Second stack; appended after the first. |

Outputs

| Output | Type | Description | |---|---|---| | lora_stack | LORA_STACK | All entries of stack 1 followed by all entries of stack 2. Missing inputs are treated as empty. |


ApplyLoraStack

ApplyLoraStack node

Applies every LoRA in a stack to a model (and optionally a CLIP), with duplicate detection, an in-memory file cache, and optional download of missing files from cloud storage.

For each (name, strength_model, strength_clip) entry:

  • entries with both strengths equal to 0 are skipped;
  • a LoRA already applied earlier in the stack (same name) is skipped;
  • the file is resolved in the loras model folder. If it is missing and cloud download is enabled in config.yaml (download_missing_loras, cloud_storage_id; active on Linux only), the node attempts to fetch it; otherwise a warning toast is emitted and the entry is skipped;
  • the LoRA weights are loaded from disk and kept in a cache shared by all ApplyLoraStack nodes (size limited by cache.max_loras in config.yaml, default 5, oldest evicted first), then applied with comfy.sd.load_lora_for_models.

Inputs

| Input | Type | Description | |---|---|---| | lora_stack | LORA_STACK | The stack to apply. An empty or missing stack passes model/clip through unchanged. | | model | MODEL | The model to patch. | | clip | CLIP (optional) | The CLIP to patch. If omitted, only the model is patched. |

Outputs

| Output | Type | Description | |---|---|---| | lora_stack | LORA_STACK | The stack of LoRAs actually applied (skipped/failed entries removed) — useful for logging or converting to a string. | | model | MODEL | The patched model. | | clip | CLIP | The patched CLIP (or the input value if none was provided). |


ConvertLoraStackToString

ConvertLoraStackToString node

Formats a LORA_STACK as text, one LoRA per line, in the <lora:name:model_strength:clip_strength> syntax (strengths rounded to 2 decimals). Entries missing a strength default to the model strength, or to 1.0 when only the name is present.

Inputs

| Input | Type | Description | |---|---|---| | lora_stack | LORA_STACK (optional) | The stack to format. Empty/missing produces an empty string. |

Outputs

| Output | Type | Description | |---|---|---| | stack_text | STRING | One <lora:...> line per entry. |


ConvertLoraStringToStack

ConvertLoraStringToStack node

The reverse operation: parses <lora:name:strength[:clip_strength]> references out of a text prompt and turns them into a LORA_STACK, returning the prompt cleaned of the tags.

  • If the optional :clip_strength part is missing, the model strength is used for both.
  • A LoRA name without extension gets .safetensors appended; path separators are normalised.
  • The cleaned prompt has all <...> angle-bracket sections removed (not only LoRA tags), with leftover runs of spaces collapsed (newlines preserved).

Inputs

| Input | Type | Description | |---|---|---| | prompt | STRING | The text to parse. | | initial_lora_stack | LORA_STACK (optional) | A stack to prepend; the parsed entries are appended to it. |

Outputs

| Output | Type | Description | |---|---|---| | clean_prompt | STRING | The prompt with the angle-bracket sections removed. | | final_lora_stack | LORA_STACK | initial_lora_stack + the entries parsed from the prompt. |


ModelInfo

ModelInfo node

A "settings sheet" for a model: it groups the model selection and its recommended generation parameters in one node and simply passes every value through to its outputs, so they can be wired to loaders, samplers, etc. The values can be loaded from / saved to a .ntxdata sidecar file stored next to the model file (see Frontend below).

The model_name combo lists both checkpoints and diffusion models, prefixed with the model kind: ckpt:<name> for models/checkpoints, diff:<name> for models/diffusion_models. On execution the prefix is stripped: the bare name is emitted on model_name and the resolved folder type (checkpoints / diffusion_models) on model_type.

Inputs

| Input | Type | Description | |---|---|---| | model_name | COMBO | The model, prefixed with ckpt: or diff:. | | clip_name, clip_name_2, clip_name_3 | COMBO | Up to three text encoders (None = unused). | | vae_name | COMBO | VAE to use, or Baked VAE for the one embedded in the checkpoint. | | clip_skip | INT | CLIP skip (≤ 0, default -1). | | shift | FLOAT | Sampling shift (model-dependent). | | guidance | FLOAT | Guidance value (e.g. Flux). | | steps | INT | Recommended step count. | | cfg | FLOAT | Recommended CFG scale. | | sampler_name, scheduler | COMBO | Recommended sampler / scheduler. | | model_prompt_positive, model_prompt_negative | STRING | Prompt snippets associated with the model (e.g. trigger words, quality tags). | | notes | STRING (multiline) | Free-form notes about the model. |

Outputs

Every input is repeated as an output with the same name (combos are emitted as wildcard type so they can connect to any matching input), plus:

| Output | Type | Description | |---|---|---| | model_type | STRING | The folder type decoded from the prefix: checkpoints or diffusion_models. |

Frontend

Right-click menu options on the node:

  • Load Model Info — asks the backend for the data stored in the model's .ntxdata sidecar file and fills the node's widgets with it. Fields missing from the file are left unchanged and listed in a warning toast.
  • Save Model Info — sends the current widget values to the backend, which writes them into the sidecar data. Note: the file is written with a .ntxdata_new extension (next to the model), so the existing .ntxdata is never overwritten directly.

LoadPrompt

LoadPrompt node

Picks a prompt from a nested, file-based prompt library and outputs its text (plus an optional preview image). The library lives in input/ntx_data/prompts/ and is merged from two sources:

  • every *.yaml / *.yml file in the top level of that folder: dictionary keys become nested category paths and list items become the selectable leaves. A leaf is either a plain string (used as both id and prompt text, or split as id::text), or a dictionary with name (the id) and positive (the prompt) keys — any extra keys become named parameters used by the LoadPromptAdvanced variant;
  • every *.txt file inside subdirectories of the folder: the relative path without extension becomes the id (e.g. scenes/fantasy/castle.txtscenes/fantasy/castle) and the file content the prompt.

For example, this YAML file shows the three leaf forms:

scenes:
  fantasy:
    - a misty castle on a cliff at dawn
    - dungeon::a torch-lit stone dungeon, dripping water, volumetric light
  sci-fi:
    - name: space station
      positive: interior of a vast orbital space station, earth visible through the windows

| Id | Prompt text | |---|---| | scenes/fantasy/a misty castle on a cliff at dawn | the id itself (plain string leaf) | | scenes/fantasy/dungeon | a torch-lit stone dungeon, dripping water, volumetric light (id::text leaf) | | scenes/sci-fi/space station | interior of a vast orbital space station, earth visible through the windows (dictionary leaf) |

The library is cached in memory on the backend, and the option list of the id combo is re-read from the files on disk every time the node definitions are fetched — on page load and when they are reloaded with R (Refresh Node Definitions) — so prompts added on disk show up without a backend restart. The tree picker's Refresh button and the right-click reload entry (see Frontend) also update the dropdown of every LoadPrompt* node immediately. Any id value is accepted at execution time, so a workflow saved with an id that was later removed from the library still runs (the prompt box keeps its saved text).

Inputs

| Input | Type | Description | |---|---|---| | id | COMBO | The prompt id (category/.../name). The option list mirrors the library on disk (see above). | | prompt | STRING (multiline) | The prompt text. The frontend fills it automatically when an id is selected, and it can be freely edited afterwards. If left empty (e.g. headless/API execution), the library text for the id is used. |

Outputs

| Output | Type | Description | |---|---|---| | prompt | STRING | The prompt text (edited value, or the library text if the box was empty). | | id | STRING | The selected id. | | image | IMAGE | The preview image stored next to the prompt id (same path with a .png / .jpeg / .jpg extension), or nothing if no such file exists. |

Frontend

  • Shift+click on the id widget opens a tree picker organised by category, with a filter box, OK/Cancel, double-click to confirm, and Enter/Escape keys. Its Refresh button makes the backend re-read the prompt files from disk, rebuilds the tree and updates the id dropdown of every LoadPrompt* node in the graph.
  • The tree picker shows a preview pane below the tree: the library text of the highlighted prompt, together with its thumbnail when an image sits next to the prompt file.
  • Selecting an id (from the picker or the combo) automatically fills the prompt textbox with the library text. If the current text was edited manually (it differs from the library text of the previously selected id), a confirmation dialog asks before replacing it — cancelling keeps the edited text while still switching the id.

Right-click menu options on the node:

  • Rebuild Prompts List from disk — same effect as the tree picker's Refresh button: the backend re-reads the prompt files, the cached maps are refreshed and the id dropdowns are updated.
  • Pick prompt — opens the same tree picker (filter, preview pane, double-click to confirm) purely to copy a prompt's library text to the clipboard; nothing on the node is changed. Unlike the other entry, this one is added to the right-click menu of every node and of the empty canvas, not just LoadPrompt* nodes, so a prompt can be grabbed from anywhere in the graph. When invoked on a LoadPrompt* node the picker starts on that node's current id; elsewhere (another node or the empty canvas) it reopens on the last prompt picked this way, so the previous selection is remembered across calls (until the page is reloaded). A toast confirms the copy (and a hidden-textarea fallback is used when the browser clipboard API is unavailable, e.g. over plain HTTP).

The same frontend behaviour (tree picker, prompt auto-fill, RMB reload) is shared by the LoadPromptAdvanced and LoadPromptChar variants described below.


LoadPromptAdvanced

LoadPromptAdvanced node

Same as LoadPrompt, with three extra free-form string parameters that are passed straight through to the outputs, plus a dictionary output carrying all the extra keys of the entry.

Differences from LoadPrompt:

  • three additional STRING inputs, param1, param2, param3, each repeated unchanged as an output with the same name;
  • an additional params DICT output with every extra key:value pair of the selected library entry (empty for plain-string leaves) — useful downstream with ReplaceTextParameters, and not limited to three values or to widget renaming;
  • when the selected library entry is a dictionary carrying extra keys besides name and positive, the frontend fills the param widgets from those keys when the id is selected. A widget is matched by its current (user-facing) name, so renaming e.g. param2 to outfit on the node makes it pick up the entry's outfit value. Params the entry does not define are cleared.

Example

A YAML file in the prompt library:

characters:
  fantasy:
    - name: elf ranger
      positive: an elf ranger with a longbow, forest background
      outfit: green hooded cloak, leather armor
      hair: long silver hair

This defines the id characters/fantasy/elf ranger, whose prompt is the positive text and whose extra parameters are outfit and hair. On a LoadPromptAdvanced node where param1 has been renamed to outfit and param2 to hair, selecting that id fills the widgets as:

| Widget | Value after selection | |---|---| | prompt | an elf ranger with a longbow, forest background | | outfit (renamed param1) | green hooded cloak, leather armor | | hair (renamed param2) | long silver hair | | param3 | cleared (the entry does not define a param3 key) |

LoadPromptChar

LoadPromptChar node

Same as LoadPrompt, specialised for character prompts to be saved/reused by name.

Differences from LoadPrompt:

  • an additional STRING input save_name, passed through unchanged to the save_name output (like the params of LoadPromptAdvanced, its widget is filled from the library entry's extra keys when an id is selected);
  • the prompt text is emitted on an output named char instead of prompt;
  • there are no id and image outputs — the node outputs only char and save_name.

ReplaceTextParameters

ReplaceTextParameters node

Replaces named placeholders inside a text with values taken from a parameters dictionary. Placeholders are written as %name% or %%name%% (the double-%% form is resolved first, so it survives inside text that also uses single % signs). A placeholder whose name is not found in the dictionary is replaced with an empty string.

The special form %date:FORMAT% (or %%date:FORMAT%%) inserts the current date/time instead of a dictionary value. FORMAT uses JavaScript-style tokens, converted internally to Python strftime: YYYY/yy (year), MMMM/MMM/MM (month name / short name / number), DD (day), DDDD (day of year), HH (hour), mm (minutes), ss (seconds).

Example

With text = in the style of %artist%, generated %%date:YYYY-MM-DD%% and a parameters dictionary containing {"artist": "anime"}, the output is in the style of anime, generated 2026-07-02.

Inputs

| Input | Type | Description | |---|---|---| | text | STRING | The text containing the placeholders. | | parameters | DICT (optional) | The {name: value} dictionary used for the replacements. If missing, every non-date placeholder resolves to an empty string. |

Outputs

| Output | Type | Description | |---|---|---| | text | STRING | The text with all placeholders replaced. |

Frontend

  • The node body shows a read-only result area under the text widget, filled with the replaced text whenever the node executes. The content is display-only and is not saved into the workflow or the API prompt; if the node is skipped because its inputs are unchanged (cached), the area keeps its previous content.

LazySelectAny

LazySelectAny node

A branch selector with lazy evaluation: it returns the value of one of its five wildcard inputs, chosen by index — and only the selected branch of the graph is executed. The node uses ComfyUI's lazy-input mechanism (check_lazy_status) to request the evaluation of just the selected input, so the upstream nodes feeding the unselected inputs are never run. This makes it useful as a switch between alternative (possibly expensive) sub-graphs, e.g. two different image-processing chains.

Inputs

| Input | Type | Description | |---|---|---| | select | INT | Index of the input to return (0–4). | | input0input4 | any (optional, lazy) | The selectable values. All five slots accept any data type; only the one addressed by select is evaluated. |

Outputs

| Output | Type | Description | |---|---|---| | output | any | The value of the selected input (None if that slot is not connected). |


PreviewAsText

PreviewAsText node

Shows any value as text on the node, like the core Preview as Text node — but it is not an output node, so placing it in a workflow never triggers an execution by itself. The node only runs when a downstream node actually consumes its text output; wire the value through it rather than dead-ending into it (or use the right-click queue entry below to run it on demand). The preview refreshes whenever the node executes.

The value is converted to text as follows: strings are shown as-is; ints, floats and booleans via str(); anything else is serialised as indented JSON, falling back to str() (tensors are printed with 6 edge items per dimension), and finally to a source exists, but could not be serialized. message. A missing value shows None.

Inputs

| Input | Type | Description | |---|---|---| | source | any | The value to preview. |

Outputs

| Output | Type | Description | |---|---|---| | text | STRING | The text representation of source, as shown in the preview. |

Frontend

  • The node body shows a read-only Preview area, filled with the text when the node executes.
  • A Markdown / Plaintext toggle switches the preview between a rendered-markdown view and a plain textarea (default: Plaintext). The toggle and the preview content are display-only and are not saved into the workflow or the API prompt.

Right-click menu option on the node:

  • Queue (this node as output) — queues the current workflow with this node as the only execution target, as if it were an output node: exactly its branch runs (upstream dependencies included), and every other output node in the workflow is skipped. The saved workflow is not modified. Muted/bypassed nodes and nodes inside subgraphs cannot be queued this way (a warning toast is shown). Note: a run forced this way is cached separately from a normal run, so the node re-executes the first time it is reached through the regular queue afterwards.

PreviewImage

PreviewImage node

Shows image previews on the node, like the core Preview Image node — but, as with PreviewAsText, it is not an output node: it never triggers an execution by itself and only runs when a downstream node consumes its images output. Unlike the core node it therefore has a pass-through output, so it can sit in the middle of an image chain and preview whatever flows through.

The previews are written to ComfyUI's temporary folder (one PNG per image in the batch, low compression) and embed the prompt/workflow metadata, exactly like the core node (metadata is omitted when ComfyUI runs with --disable-metadata).

Inputs

| Input | Type | Description | |---|---|---| | images | IMAGE | The image batch to preview. |

Outputs

| Output | Type | Description | |---|---|---| | images | IMAGE | The input batch, passed through unchanged. |

Frontend

Right-click menu option on the node:

  • Queue (this node as output) — same behaviour as on PreviewAsText: queues the workflow with just this node's branch as the execution target.

Reroute nodes

Reroute node

A family of pass-through nodes (under reroute in the node menu), one per data type, used to organise the wires of a workflow. Each node has a single input and a single output and forwards whatever it receives, unchanged. The input is optional: when it is left disconnected the node outputs a type-appropriate default value instead, so a reroute can also serve as a source of an "empty" value.

Available variants (slot name — default output when the input is disconnected):

  • RerouteAnyvalue, accepts any type (None)
  • RerouteBooleanboolean (False)
  • RerouteFloatfloat (0.0)
  • RerouteIntegerinteger (0)
  • RerouteStringstring ("")
  • RerouteModelmodel (None)
  • RerouteClipclip (None)
  • RerouteClipVisionclip_vision (None)
  • RerouteVaevae (None)
  • RerouteImageimage (None)
  • RerouteMaskmask (None)
  • RerouteLatentlatent (None)
  • RerouteConditioningconditioning (None)
  • RerouteDictdict ({})
  • RerouteListlist ([])
  • RerouteLoraStacklora_stack ([])
  • RerouteControlNetStackcontrol_net_stack ([])

The primitive variants (boolean, float, integer, string) only accept a link — they never show an editable widget.

Inputs

| Input | Type | Description | |---|---|---| | (slot name from the list above) | matches the variant (optional) | The value to pass through. |

Outputs

| Output | Type | Description | |---|---|---| | (slot name from the list above) | matches the variant | The input value, unchanged; the variant's default when the input is disconnected. |

Frontend

Repositionable slots. By default the input sits on the left edge and the output on the right edge, but each of them can be moved to any of the four sides of the node — with the constraint that the input and the output never share a side. Wires bend accordingly, leaving or entering the node in the direction of the side their slot sits on, and the slots keep their side when the node is resized. The chosen layout is saved with the workflow (in the node properties input_side / output_side); picking Left to Right returns the node to the standard layout. A collapsed node uses the usual collapsed connection points until it is expanded again.

Free resizing. Reroute nodes can be resized down to 80 px wide (standard nodes stop at about 140 px), so they can be kept compact. Two side effects of shrinking below the text width: the title and slot labels may visually overflow the node (renaming the node to something short avoids it), and the native Resize right-click action snaps the node straight to the minimal width.

Right-click menu option on the node:

  • Slot sides — submenu listing every valid input→output side combination (Left to Right, Left to Top, …, 12 in total; same-side combinations are not offered). The current layout is marked with a ✓; clicking an entry applies both sides at once.

GlobalSet

GlobalSet node

One half of a wireless connection pair (found under reroute, together with GlobalGet): it stores any number of connections under unique names, and GlobalGet nodes read them back anywhere on the canvas — subgraphs included — without a cable. The set of inputs is defined per node through an editor dialog (same style as the PipeCustom editor), each entry with its own name and data type.

Both nodes are virtual: they exist only in the editor and are removed from the prompt when the workflow is queued — every GlobalGet output resolves directly to the node feeding the same-named GlobalSet input, so the pair never executes, adds no cost and cannot change the result. Muting or bypassing them has no effect on a run for the same reason.

Names are global: a name may be defined by only one GlobalSet in the whole workflow (nested subgraphs included) — the editor rejects a name already defined by another GlobalSet, and a pasted or cloned GlobalSet automatically renames its conflicting entries (foofoo_2, foo_3, …).

Inputs

| Input | Type | Description | |---|---|---| | custom inputs | user-defined | One slot per configured entry, with the chosen name and type. Whatever is wired in is readable under that name by every GlobalGet. A slot left unconnected stores nothing — GlobalGet outputs with that name resolve to no value (the downstream input behaves as unconnected). |

Outputs

The node has no outputs — values are read back with GlobalGet nodes.

Up to 30 entries per node.

Frontend

The node body shows an Edit inputs… button opening the editor dialog:

  • + Add input appends a new entry; each row has a name field and a type dropdown (same type list as the PipeCustom editor: IMAGE, MASK, LATENT, MODEL, CLIP, VAE, CONDITIONING, INT, FLOAT, STRING, BOOLEAN, LORA_STACK, CONTROL_NET_STACK, DICT, LIST, *).
  • Rows can be drag-reordered with the handle and removed with .
  • Renaming a row keeps its slot and wire; changing a row's type keeps the slot but drops the wire. Names are validated on OK: non-empty, no duplicates on the node, not defined by another GlobalSet.
  • Changes are propagated to the GlobalGet nodes on OK: a renamed entry renames the matching Get outputs everywhere (their slots and wires are kept), a type change retypes them (their wires are dropped, as they are no longer valid), and a removed entry leaves the Get outputs in place but a warning toast lists the orphaned names.
  • Enter (while editing a name) confirms, Escape cancels.

Right-click menu options on the node:

  • Edit global inputs… — same as the button.
  • Select its Get nodes (n) — selects every GlobalGet in the same graph that reads at least one of this node's names.

GlobalGet

GlobalGet node

The other half of the pair: exposes values stored by GlobalSet nodes as outputs, with no cable. The set of outputs is defined through the same editor dialog — but only names defined by a GlobalSet can be used, and each output automatically takes the defining entry's data type. Any number of GlobalGet nodes may read the same name, so one value can fan out across the whole workflow, including into or out of subgraphs.

Like GlobalSet, the node is virtual (see above): at queue time each output resolves straight to the real node feeding the same-named GlobalSet input. An output whose name is no longer defined, or whose GlobalSet slot is unconnected, resolves to no value — a downstream node with that required input then fails prompt validation, exactly as if the input were unconnected.

Inputs

The node has no inputs — values are stored with GlobalSet nodes.

Outputs

| Output | Type | Description | |---|---|---| | custom outputs | taken from the Set | One slot per configured entry; carries the value wired into the same-named GlobalSet input, with that entry's type. |

Up to 30 entries per node.

Frontend

The node body shows an Edit outputs… button opening the editor dialog:

  • Rows work as on GlobalSet (add, drag-reorder, remove, rename keeps the wires), with two differences: the name field autocompletes with the names defined by the GlobalSet nodes, and the type dropdown is locked to the defining entry's type as soon as the name is recognised.
  • Add all Set names appends one entry for every defined name not already on the node.
  • Names are validated on OK: non-empty, no duplicates on the node, and every name must be defined by a GlobalSet.
  • Renames made on the GlobalSet side follow automatically (see GlobalSet above); an entry whose name was removed on the Set side stays on the node (with its warning toast) until it is fixed or removed here.

Right-click menu options on the node:

  • Edit global outputs… — same as the button.
  • Jump to Global Set — submenu with one entry per output name; centers the view on the GlobalSet defining that name, switching into its graph when it lives in a different subgraph.

DoublePrompt

DoublePrompt node

A positive/negative prompt pair in a single node: two multiline text fields whose contents are output unchanged. Its distinguishing feature is that the boundary between the two fields can be dragged, so the node's height can be shared freely between the positive and the negative prompt (a long positive prompt next to a one-line negative, for instance).

Inputs

| Input | Type | Description | |---|---|---| | prompt_positive | STRING (multiline) | The positive prompt. Dynamic prompts syntax is enabled. | | prompt_negative | STRING (multiline) | The negative prompt. Dynamic prompts syntax is enabled. |

Outputs

| Output | Type | Description | |---|---|---| | prompt_positive | STRING | The positive prompt, exactly as typed. | | prompt_negative | STRING | The negative prompt, exactly as typed. |

Frontend

  • A divider sits in the gap between the two fields, marked with three grip dots (the mouse cursor turns into a vertical resize arrow over it). Dragging it moves the boundary: enlarging the positive field shrinks the negative one by the same amount, and vice versa. Each field always keeps a minimum height of 50 px.
  • Double-click on the divider restores the even 50/50 split.
  • The split is stored as a proportion of the node body (node property split_ratio, saved with the workflow), so resizing the node itself keeps the chosen balance between the two fields.

ResizeImageMask

ResizeImageMask node

Resizes an image and/or a mask with a selectable strategy: pass-through, crop to a target size, pad to a target size, several aspect-preserving scales, or matching the size of another input. The mode widget is a dynamic combo: selecting a mode shows only the widgets (and, for the match modes, the extra input slot) that the mode actually uses.

Both image and mask are optional and are processed together with the same target size, so they stay aligned. If both are connected they must have the exact same size (batch size, width and height) — otherwise the node raises an error. Whichever of the two is missing is simply skipped and its output is empty.

Every mode except do nothing rounds the final width and height with divisible_by (e.g. with divisible_by = 16, both sides of the result are multiples of 16). Where a mode computes a side from the aspect ratio, the rounding may make the final aspect ratio deviate slightly from the source.

upscale_method defaults to auto, which picks the interpolation per input instead of applying the same one to everything: a mask is always rescaled with bilinear, an image with lanczos when it grows and area when it shrinks (the sharpest filter when enlarging, a proper box average — no aliasing or moiré — when reducing). The image and the mask are resolved independently, so with both connected the same run can use lanczos on the image and bilinear on the mask. The direction is decided on the total pixel count, so a resize that grows one side and shrinks the other follows the dominant one; in the crop modes it is measured after the crop, and in the pad modes on the scaled content rather than on the padded canvas. Since bilinear produces soft edges, pick nearest-exact explicitly when a mask must stay strictly binary. Any method other than auto is applied as chosen to both inputs.

Inputs

| Input | Type | Description | |---|---|---| | image | IMAGE (optional) | The image to resize. | | mask | MASK (optional) | The mask to resize. | | mode | dynamic COMBO | The resize strategy; see the mode list below. Default do nothing. | | upscale_method | COMBO | Interpolation used for every rescale: auto (default), nearest-exact, bilinear, area, bicubic, lanczos. auto picks the filter per input, see above. | | divisible_by | INT | The final width and height are rounded to the nearest multiple of this value (1–1024, default 1 = no rounding). Ignored by do nothing. |

Modes

  • do nothing — passes image and mask through unchanged (divisible_by included: no rounding is applied).
  • round — the size is rounded with divisible_by.
  • crop to size — widgets width, height (1–16384, default 512) and crop (center, top, bottom, left, right). The input is rescaled to fill the target size completely and the excess is cropped away; crop picks which part is kept — e.g. top keeps the top of a too-tall result. When the excess falls on the axis the position does not address (e.g. top with excess on the sides), the crop is centered.
  • pad to size — widgets width, height, pad (center, top, bottom, left, right) and pad_color (color picker, default black). The input is rescaled to fit entirely inside the target size and the leftover area is filled with pad_color; pad picks where the content sits — e.g. top anchors it to the top edge, putting all the padding at the bottom. Padding on the axis the position does not address is split evenly. The mask is always padded with 0 (pad_color only affects the image).
  • scale by multiplier — widget multiplier (FLOAT 0.01–16.0, default 1.0). Both sides are multiplied by the value, then rounded with divisible_by.
  • scale longer dimension / scale shorter dimension — widget longer_side / shorter_side (1–16384, default 1024). The longer (resp. shorter) side of the input is scaled to the given value and the other side follows the aspect ratio. The chosen side is rounded with divisible_by before computing the scale, so it always lands exactly on the requested multiple; the other side is rounded after.
  • scale width / scale height — widget width / height (1–16384, default 1024). Same as above, but the anchored side is explicitly the width (resp. the height).
  • scale total pixels — widget megapixels (FLOAT 0.01–256.0, default 1.0; 1 megapixel = 1024×1024 pixels). Both sides are scaled by the same factor so that the total pixel count matches the requested megapixels, then rounded with divisible_by — because the rounding happens after the match, the final pixel count is close to, not exactly on, the target.
  • crop to match input / pad to match input — same behaviour as crop to size / pad to size (including the crop / pad + pad_color widgets), but the target width and height are taken from an extra match input slot, which accepts an IMAGE or a MASK and is required while one of these modes is selected. The match size is still rounded with divisible_by, so leave it at 1 when the output must match the reference exactly.

Outputs

| Output | Type | Description | |---|---|---| | image | IMAGE | The resized (or passed-through) image; empty when image is not connected. | | mask | MASK | The resized (or passed-through) mask; empty when mask is not connected. | | width | INT | The final width, measured on the output image (or the output mask when no image is connected; 0 when neither is). | | height | INT | The final height, measured the same way. |


ImageResolution

ImageResolution node

Outputs a width / height pair, computed with a selectable strategy: entered directly, picked from the size presets, derived from an aspect ratio and/or a pixel budget, or copied from an existing image or mask. The mode widget is a dynamic combo: selecting a mode shows only the widgets (and, for match image, the extra input slot) that the mode actually uses.

Whatever the mode, the resulting width and height are rounded with divisible_by (nearest multiple, never below the divisor). Where a mode computes a side from an aspect ratio or a pixel count, this rounding may make the result deviate slightly from the exact target.

The preset and aspect ratio lists are read from image_presets.yaml in the addon's data folder.

Inputs

| Input | Type | Description | |---|---|---| | mode | dynamic COMBO | How the resolution is produced; see the mode list below. Default custom. | | divisible_by | INT | The width and height are rounded to the nearest multiple of this value (1–1024, default 8; 1 = no rounding). |

Modes

  • custom — widgets width and height (1–16384, default 512). The values are used as-is (then rounded with divisible_by).
  • preset — widget image_size, a combo of the preset sizes (e.g. 832x1216); the width and height are extracted from the selected entry.
  • resolution — widgets aspect_ratio (a combo of the aspect ratio presets, e.g. 3:2 (Photo)) and megapixel (FLOAT 0.01–256.0, default 1.0; 1 megapixel = 1024×1024 pixels). The size is computed so that the total pixel count matches the requested megapixels while keeping the chosen aspect ratio.
  • resolution and width — widgets width (1–16384, default 1024) and megapixel. The width is fixed and the height is computed to reach the requested pixel count.
  • resolution and height — widgets height (1–16384, default 1024) and megapixel. The mirror of the above: the height is fixed and the width is computed.
  • match image — an extra match input slot, which accepts an IMAGE or a MASK and is required while this mode is selected. The width and height are read from that input. The size is still rounded with divisible_by, so set it to 1 when the output must match the reference exactly.

Outputs

| Output | Type | Description | |---|---|---| | width | INT | The computed width, rounded with divisible_by. | | height | INT | The computed height, rounded with divisible_by. |


MaskOverlay

MaskOverlay node

Previews a mask overlaid on an image as a colored, semi-transparent layer — useful for checking segmentation or inpainting masks against the picture they belong to. The composited preview is shown on the node (it is an output node, so it runs as soon as it is reached) and is also emitted on the image output for further processing.

Both inputs are optional, and the preview adapts to what is connected:

  • image + mask — the mask area is tinted with mask_color at mask_opacity strength (the blend weight is mask × mask_opacity, so soft mask edges fade smoothly). A mask whose size differs from the image is rescaled (bilinear) to fit for the blend only — the mask output keeps the original resolution;
  • image only — the image is shown unchanged;
  • mask only — the mask is shown as a grayscale image;
  • nothing connected — a 64×64 black placeholder.

An RGBA input image is converted to RGB (the alpha channel is dropped).

Inputs

| Input | Type | Description | |---|---|---| | mask_opacity | FLOAT | Opacity of the color overlay (0.0–1.0, step 0.01, default 0.5). 0 shows the image untouched, 1 paints the masked area with the solid color. | | mask_color | COLOR | Color of the overlay (color picker, default #0000FF). | | image | IMAGE (optional) | The image to overlay onto (RGBA is converted to RGB). | | mask | MASK (optional) | The mask to visualise. |

Outputs

| Output | Type | Description | |---|---|---| | image | IMAGE | The composited preview (or the fallback described above). | | mask | MASK | The input mask, unchanged; a 64×64 zero mask when no mask is connected. |


TextConcat

TextConcat node

Joins several prompts into a single one. The prompt inputs are a growing set of slots named prompt_0, prompt_1, … : two are shown to start with, and a new empty slot appears each time the last one is connected, up to 10. They are connection-only slots (no text field on the node) and all of them are optional, so any slot can be left unconnected.

Each prompt is stripped of its leading and trailing whitespace before being appended, and prompts that are empty — unconnected, or containing only whitespace — are skipped entirely, so they never leave a stray separator behind. The two separator switches control what is inserted between the prompts already collected and the next one:

  • comma_separator adds a ,, unless the text collected so far already ends with a comma (a prompt that itself ends with , is therefore not doubled);
  • newline_separator adds a line break, unless the text already ends with one.

Both switches are independent: with both enabled the prompts are joined by , followed by a newline; with both disabled the prompts are appended directly, with no separator at all. Nothing is added in front of the first prompt, and no separator is left at the end.

Inputs

| Input | Type | Description | |---|---|---| | prompt_0prompt_9 | STRING (optional, connection only) | The prompts to join, in slot order. Slots grow on demand from 2 up to 10; an unconnected slot counts as an empty string and is skipped. | | comma_separator | BOOLEAN | Insert a , between two prompts (default false). | | newline_separator | BOOLEAN | Insert a newline between two prompts (default true). |

Outputs

| Output | Type | Description | |---|---|---| | prompt | STRING | The concatenated prompts. An empty string when no prompt is connected or all of them are empty. |


DownloadModelsList

DownloadModelsList node

Downloads a list of model files from the internet into the ComfyUI models folder. The list is typed (or assembled with the Append models picker, see Frontend) in the models_list textbox, one block per model: the model subpath, an optional hash, and one or more download urls — the format described in the Example below.

Each model is processed in turn:

  • if the file already exists it is skipped — the lookup goes through ComfyUI's own model paths, so a model found in any folder configured for its type counts as present, not only in models_dir. When the block declares a hash, the existing file is verified against it and reported as a hash mismatch when it differs;
  • otherwise the urls are tried in the order they are listed, until one download succeeds; the downloaded file is then checked against the declared hash, if any.

Models are downloaded one at a time. The node is an output node, so it runs when the workflow is queued even if its result output is left unconnected; like every other node it is also cached, so queueing again without changing any input does nothing until the list (or one of the other inputs) changes.

Inputs

| Input | Type | Description | |---|---|---| | models_list | STRING (multiline) | The models to download, as blocks separated by empty lines (see Example). Lines starting with # are ignored. | | models_dir | STRING | Base folder the subpaths are resolved against. When left empty (the default), the ComfyUI models folder is used. | | civitai_api_key | STRING | Civitai API token, used for downloads that require an account. Optional, and never written to the log (the log only shows its length as asterisks). If left empty, the node will attempt to read the value from the 'tokens' section of the config.yaml file |

Outputs

| Output | Type | Description | |---|---|---| | result | STRING | The execution report: totals (processed / downloaded / skipped / failed), the errors, the list of newly downloaded files, and a final status line. |

Example

checkpoints/FLUX/flux1-dev-fp8.safetensors
hash:8E91B68084B53A7FC44ED2A3756D821E355AC1A7B6FE29BE760C1DB532F3D88A
https://civitai.com/api/download/models/1434485

loras/PONY/chars/Zhurong_Dynasty_warriors.safetensors
https://civitai.com/api/download/models/1102934

| Line | Meaning | |---|---| | first line of a block | The model subpath, relative to models_dir (e.g. checkpoints/FLUX/flux1-dev-fp8.safetensors). Both / and \ work as separators. | | hash:… / sha256:… | Optional SHA-256 of the file, used to verify it before and after downloading. | | any other line | A download url. Several urls can be listed for the same model and are tried in order. |

Blocks are separated by empty lines; the hash line may be omitted, but at least one url is needed for a model that is not on disk yet.

Frontend

Right-click menu options on the node:

  • Append models — opens a picker listing the models of a catalogue file, and appends the chosen ones to the models_list textbox. The catalogue is input/ntx_data/downloads/_full_list.txt and uses exactly the format above, so a block picked there is copied to the node as-is. The same dialog can also append a whole preset list at once (see Download list combobox below).
  • Save current list as preset — writes the current content of models_list to input/ntx_data/downloads/ as a preset, under a name asked for in a dialog (see Saving a preset below).

The picker organises the catalogue by model subpath, split on / and \, into a tree of folders (checkpoints, loras/PONY/chars, …) with the model files as leaves; folders are listed before files, both alphabetically. Its controls:

  • a checkbox on every row selects models: ticking a folder selects (or clears) everything under it, and a folder whose contents are only partly selected shows a mixed-state box. Clicking a file row toggles it as well, while clicking a folder row expands or collapses it;
  • a filter box narrows the tree to the models whose subpath matches the typed text — matching entries stay visible with their folders expanded;
  • hovering a model shows its full block (subpath, hash, urls) as a tooltip;
  • a Download list combobox under the tree picks a whole preset list (see below);
  • a line under the tree counts the current selection and names the picked list, if any, and Clear selection unticks everything and sets the combobox back to — none —;
  • Append (disabled while nothing is selected and no list is picked) confirms, and so does Enter; Cancel, Escape, or a click outside the dialog closes it without touching the node.

The catalogue file is re-read from disk every time the dialog is opened, so models added to it show up without reloading the page or restarting ComfyUI; a warning toast is shown when neither the catalogue nor any preset list holds anything to append.

On confirmation the selected blocks are appended to the end of models_list — in the order they appear in the catalogue, not the order they were ticked — separated by empty lines and keeping whatever the textbox already contained. Models that the list already declares are skipped rather than added twice (subpaths are compared ignoring case and separator style), and a toast reports how many models were appended and how many were left out as already listed.

Download list combobox

Beside the catalogue, the picker offers the ready-made lists stored in input/ntx_data/downloads/ as .dwlst files. Each file is a plain list in the format described above, and each one becomes an entry of the Download list combobox, named after the file without its extension and followed by the number of models it holds — anima.dwlst is shown as anima (5). The combobox starts on — none —, and reads — no list found — and stays disabled when the folder holds no .dwlst file.

Picking a list appends its whole content, in addition to whatever is ticked in the tree: tree selection and list are not exclusive, and either one alone is enough to enable Append. The tree blocks are written first, then the models of the list in their file order. Both go through the same duplicate check, so a model that the textbox already declares — or that the tree selection and the picked list both contain — is written only once, and the toast counts models actually added, not files.

The downloads folder is scanned every time the dialog is opened, so a list saved with Save current list as preset is offered on the next opening, without reloading the page.

Saving a preset

Save current list as preset turns the current models_list into a new .dwlst file in input/ntx_data/downloads/, ready to be picked again from the combobox. A warning toast is shown instead when the textbox is empty.

The dialog asks for the preset name — its title recalls how many models are about to be saved — and Save (or Enter) writes the file; Cancel, Escape, or a click outside closes it without writing anything. The .dwlst extension is added automatically, and typing it is harmless. Names that no filesystem accepts are refused: an empty name, one containing \ / : * ? " < > |, or one starting or ending with a dot or a space.

When a preset of that name already exists, nothing is written yet: the dialog warns that the file exists and the button becomes Overwrite, which replaces the file when pressed again. Editing the name turns the button back into Save, so a confirmation is never carried over to another file. Names are compared ignoring case, so saving Anima over an existing anima.dwlst replaces that file — keeping its original spelling — instead of adding a near-duplicate.


ImagesGrid

ImagesGrid node

Combines a batch of images into a single grid image. The images are placed in batch order, left to right and top to bottom, each in its own cell; since all the images of a batch share the same size, every cell is exactly the size of one input image and the grid is a plain tiling of them. The mode widget is a dynamic combo: selecting a mode shows only the widgets that the mode actually uses.

padding is inserted between the cells only — there is no border around the grid, so a grid of r × c images is c × width + (c - 1) × padding by r × height + (r - 1) × padding pixels. The whole canvas is filled with color beforehand, which is therefore what shows through in the padding and in any cell left empty (an RGBA input keeps its alpha channel, and the filled area is opaque).

When the grid cannot hold every input image — only possible when both dimensions are fixed, i.e. in rows and columns or with a fully numeric custom layout — the images in excess are discarded, and the number dropped is written to the log. When the input batch is empty the node returns a single 1×1 pixel of color.

Inputs

| Input | Type | Description | |---|---|---| | images | IMAGE | The batch of images to combine. | | mode | dynamic COMBO | How the grid size is decided; see the mode list below. Default fixed columns. | | padding | INT | Gap between the cells, in pixels (0–1024, default 0 = the images touch). | | color | COLOR | Color picker (default black #000000) filling the padding and the empty cells. |

Modes

  • fixed columns — widget columns (1–64, default 2). The number of rows is derived from the number of images, so every image always fits.
  • fixed rows — widget rows (1–64, default 2). The mirror of the above: the number of columns is derived from the number of images.
  • rows and columns — widgets rows and columns (1–64, default 2). The grid size is exact: fewer images than cells leaves the remaining cells filled with color, more images than cells discards the excess.
  • custom — widget options, a multiline text listing several layouts, one per line, so that the grid shape adapts to the number of images. See below.

The custom layout list

Each line of options is a layout written as <max_images> <rows> <columns>, meaning "up to max_images images, use rows × columns". Either rows or columns — never both — can be *, meaning "as many as needed" for the number of images actually received. Values are separated by spaces and empty lines are ignored.

At run time the node picks the first layout of the list whose max_images is greater than or equal to the number of input images, or the last layout of the list when the images exceed them all. The order of the lines is the order they are written in — they are not sorted — so list them from the smallest max_images to the largest.

A line is discarded, with the reason written to the log, when it is not a triad of values, when a value is not a number, when both rows and columns are *, or when any value is zero or negative. The other lines still work. If no valid layout is left at all, the node falls back to a near-square grid (⌈√n⌉ columns) and logs a warning.

Example

The default value of options is:

1 1 1
2 1 2
3 1 3
4 2 2
9 * 3
10 * 4

which gives:

| Images received | Layout used | Resulting grid | |---|---|---| | 1 | 1 1 1 | 1 row × 1 column | | 2 | 2 1 2 | 1 row × 2 columns | | 3 | 3 1 3 | 1 row × 3 columns | | 4 | 4 2 2 | 2 rows × 2 columns | | 5 | 9 * 3 | 2 rows × 3 columns, the last cell filled with color | | 9 | 9 * 3 | 3 rows × 3 columns | | 10 | 10 * 4 | 3 rows × 4 columns, the last 2 cells filled with color | | 11 | 10 * 4 (last one, none fits) | 3 rows × 4 columns, the last cell filled with color |

A line such as 20 3 * reads "up to 20 images, 3 rows and as many columns as needed": with 7 images it produces a 3 × 3 grid, the two spare cells filled with color.

Outputs

| Output | Type | Description | |---|---|---| | grid | IMAGE | The composed grid, as a single image (batch of 1); a 1×1 pixel of color when the input batch is empty. |


Primitive

Primitive node

A single constant value whose type is chosen per node: INT, FLOAT, BOOLEAN or STRING. Instead of picking a different node for each type, one node is dropped into the graph and its type — plus, for the numeric types, the minimum, maximum and step of its widget — is set from the Edit primitive right-click dialog. The node then shows exactly one value widget of the chosen type and an output labelled with that type, so it behaves like the matching core primitive node.

Every type keeps its own stored value, so switching back and forth never destroys the others; the value shown is carried over and converted when the type changes (a number becomes true unless it is zero, false and an empty string become 0, and so on), clamped to the current minimum/maximum. Changing the type drops the output wires the new type is not valid for and keeps the compatible ones.

The minimum, maximum and step are a display setting only: they constrain the widget in the editor, they are not enforced when a value arrives through the input socket. They are shared between INT and FLOAT (switching between the two carries the range over, rounded to whole numbers for INT) and are ignored by BOOLEAN and STRING.

Inputs

| Input | Type | Description | |---|---|---| | value | INT / FLOAT / BOOLEAN / STRING | The value returned by the node. The widget's type — and, for the numeric types, its range and step — follow the setting chosen in the Edit primitive dialog. Like every widget it can also be driven by a wire. Each type stores its value in its own input (int_value, float_value, boolean_value, string_value); only the selected one is shown, under the name value. | | primitive_type | COMBO (hidden) | The selected type, managed by the Edit primitive dialog. Never shown on the node. Default: FLOAT. |

Outputs

| Output | Type | Description | |---|---|---| | value | INT / FLOAT / BOOLEAN / STRING | The value, typed as the selected primitive type — the output slot is relabelled accordingly (INT, FLOAT, …). |

Frontend

  • The node carries one widget per supported type but shows only the one matching the selected type, labelled value; the others — and their input sockets — are hidden and cannot be connected.

Right-click menu option on the node:

  • Edit primitive — opens a dialog with:
    • TypeINT, FLOAT, BOOLEAN or STRING.
    • Minimum / Maximum — the widget's bounds, shown for INT and FLOAT only. Leave a field empty for no limit.
    • Step — the increment applied by the widget's arrows and by dragging it. Defaults to 1 for INT and 0.1 for FLOAT; for FLOAT it also sets how many decimals are shown.
    • Apply confirms (also Enter), Cancel or Escape closes without changing anything. A minimum greater than the maximum, or a step of zero or less, is refused with a message.