Skip to content

KoinnAI/ComfyUI-DynPromptSimplified

Repository files navigation

ComfyUI Dynamic Prompting Simplified

A minimal dynamic prompting + mirrored wildcards node for ComfyUI.

Overview

  • {a|b|{c|d}} nested choice expansion (deterministic by seed).
  • __name__ loads wildcards/name.txt.
  • __name-mir__ strictly loads wildcards/name-mir.txt.
  • In negative phase, if __name__ is used and name-mir.txt exists, it auto-mirrors to all options except the chosen one.
  • Deterministic selection per seed.

Install

  1. Copy this folder to: <ComfyUI>/custom_nodes/ComfyUI-DynPromptSimplified
  2. (Optional) Put your wildcard files in ComfyUI-DynPromptSimplified/wildcards/ or set wildcard_dir to any folder.
  3. Restart ComfyUI.

Node

DynPrompt Expand (mirrored wildcards)

  • Inputs: text, negative, seed, wildcard_dir, auto_neg_from_mir
  • Outputs: expanded text, expanded negative

Node Inputs

  • positive: positive prompt
  • negative: negative prompt
  • Seed: this is used to determine how the node will select tags
  • allow neg from mir: automatically adds -mir tags to negative prompt without explicitly needing to add it to the negative prompt
  • variety: int between 0-10; creates extra randomness 'lanes' Variety

Wire the outputs into your usual `CLIP Text Encode` node
You can preview what the output is with the "preview any" node in utils

Preview 1


## ✨ Features
  • Nested choices with {option1|option2|{nested1|nested2}}

  • Wildcard expansion with __name__ → expands from wildcards/name.txt

  • Line-separated wildcard files that may themselves contain braces and wildcards

  • Mirrored wildcards with __name-mir__:

    • Positive prompt gets the chosen option
    • Negative prompt gets all the other options, comma-separated
    • Strict resolution: __name-mir__ reads only wildcards/name-mir.txt (no fallback to name.txt)
  • Deterministic behavior using the current generation seed

  • Works for both positive and negative prompts

  • Expanded prompts are saved into PNG metadata

    📑 Wildcard Files

  • Located in the wildcards/ directory (configurable).

  • Each line is one possible expansion.

  • Lines may contain further braces {} and wildcard calls.

Example: wildcards/hats.txt

{red hat|blue hat|{green hat|yellow hat|{black hat|gold hat}}}
beret
top hat

🔄 Mirrored Wildcards

  • File name: name-mir.txt (called via __name-mir__).
  • Ensures complementary picks between positive/negative prompts.

Example: wildcards/hats-mir.txt

{red hat|blue hat|green hat}
{tall hat|short hat|medium hat}
  • Positive prompt: portrait, __hats-mir__
  • Negative prompt: lowres, __hats-mir__
    • If pos → red hat, neg → blue hat, green hat
    • If pos → short hat, neg → tall hat, medium hat

This ensures that the negative prompt excludes the token chosen in the positive prompt.

🧩 Nested -mir behavior (auto-discovery)

Mirrored tokens can be nested inside other wildcards. If a wildcard you use in the positive prompt expands to another token like __foo-mir__, the extension can automatically inject the mirrored complement into the negative prompt (so you don’t have to add __foo-mir__ manually).

  • This requires the checkbox in the UI:
    “Automatically mirror -mir wildcards without explicitly adding them to the negative prompt.” (enabled by default)
  • The auto-inject only happens if the negative prompt does not already contain that __*-mir__ token.
  • Resolution is strict: __foo-mir__ reads wildcards/foo-mir.txt only.

Example (nested):

wildcards/outfits.txt
---------------------
__hats-mir__, {casual|formal}

wildcards/hats-mir.txt
----------------------
{red hat|blue hat|green hat}

Usage:

  • Positive: portrait, __outfits__
  • Negative: (leave blank or put your usual negatives)

- add node from: add node -> prompt -> DSynPrompt Expand (deep-mirrored)

Preview 1


Behavior:

  • The positive prompt expands __outfits__ → which contains __hats-mir__.
  • With the checkbox enabled, the extension auto-injects __hats-mir__ into the negative and expands it there as the comma‑separated “other” options.
  • If the seed picks red hat for positive, the negative gets blue hat, green hat automatically.

🎲 Variety (extra randomness lanes)

What it is: An integer knob that creates alternate, reproducible randomness lanes without changing your seed or prompt.

Deterministic: With the same (seed, variety, prompt, files), you get the same positive & negative outputs every time.

Independence from fixed text: Only decision points advance randomness—each {…} block and each __wildcard__ / __*-mir__. Fixed words/commas/spaces do not affect picks.

How it works (under the hood):

  • Choices use choice#0, choice#1, …
  • Wildcards use wild#0, wild#1, … (also salted with the token name)
  • We hash (seed, counter[, token], variety) to pick an index.

When to use: Keep seed fixed and sweep variety = 0,1,2… to explore different, reproducible variants from the same prompt.

Example

Prompt: __color__, {A|B|{C|D}}, __pose-mir__

(seed=123, variety=0) → picks one set of branches (seed=123, variety=1) → different set of branches (still reproducible)

⚙️ Usage

  • Write prompts as usual with {} and __wildcards__.
  • Wildcards are recursively expanded.
  • Mirrored wildcards respect complement logic.

Prompt Example:

Positive: portrait, {cinematic|studio|outdoor}, __hats__, soft lighting
Negative: lowres, bad anatomy, __hats-mir__

Logic

Determinism & Counters

  • Randomness depends only on decision points:
    • Each non-nested {…} is the next choice#N.
    • Each __token__ (including __*-mir__) is the next wild#M.
  • For each pick we hash (seed, counter[, token], variety) → index.
  • Fixed text (words/commas/spaces) does not affect counters or picks.
  • variety is an extra integer salt to create reproducible “lanes”.

Expansion Order (Positive)

  1. Braces-first pass (repeat until none left)
    Collapse the left-most non-nested {…} using choice#N.
  2. Wildcard pass (left → right)
    Replace each __token__ using wild#M:
    • __name__ → read wildcards/name.txt (supports subfolders like a/b.txt).
    • __name-mir__strict read wildcards/name-mir.txt only (no fallback).
    • If a chosen line itself contains braces/wildcards, it’s injected and will be handled by subsequent passes.
    • Missing file ⇒ token disappears (empty).
  3. Braces-second pass
    Collapse any {…} that arrived from wildcard lines.
  4. Cleanup
    Normalize commas/spaces.

Deep Mirroring (Negative complement)

  • Your user Negative is expanded normally (same algorithm as Positive: braces → wildcards → braces).
  • If auto_neg_from_mir is ON, we run a mirror pass over the Positive prompt:
    • Re-run the same brace + wildcard passes with the same (seed, variety) and counters.
    • For every __*-mir__ encountered (even if nested inside other wildcards):
      • Compute the chosen option.
      • Add all non-empty alternatives except the chosen one to a bag.
      • Special case: if the chosen alternative is empty (from {option|}), add all non-empty options.
  • Merge: final_negative = dedup(user_negative_expanded + mirrored_bag).

File Semantics

  • Single-line files:
    • Non-mir: if it’s {a|b|{c|d}|}, split by top-level | (empties kept as "").
    • *-mir: if it’s {a|b|{c|d}}, flatten to leaf options ["a","b","c","d"] so “all-except-chosen” is well-defined.
  • Multi-line files: each non-empty, non-comment line is an option (lines may contain braces/wildcards).
  • Path safety: allows subfolders (a/b), dotted names (style.v1); blocks traversal (..).

Pseudocode (conceptual)

    counters = {choice: 0, wild: 0}

    def pick_choice(options):
        i = hash(seed, f"choice#{counters.choice}", variety) % len(options)
        counters.choice += 1
        return options[i]

    def pick_wild(token, options):
        i = hash(seed, f"wild#{counters.wild}:{token}", variety) % len(options)
        counters.wild += 1
        return options[i]

    # Positive:
    text = collapse_braces(text, pick_choice)        # pass 1
    text = expand_wildcards(text, pick_wild)         # pass 2
    text = collapse_braces(text, pick_choice)        # pass 3

    # Negative:
    neg  = collapse_braces(neg, pick_choice)
    neg  = expand_wildcards(neg, pick_wild)
    neg  = collapse_braces(neg, pick_choice)

    # Deep mirror additions:
    mirror_bag = run_positive_again_collecting_all_except_chosen_for('*-mir')

    final_negative = dedup_join(neg, mirror_bag)

Key Guarantees

  • Same (seed, variety, prompt, files) ⇒ same Positive & Negative.
  • Adding/removing fixed text does not change picks.
  • Changing decision points (adding/removing a {…} or __wildcard__) changes the sequence of draws (by design).

Notes

  • Missing wildcard files resolve to empty strings.
  • Choice/wildcard expansion is capped to prevent runaway recursion.
  • If negative is blank and auto_neg_from_mir is ON, the node scans the positive for __tokens__ and auto-builds a mirrored negative when possible.

Changelog

2025-08-24 — Counter-Based Randomness & “Variety” lanes

Why: fixed tokens could cause prompt to converge.

What changed

  • Decision-only RNG: Random choices now depend only on decision points (each {…} block and each __wildcard__ / __*-mir__ call), not on surrounding fixed text.
    • Internally we hash (seed, decision_counter[, token], variety) where counters advance as we hit choices/wildcards left→right.
    • Adding/removing fixed words, commas, or spacing no longer shifts picks.
  • Deterministic lanes via variety: New integer input that adds an extra salt.
    • Same (seed, variety, prompt, files) ⇒ same outputs.
    • Sweep variety = 0,1,2… to get reproducible alternates without touching seed or prompt.
  • Deep mirroring (strict): __foo-mir__ contributes mirrored exclusions to Negative even when nested inside other wildcards, and only reads foo-mir.txt (never falls back).
  • Empty-branch support in *-mir.txt: {option|} is honored.
    • If Positive picks option, Negative adds nothing for that token; if Positive picks the empty branch, Negative adds the non-empty options.
  • Single-line *-mir.txt with nested braces: {1|2|{3|4}} is flattened to leaves so mirroring can compute “all-except-chosen” correctly.

Behavior notes

  • Fixed text no longer influences randomness. Only the number/order of decision points matters.
  • Changing decision points (e.g., adding a {…} or another __wildcard__) will change the sequence of draws—as expected.
  • Deep mirror uses the same counters as Positive, ensuring the Negative always gets the correct complement.

Example

Prompt: __color__, {A|B|{C|D}}, __pose-mir__
Same seed, same variety → same picks (pos & neg)
Change only fixed words → picks stay identical
Bump variety (e.g., 0→1) → different, reproducible picks

License: GPLv3 (same as the original)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages