Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
492ad21f02 | ||
|
|
485cb8bf43 | ||
|
|
a7d965f92a |
+20
@@ -1,3 +1,23 @@
|
||||
/__pycache__
|
||||
test_nodes.py
|
||||
/.venv
|
||||
node_modules/
|
||||
.env
|
||||
.env.*
|
||||
dist/
|
||||
build/
|
||||
out/
|
||||
release/
|
||||
bin/
|
||||
obj/
|
||||
__pycache__/
|
||||
.venv/
|
||||
venv/
|
||||
.cache/
|
||||
*.log
|
||||
logs/
|
||||
tmp/
|
||||
temp/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
desktop.ini
|
||||
|
||||
@@ -61,7 +61,9 @@ NoxPromptShotlistOrganizer = _nodes.NoxPromptShotlistOrganizer
|
||||
NoxPromptProductionPlanner = _nodes.NoxPromptProductionPlanner
|
||||
NoxPromptEnhancer = _nodes.NoxPromptEnhancer
|
||||
NoxPromptBuilder = _nodes.NoxPromptBuilder
|
||||
NoxPromptBlueprintGenerator = _nodes.NoxPromptBlueprintGenerator
|
||||
NoxPromptPaletteMixer = _nodes.NoxPromptPaletteMixer
|
||||
NoxPromptPromptInspector = _nodes.NoxPromptPromptInspector
|
||||
NoxPromptNarrativeWeaver = _nodes.NoxPromptNarrativeWeaver
|
||||
NoxPromptCharacterCreator = _nodes.NoxPromptCharacterCreator
|
||||
NoxPromptHumanDesigner = _nodes.NoxPromptHumanDesigner
|
||||
@@ -79,6 +81,7 @@ NoxPromptCamz = _nodes.NoxPromptCamz
|
||||
NoxPromptCombiner = _nodes.NoxPromptCombiner
|
||||
NoxPromptPipelineCombiner = _nodes.NoxPromptPipelineCombiner
|
||||
NoxPromptAnalyzer = _nodes.NoxPromptAnalyzer
|
||||
NoxPromptCameraSelector = _nodes.NoxPromptCameraSelector
|
||||
|
||||
NODE_CLASSES = tuple(_nodes.NODE_CLASSES)
|
||||
get_node_classes = _nodes.get_node_classes
|
||||
@@ -107,7 +110,9 @@ _BASE_EXPORTS = [
|
||||
"NoxPromptProductionPlanner",
|
||||
"NoxPromptEnhancer",
|
||||
"NoxPromptBuilder",
|
||||
"NoxPromptBlueprintGenerator",
|
||||
"NoxPromptPaletteMixer",
|
||||
"NoxPromptPromptInspector",
|
||||
"NoxPromptNarrativeWeaver",
|
||||
"NoxPromptCharacterCreator",
|
||||
"NoxPromptHumanDesigner",
|
||||
@@ -125,6 +130,7 @@ _BASE_EXPORTS = [
|
||||
"NoxPromptCombiner",
|
||||
"NoxPromptPipelineCombiner",
|
||||
"NoxPromptAnalyzer",
|
||||
"NoxPromptCameraSelector",
|
||||
"NODE_CLASSES",
|
||||
"get_node_classes",
|
||||
]
|
||||
|
||||
@@ -8,7 +8,7 @@ Modular prompt-engineering tools for ComfyUI that turn cinematic ideas into prod
|
||||
|
||||
## Highlights
|
||||
|
||||
- **20 purpose-built nodes** spanning builders, character design, wardrobe, lighting, camera direction, NSFW safeguards, streaming prompts, and pipeline organization.
|
||||
- **22 purpose-built nodes** spanning builders, character design, wardrobe, lighting, camera direction, NSFW safeguards, streaming prompts, analysis, and pipeline organization.
|
||||
- **Formula-driven Wan 2.2 builder** with curated motion, composition, and stylization vocabularies plus inline overrides and randomization; pair with Lighting Master for centralized lighting control.
|
||||
- **Preset-friendly companions** – every major node supports save/load/list actions so you can build libraries of looks, poses, palettes, and camera moves.
|
||||
- **Consistency tooling** – Narrative Weaver, Palette Mixer, Organizer, and Pipeline Combiner keep every fragment aligned, with automatic contradiction warnings.
|
||||
@@ -23,11 +23,13 @@ Modular prompt-engineering tools for ComfyUI that turn cinematic ideas into prod
|
||||
|
||||
### Core Builder Stack
|
||||
- `NoxPromptBuilder` *(NoxPrompter/Builders)* – Wan 2.2 style prompt assembler with basic/advanced formulas, keyword styling, randomization, preset storage, and pass-through inputs for Lighting Master and Camera Master outputs.
|
||||
- `NoxPromptBlueprintGenerator` *(NoxPrompter/Builders)* – Generates ready-to-use subject, scene, motion, hook, palette override, and insight strings from curated archetype, profession, temperament, environment, lighting, and style tables with custom overrides and presets.
|
||||
- `NoxPromptPaletteMixer` *(NoxPrompter/Companions)* – Produces palette override strings, custom keyword bundles, and usage notes from cinematic mood profiles.
|
||||
- `NoxPromptNarrativeWeaver` *(NoxPrompter/Companions)* – Outputs subject, scene, motion, hook, and descriptor text blocks from archetype + tone selectors.
|
||||
- `NoxPromptCombiner` *(NoxPrompter/Text)* – Blends up to three prompts via concatenate, alternate, blend, or weighted strategies.
|
||||
- `NoxPromptEnhancer` *(NoxPrompter/Text)* – Adds emphasis, quality tags, and style packs (artistic, photorealistic, cinematic, fantasy, sci-fi, portrait) to any prompt.
|
||||
- `NoxPromptAnalyzer` *(NoxPrompter/Analysis)* – Reports word count, complexity, detected style, sentiment, and optional quick critiques for the provided prompt.
|
||||
- `NoxPromptPromptInspector` *(NoxPrompter/Analysis)* – Cleans, deduplicates, and organizes prompt fragments, highlights contradiction warnings, and produces fragment lists and metrics for QA passes.
|
||||
|
||||
### Character & Styling Suite
|
||||
- `NoxPromptCharacterCreator` *(NoxPrompter/Characters)* – Builds lore-rich hero bios, story hooks, and prompt fragments with presetable archetypes and traits.
|
||||
@@ -66,7 +68,7 @@ After the restart, right-click the canvas and confirm a **NoxPrompter** category
|
||||
|
||||
1. **Ask the Usage Guide** – Drop `NoxPromptUsageGuide`, set `detail_level = Extended`, and review the generated workflow and safety pointers in ComfyUI.
|
||||
2. **Define your hero** – Chain `NoxPromptCharacterCreator → NoxPromptHumanDesigner → NoxPromptWardrobeDesigner` to lock identity, anatomy, and wardrobe language.
|
||||
3. **Seed the narrative** – Use `NoxPromptNarrativeWeaver` for subject/scene/motion text, and `NoxPromptPaletteMixer` for palette overrides + keywords.
|
||||
3. **Seed the narrative** – Run `NoxPromptBlueprintGenerator` for a quick subject/scene/motion/emotion blueprint, refine with `NoxPromptNarrativeWeaver`, and capture palette overrides + keywords via `NoxPromptPaletteMixer`.
|
||||
4. **Assemble the master prompt** – Feed the outputs into `NoxPromptBuilder` (Advanced Formula) and optionally enable `randomize_missing` with a seed for repeatable variations.
|
||||
5. **Polish or enhance** – Run the builder result through `NoxPromptEnhancer` or `NoxPromptCombiner` to layer styles, emphasis, or alternate prompts.
|
||||
6. **Add direction** – `NoxPromptActionDirector` and/or `NoxPromptLightingMaster` create dedicated camera + lighting clauses; `NoxPromptCamz` covers streaming personas.
|
||||
@@ -76,6 +78,7 @@ After the restart, right-click the canvas and confirm a **NoxPrompter** category
|
||||
## Detailed Usage
|
||||
|
||||
### Builder & Companion Trio
|
||||
- **Blueprint Generator** – Start with `NoxPromptBlueprintGenerator` to rapidly draft subject, scene, motion, emotion, and palette overrides from curated dropdowns. Its outputs slot directly into the Builder (subject/scene/motion), Palette Mixer (overrides), or Organizer (insight notes) when you need an ideation-safe baseline.
|
||||
- **Narrative Weaver → Palette Mixer → Prompt Builder** gives you richly described subject/scene/motion text plus cinematic palette overrides in seconds.
|
||||
- Set `keyword_style` to **auto** for labeled clauses, **inline** for free-flow descriptions, or **compact** for dense Wan 2.2 strings.
|
||||
- Turn on `randomize_missing` when dropdowns are left at “None”; the combination of curated vocabularies and seeded randomness is great for ideation batches.
|
||||
@@ -103,6 +106,7 @@ After the restart, right-click the canvas and confirm a **NoxPrompter** category
|
||||
- Pipeline order referencing every node used (matches the Usage Guide’s recommendations).
|
||||
- Reference notes grouped by theme (camera, motion, palette, safety) with contradiction warnings when descriptors disagree.
|
||||
- `NoxPromptOrganizer` is perfect for presentation. Select a structure mode (Builder-first, Narrative-first, Lighting-first), and it will output a formatted prompt, a missing-section list, and a quick overview for review sessions.
|
||||
- `NoxPromptPromptInspector` runs a QA pass on any prompt or negative block—splitting fragments, deduplicating them, running contradiction detection, and emitting organized fragments plus metrics you can feed into review docs or the Organizer.
|
||||
|
||||
### Preset Manager (Shared Behavior)
|
||||
- Most nodes expose `preset_action` (`none`, `save`, `load`, `list`) and `preset_name`. These hook into the shared `PresetManager` to store JSON presets under `ComfyNoxPrompter/presets/<node_name>/`.
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# Node Responsibility Matrix
|
||||
|
||||
This reference captures the intended focus area for each exported node.
|
||||
It was generated by reviewing `noxprompter.nodes.NODE_CLASSES` and the
|
||||
runtime inspection helper at `tools/inspect_node_inputs.py`.
|
||||
|
||||
| Node | Category | Primary Deliverable | Key Input Buckets | Potential Overlap Notes |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `NoxPromptUsageGuide` | Guidance | Onboarding copy | detail level toggles | None observed |
|
||||
| `NoxPromptWorkflowNavigator` | Guidance | Suggested node routes | goal, feature toggles | None observed |
|
||||
| `NoxPromptKontextHelper` | Editing | Repair instructions | edit focus, troubleshooting flags | None observed |
|
||||
| `NoxPromptOrganizer` | Planning | Prompt buckets summary | character/action/lighting/camera notes, structure | Camera notes accepted as plain text (not option driven) |
|
||||
| `NoxPromptPromptBoard` | Planning | Pitch board text | story/lighting/camera summaries | Camera entries are free-form text |
|
||||
| `NoxPromptShotlistOrganizer` | Planning | Shotlist with prep checklist | shot metadata incl. camera text | Camera entries are free-form text |
|
||||
| `NoxPromptProductionPlanner` | Planning | Departmental callouts | schedule + departmental notes | Camera notes are free-form text |
|
||||
| `NoxPromptBlueprintGenerator` | Builder | Structured blueprint tuple | archetype/environment/motion/style presets | Shares palette + motion controls with builder |
|
||||
| `NoxPromptBuilder` | Builder | Final positive/negative prompt | formula, subject briefs, palette strings, camera notes | Palette-driven assembly that consumes outputs from specialist nodes |
|
||||
| `NoxPromptPaletteMixer` | Builder | Palette strings | mood/intensity + toggles | None observed |
|
||||
| `NoxPromptPromptInspector` | QA | Fragment analysis | parsing/filter settings | None observed |
|
||||
| `NoxPromptAnalyzer` | QA | Focused review snippet | prompt + analysis type | None observed |
|
||||
| `NoxPromptPipelineCombiner` | QA/Assembly | Canonical pipeline prompt | upstream prompt fragments, overrides | Accepts camera overrides as text |
|
||||
| `NoxPromptCameraMaster` (`NoxPromptCameraLanguage`) | Camera | Rig + language bundle | comprehensive camera option sets | Source of truth for camera selections |
|
||||
| `NoxPromptAdvancedCamera` | Camera | (Not registered) advanced rig variants | TBD | Needs audit for registration |
|
||||
| `NoxPromptLightingMaster` | Lighting | Lighting blueprint + notes | lighting option sets, lighting notes, camera notes text | Camera rig capture removed; relies on notes only |
|
||||
| `NoxPromptActionDirector` | Action | Action beat prompt | action presets, camera language text | Camera language accepted via text |
|
||||
| `NoxPromptEnhancer` | Action | Prompt augmentation | emphasis toggles | None observed |
|
||||
| `NoxPromptPoseMaster` | Pose | Pose brief tuple | pose profile, safety brief, camera notes text | Camera framing presets removed in favor of freeform notes |
|
||||
| `NoxPromptHumanDesigner` | Character | Body/face prompt | morphology switches | None observed |
|
||||
| `NoxPromptCharacterCreator` | Character | Character spec | archetype/profession/temperament presets | Shares presets with BlueprintGenerator |
|
||||
| `NoxPromptWardrobeDesigner` | Wardrobe | Wardrobe prompt bundle | garment style controls | None observed |
|
||||
| `NoxPromptNarrativeWeaver` | Narrative | Story hook prompt | archetype/tone/environment | Shares fields with BlueprintGenerator |
|
||||
| `NoxPromptBlueprintGenerator` | Narrative | Blueprint tuple | archetype/environment/motion/style presets | Overlaps with CharacterCreator & Builder palettes |
|
||||
| `NoxPromptPaletteMixer` | Narrative | Palette string | mood/intensity | None observed |
|
||||
| `NoxPromptCamz` | Live persona | Streaming persona prompt | persona/stage/camera style | Camera style uses presets outside CameraMaster |
|
||||
| `NoxPromptNSFWDesigner` | NSFW | Styled NSFW blueprint | wardrobe/tone notes, safety brief, camera notes text | Camera framing presets removed in favor of freeform notes |
|
||||
| `NoxPromptNSFWPoseMaster` | NSFW Pose | Pose brief | pose profile, risk brief, camera notes text | Camera framing presets removed in favor of freeform notes |
|
||||
| `NoxPromptNSFWAIO` | NSFW | Aggregated blueprint | designer/action/safety prompts | Camera notes accepted as text |
|
||||
| `NoxPromptNSFWActionDirector` | NSFW Action | Action beat prompt | action presets + camera language text | Camera language accepted as text |
|
||||
|
||||
## Highlights
|
||||
|
||||
* The shared inspection script outputs the raw required/optional input keys for
|
||||
every node. Run it with:
|
||||
|
||||
```powershell
|
||||
C:/Users/James/Documents/GitHub/ComfyNoxPrompter/.venv/Scripts/python.exe tools/inspect_node_inputs.py
|
||||
```
|
||||
|
||||
* True **camera option sets** (enumerated selections backed by
|
||||
`CAMERA_*` constants) now reside solely in dedicated camera nodes:
|
||||
- `NoxPromptCameraMaster`
|
||||
- `NoxPromptCameraLanguage`
|
||||
- `NoxPromptAdvancedCamera`
|
||||
- `NoxPromptCamz` (`camera_style` preset family)
|
||||
|
||||
* Several non-camera nodes also expose *text* fields for camera notes; these can
|
||||
stay once preset-driven overlaps are resolved.
|
||||
|
||||
* Remaining shared preset libraries:
|
||||
- Character archetype/profession tables shared across `BlueprintGenerator` and `CharacterCreator`.
|
||||
- Motion/emotion tables used in both `Builder` and action/pipeline nodes.
|
||||
|
||||
This document will be updated as overlaps are resolved.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+41
-2
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from typing import Any, Dict, List, Tuple
|
||||
from typing import Any, Dict, Iterable, List, Tuple
|
||||
|
||||
from .constants import CUSTOM_OPTION, PRESET_ROOT, PROMPT_CONTRADICTION_RULES
|
||||
|
||||
@@ -170,6 +170,40 @@ class PresetManager:
|
||||
return sorted(names)
|
||||
|
||||
|
||||
def option_keys(
|
||||
options: Any,
|
||||
*,
|
||||
ensure_none: bool = True,
|
||||
none_label: str = "None",
|
||||
exclude_tokens: Iterable[str] | None = None,
|
||||
) -> List[str]:
|
||||
"""Return a sanitized list of option keys for dropdown inputs.
|
||||
|
||||
The helper keeps the original dictionary order, removes keys containing any of
|
||||
the ``exclude_tokens`` (case-insensitive), and ensures a ``none_label`` entry
|
||||
is available when ``ensure_none`` is True.
|
||||
"""
|
||||
|
||||
tokens = tuple(token.lower() for token in (exclude_tokens or ("custom",)))
|
||||
|
||||
if hasattr(options, "keys"):
|
||||
iterator = options.keys()
|
||||
else:
|
||||
iterator = options
|
||||
|
||||
labels: List[str] = []
|
||||
for key in iterator:
|
||||
normalized = (key or "").lower()
|
||||
if any(token in normalized for token in tokens):
|
||||
continue
|
||||
labels.append(key)
|
||||
|
||||
if ensure_none and none_label not in labels:
|
||||
labels.insert(0, none_label)
|
||||
|
||||
return labels
|
||||
|
||||
|
||||
class PresetMixin:
|
||||
_preset_manager = PresetManager()
|
||||
|
||||
@@ -231,10 +265,14 @@ def _resolve_action_option(
|
||||
|
||||
|
||||
def _resolve_option(selection: str, table: Dict[str, Dict[str, str]]) -> Tuple[str, str]:
|
||||
normalized = (selection or "").strip()
|
||||
if normalized.lower() == "none":
|
||||
return "", ""
|
||||
|
||||
record = table.get(selection)
|
||||
if record:
|
||||
return record.get("prompt", ""), record.get("notes", record.get("summary", ""))
|
||||
fallback = (selection or "").strip()
|
||||
fallback = normalized
|
||||
return fallback, ""
|
||||
|
||||
|
||||
@@ -322,4 +360,5 @@ __all__ = [
|
||||
"_split_tokens",
|
||||
"_format_notes",
|
||||
"detect_prompt_contradictions",
|
||||
"option_keys",
|
||||
]
|
||||
|
||||
+222
-2
@@ -1468,6 +1468,91 @@ NSFW_ACTION_INTERACTION_OPTIONS = {
|
||||
"Tandem Pose": {"prompt": "tandem pose alignment, sculpted silhouettes, mutual balance", "summary": "Dual posing moment engineered for visual harmony."},
|
||||
}
|
||||
|
||||
CAMERA_TYPES = {
|
||||
"None": "",
|
||||
"DSLR": "professional DSLR camera",
|
||||
"Mirrorless": "high-end mirrorless camera",
|
||||
"Film Camera": "classic film camera",
|
||||
"Point-and-Shoot": "compact point-and-shoot camera",
|
||||
"Action Camera": "rugged action camera",
|
||||
"Smartphone": "high-resolution smartphone camera",
|
||||
"Medium Format": "medium format camera",
|
||||
"Large Format": "large format camera",
|
||||
"Custom": "",
|
||||
}
|
||||
|
||||
ISO_OPTIONS = {
|
||||
"None": "",
|
||||
"50": "ISO 50",
|
||||
"100": "ISO 100",
|
||||
"200": "ISO 200",
|
||||
"400": "ISO 400",
|
||||
"800": "ISO 800",
|
||||
"1600": "ISO 1600",
|
||||
"3200": "ISO 3200",
|
||||
"6400": "ISO 6400",
|
||||
"Custom": "",
|
||||
}
|
||||
|
||||
FSTOP_OPTIONS = {
|
||||
"None": "",
|
||||
"f/1.4": "f/1.4 aperture",
|
||||
"f/2.0": "f/2.0 aperture",
|
||||
"f/2.8": "f/2.8 aperture",
|
||||
"f/4.0": "f/4.0 aperture",
|
||||
"f/5.6": "f/5.6 aperture",
|
||||
"f/8.0": "f/8.0 aperture",
|
||||
"f/11": "f/11 aperture",
|
||||
"f/16": "f/16 aperture",
|
||||
"f/22": "f/22 aperture",
|
||||
"Custom": "",
|
||||
}
|
||||
|
||||
SHUTTER_SPEED_OPTIONS = {
|
||||
"None": "",
|
||||
"1/8000": "1/8000s shutter speed",
|
||||
"1/4000": "1/4000s shutter speed",
|
||||
"1/2000": "1/2000s shutter speed",
|
||||
"1/1000": "1/1000s shutter speed",
|
||||
"1/500": "1/500s shutter speed",
|
||||
"1/250": "1/250s shutter speed",
|
||||
"1/125": "1/125s shutter speed",
|
||||
"1/60": "1/60s shutter speed",
|
||||
"1/30": "1/30s shutter speed",
|
||||
"1/15": "1/15s shutter speed",
|
||||
"1/8": "1/8s shutter speed",
|
||||
"1/4": "1/4s shutter speed",
|
||||
"1/2": "1/2s shutter speed",
|
||||
"1s": "1s shutter speed",
|
||||
"Custom": "",
|
||||
}
|
||||
|
||||
LENS_FOCAL_LENGTHS = {
|
||||
"None": "",
|
||||
"16mm": "16mm lens",
|
||||
"24mm": "24mm lens",
|
||||
"35mm": "35mm lens",
|
||||
"50mm": "50mm lens",
|
||||
"85mm": "85mm lens",
|
||||
"100mm": "100mm lens",
|
||||
"135mm": "135mm lens",
|
||||
"200mm": "200mm lens",
|
||||
"300mm": "300mm lens",
|
||||
"Custom": "",
|
||||
}
|
||||
|
||||
WHITE_BALANCE_OPTIONS = {
|
||||
"None": "",
|
||||
"Auto": "auto white balance",
|
||||
"Daylight": "daylight white balance",
|
||||
"Cloudy": "cloudy white balance",
|
||||
"Shade": "shade white balance",
|
||||
"Tungsten": "tungsten white balance",
|
||||
"Fluorescent": "fluorescent white balance",
|
||||
"Flash": "flash white balance",
|
||||
"Custom": "",
|
||||
}
|
||||
|
||||
EMOTION_OPTIONS = {
|
||||
"None": {"prompt": "", "summary": "No explicit emotional cue."},
|
||||
"Anger": {"prompt": "fiery anger, clenched expression, intense gaze", "summary": "Emotion of anger and determination."},
|
||||
@@ -2076,6 +2161,17 @@ CHARACTER_ROLE_OPTIONS = {
|
||||
}
|
||||
|
||||
WARDROBE_BASE_GARMENT_OPTIONS = {
|
||||
"Athletic Wear": {"prompt": "high-performance athletic wear, breathable mesh", "notes": "Sleek, functional attire designed for movement and endurance."},
|
||||
"Bohemian Maxi": {"prompt": "flowing bohemian maxi dress, layered prints, relaxed waist", "notes": "Lightweight, travel-friendly dress with breezy motion for outdoor scenes."},
|
||||
"Punk Layered": {"prompt": "punk layered ensemble, distressed tees, safety-pin accents", "notes": "Edgy street style mixing DIY textures and bold hardware details."},
|
||||
"Steampunk Coat": {"prompt": "steampunk frock coat, brass buckles, tailored waist", "notes": "Victorian-inspired outerwear with mechanical trimmings for period-fusion looks."},
|
||||
"Shimmer Slip": {"prompt": "silvery shimmer slip dress, bias-cut, delicate straps", "notes": "Minimalist evening layer that catches highlights and creates elegant movement."},
|
||||
"Crop Top & Miniskirt": {"prompt": "coordinated crop top with miniskirt, playful cut, modern edge", "notes": "Youthful two-piece set ideal for active or nightlife scenes."},
|
||||
"Tribal Wraps": {"prompt": "layered tribal wraps, woven sashes, textured panels", "notes": "Ethnographic-inspired drapery conveying handmade craft and ritual cues."},
|
||||
"Armor Tunic": {"prompt": "reinforced armor tunic, leather inlays, riveted seams", "notes": "Light protective layer balancing mobility and battlefield aesthetic."},
|
||||
"Festival Kimono": {"prompt": "short festival kimono, bold prints, tassel ties", "notes": "Playful performer piece designed for movement and color interaction."},
|
||||
"Parka Anorak": {"prompt": "insulated parka anorak, storm hood, taped seams", "notes": "Practical cold-weather outer layer built for expedition and realism."},
|
||||
"Ritual Vestments": {"prompt": "ceremonial vestments, embroidered panels, sigil trims", "notes": "Formal ritualwear that signals status and cultural specificity."},
|
||||
"Battle Dress": {"prompt": "battle dress core, reinforced bodice, articulated plating", "notes": "Armor-meets-fashion silhouette balancing protection and movement."},
|
||||
"yoga Pants & Tank": {"prompt": "stretch-fit yoga pants paired with breathable tank top", "notes": "Flexible activewear for dynamic poses and fluid motion."},
|
||||
"Evening Suit": {"prompt": "sleek evening suit, tailored fit", "notes": "Sophisticated formalwear with sharp lines and classic elegance."},
|
||||
@@ -2876,6 +2972,11 @@ HUMAN_DESIGN_BEAUTY_MARK_OPTIONS = {
|
||||
}
|
||||
|
||||
WARDROBE_COLORWAY_OPTIONS = {
|
||||
"Metallic Sheen": {"prompt": "metallic sheen palette, silver, gold, bronze", "notes": "Reflective metallics catching light dynamically."},
|
||||
"Neon Glow": {"prompt": "neon glow palette, electric pink, bright cyan", "notes": "Vibrant neon colors radiating energy."},
|
||||
"Floral Fantasy": {"prompt": "floral fantasy palette, blooming petals, lush greens", "notes": "Romantic floral hues evoking a garden paradise."},
|
||||
"Geometric Harmony": {"prompt": "geometric harmony palette, soft shapes, balanced colors", "notes": "Soothing palette with a focus on shape and form."},
|
||||
"Abstract Vortex": {"prompt": "abstract vortex palette, swirling colors, dynamic movement", "notes": "Energetic palette capturing the essence of motion."},
|
||||
"Monochrome Obsidian": {"prompt": "monochrome obsidian palette, layered blacks, matte to gloss", "notes": "Black-on-black layering emphasizing texture over hue."},
|
||||
"Solar Burst": {"prompt": "solar burst gradient, saffron to rose gold", "notes": "Sunrise gradient igniting dramatic silhouettes."},
|
||||
"Moonlit Pastel": {"prompt": "moonlit pastel wash, lavender mist, silver trims", "notes": "Soft pastels cooled by moonlit silver accents."},
|
||||
@@ -2904,6 +3005,10 @@ WARDROBE_COLORWAY_OPTIONS = {
|
||||
}
|
||||
|
||||
WARDROBE_PATTERN_OPTIONS = {
|
||||
"none": {"prompt": "plain fabric, no pattern", "notes": "Clean, unadorned fabric showcasing silhouette."},
|
||||
"normal": {"prompt": "subtle fabric texture, fine weave", "notes": "Light texture adding depth without distraction."},
|
||||
"bold": {"prompt": "bold graphic patterns, high contrast", "notes": "Striking patterns that command attention."},
|
||||
"Floral": {"prompt": "floral motifs, blooming vines", "notes": "Lush botanical patterns evoking garden elegance."},
|
||||
"Fractal": {"prompt": "fractal tessellation pattern, recursive geometry", "notes": "Endless fractal motif that feels otherworldly."},
|
||||
"Constellation": {"prompt": "constellation embroidery, starlit map", "notes": "Star chart motifs mapping ancestral skies."},
|
||||
"Circuitry": {"prompt": "circuit board etching, luminous trace", "notes": "Printed circuits glowing with micro current."},
|
||||
@@ -3372,6 +3477,12 @@ NSFW_POSE_PROFILES = {
|
||||
"prompt": "",
|
||||
"notes": "No specific pose emphasis.",
|
||||
},
|
||||
"XXX laying" : {
|
||||
"prompt": "sensual laying pose, relaxed limbs, inviting gaze, legs spread, gentials showing toward caemra with legs apart",
|
||||
"notes": ""
|
||||
},
|
||||
|
||||
|
||||
"Suggestive Pinup": {
|
||||
"prompt": "suggestive pinup pose, playful hip tilt, confident eye contact",
|
||||
"notes": "Keep the focus on charisma and silhouette rather than explicit detail.",
|
||||
@@ -3439,6 +3550,87 @@ NSFW_WARDROBE_STYLES = {
|
||||
"prompt": "",
|
||||
"notes": "No wardrobe styling adjustments.",
|
||||
},
|
||||
"XXX Fetish Noir": {
|
||||
"prompt": "leather corset, thigh-high boots, wet-look finish, smoky makeup",
|
||||
"notes": "Fetish-influenced editorial styling; emphasize agency, secure fittings, and confident posture.",
|
||||
},
|
||||
"XXX Bondage Lace": {
|
||||
"prompt": "delicate lace with decorative strap harness, inset O-rings, sheer panels",
|
||||
"notes": "Suggestive harness layering while keeping composition tasteful and consent-forward.",
|
||||
},
|
||||
"XXX Open-Back Bodysuit": {
|
||||
"prompt": "open-back bodysuit with cheek-baring cutouts, high-leg silhouette",
|
||||
"notes": "Bold exposure focused on silhouette and form—frame to remain artistic rather than explicit.",
|
||||
},
|
||||
"XXX Sheer Tease": {
|
||||
"prompt": "ultra-sheer mesh dress, visible silhouette, strategic opaque trims",
|
||||
"notes": "Plays with implied nudity through translucence; maintain tasteful coverage in framing.",
|
||||
},
|
||||
"XXX Latex Lingerie": {
|
||||
"prompt": "latex bra and brief set, high-gloss contour seams, polished finishing",
|
||||
"notes": "Strong fetish aesthetic—watch specular highlights and maintain performer comfort.",
|
||||
},
|
||||
"XXX Peekaboo Cutouts": {
|
||||
"prompt": "micro-cutout lingerie with geometric straps, peekaboo reveals, bold hardware",
|
||||
"notes": "Edgy, playful reveals—balance with confident posing and clear consent cues.",
|
||||
},
|
||||
"XXX Wet-Look Bikini": {
|
||||
"prompt": "wet-look bikini, glistening finish, water-sheen styling",
|
||||
"notes": "Beach-club adult vibe emphasizing texture and sheen without explicit detail.",
|
||||
},
|
||||
"XXX Chains & Fringe": {
|
||||
"prompt": "metal chain accents woven with fringe trims, kinetic movement on hips and shoulders",
|
||||
"notes": "Dynamic stage-ready outfit—secure attachments and avoid sharp hardware near skin.",
|
||||
},
|
||||
"XXX Decorative Harness": {
|
||||
"prompt": "ornamental harness layered over silk lingerie, metallic rings and polished straps",
|
||||
"notes": "Accessorizing harness for adult editorial looks; ensure comfort and non-restrictive wear.",
|
||||
},
|
||||
"XXX Velvet Restraint": {
|
||||
"prompt": "velvet harness and matching cuffs, plush textures, deep jewel tones",
|
||||
"notes": "Soft-fetish aesthetic prioritizing plush materials and consensual visual language.",
|
||||
},
|
||||
"Boudoir Chic": {
|
||||
"prompt": "silk camisole with lace robe, soft pastel palette, delicate straps",
|
||||
"notes": "Blend romantic fabrics with intimate lighting for tender boudoir vibes.",
|
||||
},
|
||||
"Retro Pinup": {
|
||||
"prompt": "vintage pinup set, high-waist briefs, polka-dot prints, curled hair",
|
||||
"notes": "Classic curves and playful props for nostalgic glamour.",
|
||||
},
|
||||
"Sporty Crop": {
|
||||
"prompt": "athletic crop top and track shorts, breathable mesh, sporty stripes",
|
||||
"notes": "High-energy active styling for dynamic movement and casual confidence.",
|
||||
},
|
||||
"Costume Play": {
|
||||
"prompt": "themed cosplay-inspired outfit, character-evocative silhouette, playful accessories",
|
||||
"notes": "Creative, character-led styling while avoiding direct trademarked likenesses.",
|
||||
},
|
||||
"Satin Slip": {
|
||||
"prompt": "bias-cut satin slip dress, whisper-thin sheen, minimalist straps",
|
||||
"notes": "Elegant and understated—works beautifully with soft directional light.",
|
||||
},
|
||||
"Sheer Lace Overlay": {
|
||||
"prompt": "sheer lace overlay layered over opaque panels, floral embroidery accents",
|
||||
"notes": "Use layered coverage to suggest form while maintaining tasteful composition.",
|
||||
},
|
||||
"Bondage-Inspired": {
|
||||
"prompt": "strap harness elements layered over garments, reinforced seams, polished buckles",
|
||||
"notes": "Aesthetic nod to fetishwear; emphasize secure construction and consensual presentation.",
|
||||
},
|
||||
"Festival Rave": {
|
||||
"prompt": "neon micro-top, mesh panels, glitter accents and playful tassels",
|
||||
"notes": "Vibrant, high-energy palette built for nightlife and festival atmospheres.",
|
||||
},
|
||||
"Sport-Luxe": {
|
||||
"prompt": "luxury athleisure, cashmere-lined hoodie, streamlined joggers, subtle sheen",
|
||||
"notes": "Mix comfort with elevated materials for a refined active look.",
|
||||
},
|
||||
"Corporate Seductress": {
|
||||
"prompt": "tailored blazer worn over silk blouse and pencil skirt, assertive heels",
|
||||
"notes": "Power-dressing with a subtle sensual edge suitable for editorial styling.",
|
||||
},
|
||||
|
||||
"Casual": {
|
||||
"prompt": "comfortable casual wear, soft fabrics, relaxed fit",
|
||||
"notes": "Emphasize comfort and ease of movement.",
|
||||
@@ -3452,9 +3644,13 @@ NSFW_WARDROBE_STYLES = {
|
||||
"notes": "Highlight femininity and elegance through intricate lace details.",
|
||||
},
|
||||
"Torn Yoga Pants": {
|
||||
"prompt": "torn yoga pants, torn crotch, snug fit, athletic vibe",
|
||||
"prompt": "ripped in the crotch, torn yoga pants, torn crotch, snug fit, athletic vibe",
|
||||
"notes": "Combine comfort with a hint of edginess.",
|
||||
},
|
||||
"Ripped Denim Shorts": {
|
||||
"prompt": "ripped in the crotch denim shorts, frayed edges, casual cool",
|
||||
"notes": "Perfect for laid-back summer vibes with a touch of edge.",
|
||||
},
|
||||
"Torn Jeans": {
|
||||
"prompt": "ripped denim jeans, distressed details, casual cool",
|
||||
"notes": "Embrace a laid-back vibe with edgy denim.",
|
||||
@@ -3536,6 +3732,10 @@ NSFW_TONE_MOODS = {
|
||||
"prompt": "",
|
||||
"notes": "No additional mood guidance.",
|
||||
},
|
||||
"XXX Extreme": {
|
||||
"prompt": "extreme adult vibe, high-energy, bold expression",
|
||||
"notes": "Maximize intensity while ensuring clear consent and agency cues.",
|
||||
},
|
||||
"Erotic" : {
|
||||
"prompt": "sensual erotic vibe, smoldering gaze, intimate setting",
|
||||
"notes": "Emphasize connection and allure through body language.",
|
||||
@@ -3821,6 +4021,7 @@ NSFW_HEAT_LEVELS = {
|
||||
"notes": "No additional heat descriptors applied.",
|
||||
"negative": "",
|
||||
},
|
||||
|
||||
"Subtle": {
|
||||
"prompt": "subtle allure, whispered tension, restrained tease",
|
||||
"notes": "Ideal for low-intensity boudoir and soft editorial scenarios.",
|
||||
@@ -3842,7 +4043,7 @@ NSFW_HEAT_LEVELS = {
|
||||
"negative": "demeaning scenarios, non-consensual direction",
|
||||
},
|
||||
|
||||
"Solo XXX": {
|
||||
"Solo XXX sensual_fingering": {
|
||||
"prompt": "solo xxx energy, unapologetic self-expression, empowered allure, sensual_fingering,",
|
||||
"notes": "Celebrate solo adult confidence with clear agency messaging.",
|
||||
"negative": "non-consensual themes, degrading scenarios",
|
||||
@@ -3868,6 +4069,12 @@ NSFW_HEAT_LEVELS = {
|
||||
"notes": "Use the custom heat prompt input for adult-only heat descriptors.",
|
||||
"negative": "",
|
||||
},
|
||||
"XXX Extreme": {
|
||||
"prompt": "extreme adult intensity, boundary-pushing exhibitionism, high-octane performance",
|
||||
"notes": "For explicit adult contexts only—ensure all elements emphasize consent and empowerment.",
|
||||
"negative": "non-consensual themes, degrading scenarios, fetish extremes",
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
NSFW_EXPLICITNESS_LEVELS = {
|
||||
@@ -3899,6 +4106,7 @@ NSFW_EXPLICITNESS_LEVELS = {
|
||||
"prompt": "erotic showcase, full nude confidence, provocative glimmer lighting",
|
||||
"negative": "pornographic close-ups, bodily fluids, explicit intercourse depiction",
|
||||
},
|
||||
|
||||
"XXX Performance": {
|
||||
"prompt": "adult-stage performance vibe, theatrical erotica, high-voltage seduction",
|
||||
"negative": "graphic sexual acts, fetish degradation, non-consensual scenarios",
|
||||
@@ -3931,6 +4139,8 @@ NSFW_BASELINE_NEGATIVES = [
|
||||
"youth",
|
||||
"loli",
|
||||
"shota",
|
||||
"prepubescent",
|
||||
"childlike",
|
||||
"young-looking",
|
||||
"non-consensual",
|
||||
"abuse",
|
||||
@@ -3969,6 +4179,11 @@ PROMPT_CONTRADICTION_RULES = [
|
||||
"midday",
|
||||
"high noon",
|
||||
"noonday",
|
||||
"afternoon",
|
||||
"sun-drenched",
|
||||
"sun-soaked",
|
||||
"broad daylight",
|
||||
|
||||
],
|
||||
"night": [
|
||||
"night",
|
||||
@@ -3977,6 +4192,11 @@ PROMPT_CONTRADICTION_RULES = [
|
||||
"moonlit",
|
||||
"nocturnal",
|
||||
"after-hours",
|
||||
"dark skies",
|
||||
"starry night",
|
||||
"twinkling stars",
|
||||
"city lights",
|
||||
"urban glow",
|
||||
],
|
||||
"dawn": ["dawn", "sunrise", "first light"],
|
||||
"dusk": ["dusk", "sunset", "twilight", "golden hour"],
|
||||
|
||||
@@ -5,11 +5,14 @@ from __future__ import annotations
|
||||
from .nox_prompt_action_director import NoxPromptActionDirector
|
||||
from .nox_prompt_analyzer import NoxPromptAnalyzer
|
||||
from .nox_prompt_builder import NoxPromptBuilder
|
||||
from .nox_prompt_blueprint_generator import NoxPromptBlueprintGenerator
|
||||
from .nox_prompt_camz import NoxPromptCamz
|
||||
from .nox_prompt_camera_language import NoxPromptCameraMaster, NoxPromptCameraLanguage
|
||||
from .nox_prompt_camera_selector import NoxPromptCameraSelector
|
||||
from .nox_prompt_character_creator import NoxPromptCharacterCreator
|
||||
from .nox_prompt_combiner import NoxPromptCombiner
|
||||
from .nox_prompt_enhancer import NoxPromptEnhancer
|
||||
from .nox_prompt_prompt_inspector import NoxPromptPromptInspector
|
||||
from .nox_prompt_human_designer import NoxPromptHumanDesigner
|
||||
from .nox_prompt_lighting_master import NoxPromptLightingMaster
|
||||
from .nox_prompt_narrative_weaver import NoxPromptNarrativeWeaver
|
||||
@@ -39,7 +42,9 @@ _NODE_CLASSES = (
|
||||
NoxPromptProductionPlanner,
|
||||
NoxPromptEnhancer,
|
||||
NoxPromptBuilder,
|
||||
NoxPromptBlueprintGenerator,
|
||||
NoxPromptPaletteMixer,
|
||||
NoxPromptPromptInspector,
|
||||
NoxPromptNarrativeWeaver,
|
||||
NoxPromptCharacterCreator,
|
||||
NoxPromptHumanDesigner,
|
||||
@@ -53,6 +58,7 @@ _NODE_CLASSES = (
|
||||
NoxPromptNSFWDesigner,
|
||||
NoxPromptNSFWPoseMaster,
|
||||
NoxPromptCamz,
|
||||
NoxPromptCameraSelector,
|
||||
NoxPromptCombiner,
|
||||
NoxPromptPipelineCombiner,
|
||||
NoxPromptAnalyzer,
|
||||
@@ -77,7 +83,9 @@ __all__ = [
|
||||
"NoxPromptProductionPlanner",
|
||||
"NoxPromptEnhancer",
|
||||
"NoxPromptBuilder",
|
||||
"NoxPromptBlueprintGenerator",
|
||||
"NoxPromptPaletteMixer",
|
||||
"NoxPromptPromptInspector",
|
||||
"NoxPromptNarrativeWeaver",
|
||||
"NoxPromptCharacterCreator",
|
||||
"NoxPromptHumanDesigner",
|
||||
@@ -89,6 +97,7 @@ __all__ = [
|
||||
"NoxPromptLightingMaster",
|
||||
"NoxPromptCameraMaster",
|
||||
"NoxPromptCameraLanguage",
|
||||
"NoxPromptCameraSelector",
|
||||
"NoxPromptNSFWDesigner",
|
||||
"NoxPromptNSFWPoseMaster",
|
||||
"NoxPromptCamz",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,342 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
from typing import Dict, List, Sequence, Tuple
|
||||
|
||||
from ..common import PresetMixin, _resolve_option
|
||||
from ..constants import (
|
||||
CHARACTER_ARCHETYPE_OPTIONS,
|
||||
CHARACTER_PROFESSION_OPTIONS,
|
||||
CHARACTER_TEMPERAMENT_OPTIONS,
|
||||
CUSTOM_OPTION,
|
||||
EMOTION_OPTIONS,
|
||||
LIGHT_SOURCE_OPTIONS,
|
||||
MOTION_TYPE_OPTIONS,
|
||||
NARRATIVE_ENVIRONMENTS,
|
||||
VISUAL_STYLE_OPTIONS,
|
||||
)
|
||||
|
||||
|
||||
class NoxPromptBlueprintGenerator(PresetMixin):
|
||||
"""Craft structured prompt blueprints from curated option sets."""
|
||||
|
||||
_CATEGORY = "NoxPrompter/Builders"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"character_archetype": (
|
||||
cls._choices(CHARACTER_ARCHETYPE_OPTIONS),
|
||||
{"default": "Champion"},
|
||||
),
|
||||
"character_profession": (
|
||||
cls._choices(CHARACTER_PROFESSION_OPTIONS),
|
||||
{"default": "Starship Captain"},
|
||||
),
|
||||
"character_temperament": (
|
||||
cls._choices(CHARACTER_TEMPERAMENT_OPTIONS),
|
||||
{"default": "Stoic"},
|
||||
),
|
||||
"narrative_environment": (
|
||||
cls._choices(NARRATIVE_ENVIRONMENTS),
|
||||
{"default": "Rain-Soaked Citadel"},
|
||||
),
|
||||
"motion_profile": (
|
||||
cls._choices(MOTION_TYPE_OPTIONS),
|
||||
{"default": "Running"},
|
||||
),
|
||||
"emotion_tag": (
|
||||
cls._choices(EMOTION_OPTIONS),
|
||||
{"default": "Determination"},
|
||||
),
|
||||
"light_source": (
|
||||
cls._choices(LIGHT_SOURCE_OPTIONS),
|
||||
{"default": "Daylight"},
|
||||
),
|
||||
"visual_style": (
|
||||
cls._choices(VISUAL_STYLE_OPTIONS),
|
||||
{"default": "Cinematic"},
|
||||
),
|
||||
"randomize_missing": ("BOOLEAN", {"default": False}),
|
||||
"random_seed": ("INT", {"default": 42, "min": 0, "max": 1_000_000, "step": 1}),
|
||||
},
|
||||
"optional": {
|
||||
"subject_override": ("STRING", {"multiline": True, "default": ""}),
|
||||
"environment_override": ("STRING", {"multiline": True, "default": ""}),
|
||||
"motion_override": ("STRING", {"multiline": True, "default": ""}),
|
||||
"emotion_override": ("STRING", {"multiline": True, "default": ""}),
|
||||
"focus_detail": ("STRING", {"multiline": True, "default": "accent lighting, kinetic sparks"}),
|
||||
"additional_notes": ("STRING", {"multiline": True, "default": ""}),
|
||||
"preset_action": (["none", "save", "load", "list"], {"default": "none"}),
|
||||
"preset_name": ("STRING", {"default": ""}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (
|
||||
"STRING",
|
||||
"STRING",
|
||||
"STRING",
|
||||
"STRING",
|
||||
"STRING",
|
||||
"STRING",
|
||||
"STRING",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"subject_focus",
|
||||
"scene_setting",
|
||||
"motion_arc",
|
||||
"narrative_hook",
|
||||
"palette_overrides",
|
||||
"insight_notes",
|
||||
"preset_status",
|
||||
)
|
||||
FUNCTION = "generate_blueprint"
|
||||
CATEGORY = _CATEGORY
|
||||
|
||||
@staticmethod
|
||||
def _choices(table: Dict[str, Dict[str, str]]) -> List[str]:
|
||||
return list(table.keys()) + ["Randomize", CUSTOM_OPTION]
|
||||
|
||||
def generate_blueprint(
|
||||
self,
|
||||
character_archetype: str,
|
||||
character_profession: str,
|
||||
character_temperament: str,
|
||||
narrative_environment: str,
|
||||
motion_profile: str,
|
||||
emotion_tag: str,
|
||||
light_source: str,
|
||||
visual_style: str,
|
||||
randomize_missing: bool,
|
||||
random_seed: int,
|
||||
subject_override: str = "",
|
||||
environment_override: str = "",
|
||||
motion_override: str = "",
|
||||
emotion_override: str = "",
|
||||
focus_detail: str = "",
|
||||
additional_notes: str = "",
|
||||
preset_action: str = "none",
|
||||
preset_name: str = "",
|
||||
) -> Tuple[str, str, str, str, str, str, str]:
|
||||
config = {
|
||||
"character_archetype": character_archetype,
|
||||
"character_profession": character_profession,
|
||||
"character_temperament": character_temperament,
|
||||
"narrative_environment": narrative_environment,
|
||||
"motion_profile": motion_profile,
|
||||
"emotion_tag": emotion_tag,
|
||||
"light_source": light_source,
|
||||
"visual_style": visual_style,
|
||||
"randomize_missing": randomize_missing,
|
||||
"random_seed": random_seed,
|
||||
"subject_override": subject_override,
|
||||
"environment_override": environment_override,
|
||||
"motion_override": motion_override,
|
||||
"emotion_override": emotion_override,
|
||||
"focus_detail": focus_detail,
|
||||
"additional_notes": additional_notes,
|
||||
}
|
||||
|
||||
config, preset_status = self._apply_preset_action(
|
||||
"blueprint_generator",
|
||||
preset_action,
|
||||
preset_name,
|
||||
config,
|
||||
)
|
||||
|
||||
character_archetype = config.get("character_archetype", character_archetype)
|
||||
character_profession = config.get("character_profession", character_profession)
|
||||
character_temperament = config.get("character_temperament", character_temperament)
|
||||
narrative_environment = config.get("narrative_environment", narrative_environment)
|
||||
motion_profile = config.get("motion_profile", motion_profile)
|
||||
emotion_tag = config.get("emotion_tag", emotion_tag)
|
||||
light_source = config.get("light_source", light_source)
|
||||
visual_style = config.get("visual_style", visual_style)
|
||||
randomize_missing = bool(config.get("randomize_missing", randomize_missing))
|
||||
random_seed = int(config.get("random_seed", random_seed))
|
||||
subject_override = config.get("subject_override", subject_override)
|
||||
environment_override = config.get("environment_override", environment_override)
|
||||
motion_override = config.get("motion_override", motion_override)
|
||||
emotion_override = config.get("emotion_override", emotion_override)
|
||||
focus_detail = config.get("focus_detail", focus_detail)
|
||||
additional_notes = config.get("additional_notes", additional_notes)
|
||||
|
||||
rng = random.Random(random_seed)
|
||||
|
||||
archetype_prompt, archetype_notes, archetype_name = self._resolve_selection(
|
||||
character_archetype,
|
||||
subject_override,
|
||||
CHARACTER_ARCHETYPE_OPTIONS,
|
||||
randomize_missing,
|
||||
rng,
|
||||
)
|
||||
profession_prompt, profession_notes, profession_name = self._resolve_selection(
|
||||
character_profession,
|
||||
subject_override,
|
||||
CHARACTER_PROFESSION_OPTIONS,
|
||||
randomize_missing,
|
||||
rng,
|
||||
)
|
||||
temperament_prompt, temperament_notes, temperament_name = self._resolve_selection(
|
||||
character_temperament,
|
||||
subject_override,
|
||||
CHARACTER_TEMPERAMENT_OPTIONS,
|
||||
randomize_missing,
|
||||
rng,
|
||||
)
|
||||
environment_prompt, environment_notes, environment_name = self._resolve_selection(
|
||||
narrative_environment,
|
||||
environment_override,
|
||||
NARRATIVE_ENVIRONMENTS,
|
||||
randomize_missing,
|
||||
rng,
|
||||
)
|
||||
motion_prompt, motion_notes, motion_name = self._resolve_selection(
|
||||
motion_profile,
|
||||
motion_override,
|
||||
MOTION_TYPE_OPTIONS,
|
||||
randomize_missing,
|
||||
rng,
|
||||
)
|
||||
emotion_prompt, emotion_notes, emotion_name = self._resolve_selection(
|
||||
emotion_tag,
|
||||
emotion_override,
|
||||
EMOTION_OPTIONS,
|
||||
randomize_missing,
|
||||
rng,
|
||||
)
|
||||
light_prompt, light_notes, light_name = self._resolve_selection(
|
||||
light_source,
|
||||
"",
|
||||
LIGHT_SOURCE_OPTIONS,
|
||||
randomize_missing,
|
||||
rng,
|
||||
)
|
||||
style_prompt, style_notes, style_name = self._resolve_selection(
|
||||
visual_style,
|
||||
"",
|
||||
VISUAL_STYLE_OPTIONS,
|
||||
randomize_missing,
|
||||
rng,
|
||||
)
|
||||
|
||||
subject_focus = self._combine_fragments(
|
||||
[
|
||||
archetype_prompt,
|
||||
profession_prompt,
|
||||
temperament_prompt,
|
||||
subject_override,
|
||||
]
|
||||
)
|
||||
|
||||
scene_setting = self._combine_fragments(
|
||||
[
|
||||
environment_prompt,
|
||||
environment_override,
|
||||
light_prompt,
|
||||
focus_detail,
|
||||
]
|
||||
)
|
||||
|
||||
motion_arc = motion_override.strip() or motion_prompt
|
||||
if motion_arc and motion_arc[-1] not in ".!?":
|
||||
motion_arc = f"{motion_arc}."
|
||||
|
||||
narrative_hook = self._combine_fragments(
|
||||
[
|
||||
environment_notes,
|
||||
motion_notes,
|
||||
emotion_notes,
|
||||
style_prompt,
|
||||
additional_notes,
|
||||
],
|
||||
separator=" | ",
|
||||
)
|
||||
|
||||
palette_overrides = self._build_palette_overrides(light_name, style_name)
|
||||
|
||||
insight_notes = self._build_insight_notes(
|
||||
{
|
||||
"Archetype": (archetype_name, archetype_notes),
|
||||
"Profession": (profession_name, profession_notes),
|
||||
"Temperament": (temperament_name, temperament_notes),
|
||||
"Environment": (environment_name, environment_notes or environment_prompt),
|
||||
"Motion": (motion_name, motion_notes or motion_prompt),
|
||||
"Emotion": (emotion_name, emotion_prompt or emotion_notes),
|
||||
"Lighting": (light_name, light_notes),
|
||||
"Style": (style_name, style_notes or style_prompt),
|
||||
}
|
||||
)
|
||||
|
||||
if narrative_hook and motion_arc:
|
||||
narrative_hook = f"{narrative_hook} | Motion focus: {motion_arc.strip()}"
|
||||
|
||||
return (
|
||||
subject_focus,
|
||||
scene_setting,
|
||||
motion_arc,
|
||||
narrative_hook,
|
||||
palette_overrides,
|
||||
insight_notes,
|
||||
preset_status,
|
||||
)
|
||||
|
||||
def _resolve_selection(
|
||||
self,
|
||||
selection: str,
|
||||
custom_value: str,
|
||||
table: Dict[str, Dict[str, str]],
|
||||
randomize_missing: bool,
|
||||
rng: random.Random,
|
||||
) -> Tuple[str, str, str]:
|
||||
choice = (selection or "").strip()
|
||||
if choice == "Randomize":
|
||||
choice = rng.choice(list(table.keys()))
|
||||
elif randomize_missing and (choice.lower() in {"none", ""}):
|
||||
eligible = [name for name in table if name.lower() != "none"] or list(table.keys())
|
||||
choice = rng.choice(eligible)
|
||||
if choice == CUSTOM_OPTION:
|
||||
text = custom_value.strip()
|
||||
return text, "", "Custom"
|
||||
prompt, notes = _resolve_option(choice, table)
|
||||
return prompt, notes, choice
|
||||
|
||||
def _combine_fragments(self, fragments: Sequence[str], separator: str = ", ") -> str:
|
||||
cleaned: List[str] = []
|
||||
for fragment in fragments:
|
||||
if not fragment:
|
||||
continue
|
||||
stripped = fragment.strip()
|
||||
if stripped:
|
||||
cleaned.append(stripped)
|
||||
merged = separator.join(dict.fromkeys(cleaned))
|
||||
return merged
|
||||
|
||||
def _build_palette_overrides(self, light_choice: str, style_choice: str) -> str:
|
||||
entries: List[str] = []
|
||||
if light_choice and light_choice.lower() not in {"", "none"}:
|
||||
entries.append(f"light_source: {light_choice}")
|
||||
if style_choice and style_choice.lower() not in {"", "none"}:
|
||||
entries.append(f"visual_style: {style_choice}")
|
||||
return "\n".join(entries)
|
||||
|
||||
def _build_insight_notes(self, sources: Dict[str, Tuple[str, str]]) -> str:
|
||||
lines: List[str] = []
|
||||
for label, payload in sources.items():
|
||||
name, value = payload
|
||||
descriptor = (name or "").strip()
|
||||
content = (value or "").strip()
|
||||
heading = f"{label}"
|
||||
if descriptor and descriptor.lower() not in {"", "none", "custom"}:
|
||||
heading = f"{label} ({descriptor})"
|
||||
if not content:
|
||||
continue
|
||||
if content.endswith("."):
|
||||
lines.append(f"{heading}: {content}")
|
||||
else:
|
||||
lines.append(f"{heading}: {content}.")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
__all__ = ["NoxPromptBlueprintGenerator"]
|
||||
@@ -1,26 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
import re
|
||||
from ..common import PresetMixin, PromptFragmentFilter
|
||||
from ..constants import (
|
||||
CAMERA_ADVANCED_OPTIONS,
|
||||
CAMERA_BASIC_OPTIONS,
|
||||
COLOR_TONE_OPTIONS,
|
||||
COMPOSITION_OPTIONS,
|
||||
EMOTION_OPTIONS,
|
||||
FORMULA_CONFIGS,
|
||||
LENS_ANGLE_OPTIONS,
|
||||
LENS_FOCAL_OPTIONS,
|
||||
LENS_TYPE_OPTIONS,
|
||||
LIGHT_QUALITY_OPTIONS,
|
||||
LIGHT_SOURCE_OPTIONS,
|
||||
MOTION_TYPE_OPTIONS,
|
||||
SHOT_SIZE_OPTIONS,
|
||||
SPECIAL_EFFECT_OPTIONS,
|
||||
TIME_OF_DAY_OPTIONS,
|
||||
VISUAL_STYLE_OPTIONS,
|
||||
)
|
||||
from ..constants import FORMULA_CONFIGS
|
||||
|
||||
class NoxPromptBuilder(PresetMixin):
|
||||
"""Construct prompts using formulas and curated keyword palettes."""
|
||||
@@ -37,26 +18,11 @@ class NoxPromptBuilder(PresetMixin):
|
||||
"optional": {
|
||||
"narrative_hook": ("STRING", {"multiline": True, "default": "Camera locks onto her determined eyes as thunder cracks overhead"}),
|
||||
"model_emphasis": ("STRING", {"multiline": True, "default": "Cinematic aesthetic control, precise semantic adherence"}),
|
||||
"light_source": (list(LIGHT_SOURCE_OPTIONS.keys()), {"default": "Daylight"}),
|
||||
"light_quality": (list(LIGHT_QUALITY_OPTIONS.keys()), {"default": "Soft Light"}),
|
||||
"time_of_day": (list(TIME_OF_DAY_OPTIONS.keys()), {"default": "Dusk"}),
|
||||
"shot_size": (list(SHOT_SIZE_OPTIONS.keys()), {"default": "Medium Close-up"}),
|
||||
"composition": (list(COMPOSITION_OPTIONS.keys()), {"default": "Balanced"}),
|
||||
"lens_focal": (list(LENS_FOCAL_OPTIONS.keys()), {"default": "Medium"}),
|
||||
"lens_angle": (list(LENS_ANGLE_OPTIONS.keys()), {"default": "None"}),
|
||||
"lens_type": (list(LENS_TYPE_OPTIONS.keys()), {"default": "Single Shot"}),
|
||||
"camera_basic": (list(CAMERA_BASIC_OPTIONS.keys()), {"default": "None"}),
|
||||
"camera_advanced": (list(CAMERA_ADVANCED_OPTIONS.keys()), {"default": "None"}),
|
||||
"color_tone": (list(COLOR_TONE_OPTIONS.keys()), {"default": "Warm Tone"}),
|
||||
"motion_type": (list(MOTION_TYPE_OPTIONS.keys()), {"default": "Running"}),
|
||||
"emotion": (list(EMOTION_OPTIONS.keys()), {"default": "Joy"}),
|
||||
"visual_style": (list(VISUAL_STYLE_OPTIONS.keys()), {"default": "Anime"}),
|
||||
"special_effect": (list(SPECIAL_EFFECT_OPTIONS.keys()), {"default": "None"}),
|
||||
"aesthetic_palette": ("STRING", {"multiline": True, "default": "Daylight rim lighting | Soft diffusion fill | Warm tone bounce | Balanced composition"}),
|
||||
"dynamic_palette": ("STRING", {"multiline": True, "default": "Running motion arcs | Joyful expressions | Pan-right sweep | Crane follow"}),
|
||||
"style_palette": ("STRING", {"multiline": True, "default": "Anime cinematic tonality | Tilt-shift miniature effect"}),
|
||||
"keyword_style": (["auto", "inline", "compact"], {"default": "auto"}),
|
||||
"quality_profile": (["Balanced", "Cinematic", "Minimal", "Narrative"], {"default": "Balanced"}),
|
||||
"randomize_missing": ("BOOLEAN", {"default": False}),
|
||||
"random_seed": ("INT", {"default": 0, "min": 0, "max": 1_000_000, "step": 1}),
|
||||
"palette_overrides": ("STRING", {"multiline": True, "default": ""}),
|
||||
"extra_descriptors": ("STRING", {"multiline": True, "default": "Model feature emphasis: complex & dynamic motion"}),
|
||||
"lighting_prompt": ("STRING", {"multiline": True, "default": ""}),
|
||||
"lighting_summary": ("STRING", {"multiline": True, "default": ""}),
|
||||
@@ -89,37 +55,22 @@ class NoxPromptBuilder(PresetMixin):
|
||||
motion_arc,
|
||||
narrative_hook="",
|
||||
model_emphasis="",
|
||||
light_source="Daylight",
|
||||
light_quality="Soft Light",
|
||||
time_of_day="Dusk",
|
||||
shot_size="Medium Close-up",
|
||||
composition="Balanced",
|
||||
lens_focal="Medium",
|
||||
lens_angle="None",
|
||||
lens_type="Single Shot",
|
||||
camera_basic="None",
|
||||
camera_advanced="None",
|
||||
color_tone="Warm Tone",
|
||||
motion_type="Running",
|
||||
emotion="Joy",
|
||||
visual_style="Anime",
|
||||
special_effect="None",
|
||||
aesthetic_palette="",
|
||||
dynamic_palette="",
|
||||
style_palette="",
|
||||
keyword_style="auto",
|
||||
quality_profile="Balanced",
|
||||
randomize_missing=False,
|
||||
random_seed=0,
|
||||
palette_overrides="",
|
||||
extra_descriptors="",
|
||||
lighting_prompt="",
|
||||
lighting_summary="",
|
||||
lighting_technical_notes="",
|
||||
lighting_technical_notes="",
|
||||
custom_keywords="",
|
||||
negative_prompt="",
|
||||
prompt_prefix="",
|
||||
prompt_suffix="",
|
||||
camera_prompt="",
|
||||
camera_summary="",
|
||||
camera_notes="",
|
||||
camera_prompt="",
|
||||
camera_summary="",
|
||||
camera_notes="",
|
||||
camera_directives="",
|
||||
prompt_filter_enabled=True,
|
||||
prompt_filter_profile="balanced",
|
||||
@@ -133,26 +84,11 @@ class NoxPromptBuilder(PresetMixin):
|
||||
"motion_arc": motion_arc,
|
||||
"narrative_hook": narrative_hook,
|
||||
"model_emphasis": model_emphasis,
|
||||
"light_source": light_source,
|
||||
"light_quality": light_quality,
|
||||
"time_of_day": time_of_day,
|
||||
"shot_size": shot_size,
|
||||
"composition": composition,
|
||||
"lens_focal": lens_focal,
|
||||
"lens_angle": lens_angle,
|
||||
"lens_type": lens_type,
|
||||
"camera_basic": camera_basic,
|
||||
"camera_advanced": camera_advanced,
|
||||
"color_tone": color_tone,
|
||||
"motion_type": motion_type,
|
||||
"emotion": emotion,
|
||||
"visual_style": visual_style,
|
||||
"special_effect": special_effect,
|
||||
"aesthetic_palette": aesthetic_palette,
|
||||
"dynamic_palette": dynamic_palette,
|
||||
"style_palette": style_palette,
|
||||
"keyword_style": keyword_style,
|
||||
"quality_profile": quality_profile,
|
||||
"randomize_missing": randomize_missing,
|
||||
"random_seed": random_seed,
|
||||
"palette_overrides": palette_overrides,
|
||||
"extra_descriptors": extra_descriptors,
|
||||
"lighting_prompt": lighting_prompt,
|
||||
"lighting_summary": lighting_summary,
|
||||
@@ -182,26 +118,11 @@ class NoxPromptBuilder(PresetMixin):
|
||||
motion_arc = config["motion_arc"]
|
||||
narrative_hook = config.get("narrative_hook", "")
|
||||
model_emphasis = config.get("model_emphasis", "")
|
||||
light_source = config.get("light_source", "Daylight")
|
||||
light_quality = config.get("light_quality", "Soft Light")
|
||||
time_of_day = config.get("time_of_day", "Dusk")
|
||||
shot_size = config.get("shot_size", "Medium Close-up")
|
||||
composition = config.get("composition", "Balanced")
|
||||
lens_focal = config.get("lens_focal", "Medium")
|
||||
lens_angle = config.get("lens_angle", "None")
|
||||
lens_type = config.get("lens_type", "Single Shot")
|
||||
camera_basic = config.get("camera_basic", "None")
|
||||
camera_advanced = config.get("camera_advanced", "None")
|
||||
color_tone = config.get("color_tone", "Warm Tone")
|
||||
motion_type = config.get("motion_type", "Running")
|
||||
emotion = config.get("emotion", "Joy")
|
||||
visual_style = config.get("visual_style", "Anime")
|
||||
special_effect = config.get("special_effect", "None")
|
||||
aesthetic_palette = config.get("aesthetic_palette", aesthetic_palette)
|
||||
dynamic_palette = config.get("dynamic_palette", dynamic_palette)
|
||||
style_palette = config.get("style_palette", style_palette)
|
||||
keyword_style = config.get("keyword_style", "auto")
|
||||
quality_profile = config.get("quality_profile", "Balanced")
|
||||
randomize_missing = bool(config.get("randomize_missing", False))
|
||||
random_seed = int(config.get("random_seed", 0))
|
||||
palette_overrides = config.get("palette_overrides", "")
|
||||
extra_descriptors = config.get("extra_descriptors", "")
|
||||
lighting_prompt = config.get("lighting_prompt", lighting_prompt)
|
||||
lighting_summary = config.get("lighting_summary", lighting_summary)
|
||||
@@ -218,8 +139,9 @@ class NoxPromptBuilder(PresetMixin):
|
||||
prompt_filter_profile = config.get("prompt_filter_profile", "balanced")
|
||||
|
||||
formula_config = FORMULA_CONFIGS.get(formula, next(iter(FORMULA_CONFIGS.values())))
|
||||
overrides_map, override_custom = self._parse_palette_overrides(palette_overrides)
|
||||
rng = random.Random(random_seed) if randomize_missing else None
|
||||
aesthetic_prompts, aesthetic_summary = self._parse_palette(aesthetic_palette)
|
||||
dynamic_prompts, dynamic_summary = self._parse_palette(dynamic_palette)
|
||||
style_prompts, style_summary = self._parse_palette(style_palette)
|
||||
|
||||
prompt_fragments = []
|
||||
|
||||
@@ -252,45 +174,6 @@ class NoxPromptBuilder(PresetMixin):
|
||||
if camera_prompt.strip():
|
||||
prompt_fragments.append(camera_prompt.strip())
|
||||
|
||||
aesthetic_prompts, aesthetic_summaries = self._collect_category_details(
|
||||
[
|
||||
("light_source", light_source, LIGHT_SOURCE_OPTIONS),
|
||||
("light_quality", light_quality, LIGHT_QUALITY_OPTIONS),
|
||||
("time_of_day", time_of_day, TIME_OF_DAY_OPTIONS),
|
||||
("shot_size", shot_size, SHOT_SIZE_OPTIONS),
|
||||
("composition", composition, COMPOSITION_OPTIONS),
|
||||
("lens_focal", lens_focal, LENS_FOCAL_OPTIONS),
|
||||
("lens_angle", lens_angle, LENS_ANGLE_OPTIONS),
|
||||
("lens_type", lens_type, LENS_TYPE_OPTIONS),
|
||||
("color_tone", color_tone, COLOR_TONE_OPTIONS),
|
||||
],
|
||||
overrides_map,
|
||||
rng=rng,
|
||||
randomize_missing=randomize_missing,
|
||||
)
|
||||
|
||||
dynamic_prompts, dynamic_summaries = self._collect_category_details(
|
||||
[
|
||||
("motion_type", motion_type, MOTION_TYPE_OPTIONS),
|
||||
("emotion", emotion, EMOTION_OPTIONS),
|
||||
("camera_basic", camera_basic, CAMERA_BASIC_OPTIONS),
|
||||
("camera_advanced", camera_advanced, CAMERA_ADVANCED_OPTIONS),
|
||||
],
|
||||
overrides_map,
|
||||
rng=rng,
|
||||
randomize_missing=randomize_missing,
|
||||
)
|
||||
|
||||
style_prompts, style_summaries = self._collect_category_details(
|
||||
[
|
||||
("visual_style", visual_style, VISUAL_STYLE_OPTIONS),
|
||||
("special_effect", special_effect, SPECIAL_EFFECT_OPTIONS),
|
||||
],
|
||||
overrides_map,
|
||||
rng=rng,
|
||||
randomize_missing=randomize_missing,
|
||||
)
|
||||
|
||||
keyword_mode = (keyword_style or "auto").lower()
|
||||
structure = formula_config.get("structure", [])
|
||||
|
||||
@@ -303,13 +186,12 @@ class NoxPromptBuilder(PresetMixin):
|
||||
prompt_fragments.append(chunk)
|
||||
else:
|
||||
if "aesthetic" in structure and aesthetic_prompts:
|
||||
prompt_fragments.append(f"Cinematic palette: {', '.join(aesthetic_prompts)}")
|
||||
prompt_fragments.append(f"Aesthetic palette: {', '.join(aesthetic_prompts)}")
|
||||
elif aesthetic_prompts:
|
||||
prompt_fragments.extend(aesthetic_prompts)
|
||||
|
||||
if dynamic_prompts:
|
||||
prompt_fragments.append(f"Motion dynamics: {', '.join(dynamic_prompts)}")
|
||||
|
||||
prompt_fragments.append(f"Dynamic palette: {', '.join(dynamic_prompts)}")
|
||||
|
||||
if "stylization" in structure and style_prompts:
|
||||
prompt_fragments.append(f"Stylization: {', '.join(style_prompts)}")
|
||||
@@ -321,8 +203,6 @@ class NoxPromptBuilder(PresetMixin):
|
||||
prompt_fragments.append(f"Camera direction: {camera_block}")
|
||||
|
||||
combined_custom = self._split_keywords(custom_keywords)
|
||||
if override_custom:
|
||||
combined_custom.extend(override_custom)
|
||||
if combined_custom:
|
||||
combined_custom = list(dict.fromkeys(filter(None, (item.strip() for item in combined_custom))))
|
||||
if combined_custom:
|
||||
@@ -355,15 +235,20 @@ class NoxPromptBuilder(PresetMixin):
|
||||
prompt_text = self._assemble_prompt(prompt_fragments)
|
||||
neg_text = negative_prompt.strip()
|
||||
|
||||
lighting_summary = lighting_summary.strip()
|
||||
aesthetic_sections = []
|
||||
if lighting_summary:
|
||||
aesthetic_sections.append(lighting_summary)
|
||||
aesthetic_sections.extend(aesthetic_summaries)
|
||||
aesthetic_sections.extend(style_summaries)
|
||||
if lighting_summary.strip():
|
||||
aesthetic_sections.append(lighting_summary.strip())
|
||||
if aesthetic_summary:
|
||||
aesthetic_sections.append(f"Aesthetic palette: {aesthetic_summary}")
|
||||
if style_summary:
|
||||
aesthetic_sections.append(f"Stylization: {style_summary}")
|
||||
aesthetic_notes = " | ".join(filter(None, aesthetic_sections))
|
||||
|
||||
dynamic_notes_sources = list(dynamic_summaries)
|
||||
dynamic_notes_sources = []
|
||||
if dynamic_summary:
|
||||
dynamic_notes_sources.append(f"Dynamic palette: {dynamic_summary}")
|
||||
if quality_profile:
|
||||
dynamic_notes_sources.append(f"Quality profile: {quality_profile}")
|
||||
if camera_summary.strip():
|
||||
dynamic_notes_sources.append(camera_summary.strip())
|
||||
if camera_notes.strip():
|
||||
@@ -378,140 +263,31 @@ class NoxPromptBuilder(PresetMixin):
|
||||
trimmed = trimmed[1:].strip()
|
||||
if trimmed:
|
||||
note_lines.append(trimmed)
|
||||
if len(note_lines) > 1:
|
||||
dynamic_notes_sources.extend(note_lines)
|
||||
else:
|
||||
if note_lines:
|
||||
dynamic_notes_sources.extend(note_lines)
|
||||
if camera_block:
|
||||
dynamic_notes_sources.append(f"Camera: {camera_block}")
|
||||
if lighting_technical_notes.strip():
|
||||
tech_lines = [line.strip() for line in lighting_technical_notes.splitlines() if line.strip()]
|
||||
if len(tech_lines) > 1:
|
||||
if tech_lines:
|
||||
dynamic_notes_sources.extend(tech_lines)
|
||||
else:
|
||||
dynamic_notes_sources.append(lighting_technical_notes.strip())
|
||||
if filter_summary:
|
||||
dynamic_notes_sources.append(filter_summary)
|
||||
|
||||
dynamic_notes = " | ".join(filter(None, dynamic_notes_sources))
|
||||
|
||||
return (prompt_text, neg_text, aesthetic_notes, dynamic_notes, preset_status)
|
||||
|
||||
def _collect_category_details(self, selections, overrides, rng=None, randomize_missing=False):
|
||||
prompts = []
|
||||
summaries = []
|
||||
overrides = overrides or {}
|
||||
for key, label, options in selections:
|
||||
tokens = self._expand_selection(label)
|
||||
override_tokens = overrides.get(key, [])
|
||||
if override_tokens:
|
||||
tokens.extend(override_tokens)
|
||||
if not tokens:
|
||||
tokens.append(label)
|
||||
|
||||
for token in tokens:
|
||||
option = self._resolve_option(token, options, rng=rng, randomize_missing=randomize_missing)
|
||||
prompt_text = option.get("prompt", "").strip()
|
||||
summary_text = option.get("summary", "").strip()
|
||||
if prompt_text:
|
||||
prompts.append(prompt_text)
|
||||
if summary_text:
|
||||
summaries.append(summary_text)
|
||||
|
||||
prompts = list(dict.fromkeys(filter(None, prompts)))
|
||||
summaries = list(dict.fromkeys(filter(None, summaries)))
|
||||
return prompts, summaries
|
||||
|
||||
def _resolve_option(self, label, options, rng=None, randomize_missing=False):
|
||||
normalized = (label or "").strip()
|
||||
if normalized:
|
||||
if normalized.lower() == "none":
|
||||
normalized = ""
|
||||
else:
|
||||
lower_map = {name.lower(): name for name in options.keys()}
|
||||
lookup = lower_map.get(normalized.lower())
|
||||
if lookup:
|
||||
return options.get(lookup, options.get("None", {"prompt": "", "summary": ""}))
|
||||
if randomize_missing and rng:
|
||||
candidates = [name for name in options if name.lower() != "none" and options[name].get("prompt")]
|
||||
if candidates:
|
||||
choice = rng.choice(candidates)
|
||||
return options[choice]
|
||||
return options.get("None", {"prompt": "", "summary": ""})
|
||||
|
||||
def _expand_selection(self, value):
|
||||
if not value:
|
||||
return []
|
||||
tokens = [token.strip() for token in re.split(r'[|/,]', value) if token.strip()]
|
||||
return tokens
|
||||
|
||||
def _parse_palette_overrides(self, text):
|
||||
if not text or not text.strip():
|
||||
return {}, []
|
||||
|
||||
overrides = {}
|
||||
custom_chunks = []
|
||||
mapping = self._palette_alias_map()
|
||||
|
||||
for line in text.splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
if ":" in line:
|
||||
key_part, values_part = line.split(":", 1)
|
||||
canonical = mapping.get(self._normalize_token(key_part))
|
||||
values = self._split_keywords(values_part)
|
||||
if canonical and values:
|
||||
overrides.setdefault(canonical, []).extend(values)
|
||||
elif values:
|
||||
custom_chunks.extend(values)
|
||||
else:
|
||||
custom_chunks.extend(self._split_keywords(line))
|
||||
|
||||
for key in list(overrides.keys()):
|
||||
overrides[key] = list(dict.fromkeys(overrides[key]))
|
||||
|
||||
custom_chunks = list(dict.fromkeys(custom_chunks))
|
||||
return overrides, custom_chunks
|
||||
|
||||
def _palette_alias_map(self):
|
||||
return {
|
||||
"light source": "light_source",
|
||||
"light sources": "light_source",
|
||||
"lighting": "light_source",
|
||||
"light quality": "light_quality",
|
||||
"light qualities": "light_quality",
|
||||
"time of day": "time_of_day",
|
||||
"time": "time_of_day",
|
||||
"shot size": "shot_size",
|
||||
"composition": "composition",
|
||||
"lens focal": "lens_focal",
|
||||
"focal length": "lens_focal",
|
||||
"lens angle": "lens_angle",
|
||||
"camera angle": "lens_angle",
|
||||
"lens type": "lens_type",
|
||||
"shot type": "lens_type",
|
||||
"camera basic": "camera_basic",
|
||||
"basic camera": "camera_basic",
|
||||
"camera advanced": "camera_advanced",
|
||||
"advanced camera": "camera_advanced",
|
||||
"color tone": "color_tone",
|
||||
"color": "color_tone",
|
||||
"motion type": "motion_type",
|
||||
"motion": "motion_type",
|
||||
"emotion": "emotion",
|
||||
"visual style": "visual_style",
|
||||
"style": "visual_style",
|
||||
"special effect": "special_effect",
|
||||
"effects": "special_effect",
|
||||
}
|
||||
|
||||
def _normalize_token(self, token):
|
||||
return token.strip().lower().replace("_", " ")
|
||||
def _parse_palette(self, text):
|
||||
tokens = self._split_keywords(text)
|
||||
tokens = list(dict.fromkeys(tokens))
|
||||
summary = ", ".join(tokens) if tokens else ""
|
||||
return tokens, summary
|
||||
|
||||
def _split_keywords(self, text):
|
||||
if not text:
|
||||
return []
|
||||
separators = [",", "\n", ";"]
|
||||
separators = [",", "\n", ";", "|"]
|
||||
tokens = [text]
|
||||
for sep in separators:
|
||||
temp = []
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict, List
|
||||
|
||||
from ..common import PresetMixin, option_keys
|
||||
from ..constants import (
|
||||
CAMERA_TYPES,
|
||||
FSTOP_OPTIONS,
|
||||
ISO_OPTIONS,
|
||||
LENS_FOCAL_LENGTHS,
|
||||
SHUTTER_SPEED_OPTIONS,
|
||||
WHITE_BALANCE_OPTIONS,
|
||||
)
|
||||
|
||||
|
||||
class NoxPromptCameraSelector(PresetMixin):
|
||||
"""Select and combine camera technical settings into a prompt."""
|
||||
|
||||
CATEGORY = "NoxPrompter/Camera"
|
||||
FUNCTION = "select_camera"
|
||||
RETURN_TYPES = ("STRING", "STRING")
|
||||
RETURN_NAMES = ("camera_prompt", "preset_status")
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"custom_prompt": (
|
||||
"STRING",
|
||||
{
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
},
|
||||
),
|
||||
"camera_type": (
|
||||
option_keys(CAMERA_TYPES),
|
||||
{"default": "DSLR"},
|
||||
),
|
||||
},
|
||||
"optional": {
|
||||
"iso": (
|
||||
option_keys(ISO_OPTIONS),
|
||||
{"default": "100"},
|
||||
),
|
||||
"fstop": (
|
||||
option_keys(FSTOP_OPTIONS),
|
||||
{"default": "f/2.8"},
|
||||
),
|
||||
"shutter_speed": (
|
||||
option_keys(SHUTTER_SPEED_OPTIONS),
|
||||
{"default": "1/125"},
|
||||
),
|
||||
"lens_focal_length": (
|
||||
option_keys(LENS_FOCAL_LENGTHS),
|
||||
{"default": "50mm"},
|
||||
),
|
||||
"white_balance": (
|
||||
option_keys(WHITE_BALANCE_OPTIONS),
|
||||
{"default": "Auto"},
|
||||
),
|
||||
"camera_type_custom": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"iso_custom": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"fstop_custom": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"shutter_speed_custom": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"lens_focal_length_custom": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"white_balance_custom": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"preset_action": (
|
||||
["none", "save", "load", "list"],
|
||||
{"default": "none"},
|
||||
),
|
||||
"preset_name": (
|
||||
"STRING",
|
||||
{"default": ""},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
def select_camera(
|
||||
self,
|
||||
custom_prompt: str,
|
||||
camera_type: str,
|
||||
iso: str = "100",
|
||||
fstop: str = "f/2.8",
|
||||
shutter_speed: str = "1/125",
|
||||
lens_focal_length: str = "50mm",
|
||||
white_balance: str = "Auto",
|
||||
camera_type_custom: str = "",
|
||||
iso_custom: str = "",
|
||||
fstop_custom: str = "",
|
||||
shutter_speed_custom: str = "",
|
||||
lens_focal_length_custom: str = "",
|
||||
white_balance_custom: str = "",
|
||||
preset_action: str = "none",
|
||||
preset_name: str = "",
|
||||
):
|
||||
config = {
|
||||
"custom_prompt": custom_prompt,
|
||||
"camera_type": camera_type,
|
||||
"iso": iso,
|
||||
"fstop": fstop,
|
||||
"shutter_speed": shutter_speed,
|
||||
"lens_focal_length": lens_focal_length,
|
||||
"white_balance": white_balance,
|
||||
"camera_type_custom": camera_type_custom,
|
||||
"iso_custom": iso_custom,
|
||||
"fstop_custom": fstop_custom,
|
||||
"shutter_speed_custom": shutter_speed_custom,
|
||||
"lens_focal_length_custom": lens_focal_length_custom,
|
||||
"white_balance_custom": white_balance_custom,
|
||||
}
|
||||
|
||||
config, preset_status = self._apply_preset_action(
|
||||
"camera_selector",
|
||||
preset_action,
|
||||
preset_name,
|
||||
config,
|
||||
)
|
||||
|
||||
custom_prompt = config.get("custom_prompt", custom_prompt)
|
||||
camera_type = config.get("camera_type", camera_type)
|
||||
iso = config.get("iso", iso)
|
||||
fstop = config.get("fstop", fstop)
|
||||
shutter_speed = config.get("shutter_speed", shutter_speed)
|
||||
lens_focal_length = config.get("lens_focal_length", lens_focal_length)
|
||||
white_balance = config.get("white_balance", white_balance)
|
||||
camera_type_custom = config.get("camera_type_custom", camera_type_custom)
|
||||
iso_custom = config.get("iso_custom", iso_custom)
|
||||
fstop_custom = config.get("fstop_custom", fstop_custom)
|
||||
shutter_speed_custom = config.get("shutter_speed_custom", shutter_speed_custom)
|
||||
lens_focal_length_custom = config.get("lens_focal_length_custom", lens_focal_length_custom)
|
||||
white_balance_custom = config.get("white_balance_custom", white_balance_custom)
|
||||
|
||||
camera_cfg = self._resolve_option(camera_type, camera_type_custom, CAMERA_TYPES)
|
||||
iso_cfg = self._resolve_option(iso, iso_custom, ISO_OPTIONS)
|
||||
fstop_cfg = self._resolve_option(fstop, fstop_custom, FSTOP_OPTIONS)
|
||||
shutter_cfg = self._resolve_option(shutter_speed, shutter_speed_custom, SHUTTER_SPEED_OPTIONS)
|
||||
lens_cfg = self._resolve_option(lens_focal_length, lens_focal_length_custom, LENS_FOCAL_LENGTHS)
|
||||
wb_cfg = self._resolve_option(white_balance, white_balance_custom, WHITE_BALANCE_OPTIONS)
|
||||
|
||||
prompt_fragments: List[str] = []
|
||||
if custom_prompt.strip():
|
||||
prompt_fragments.append(custom_prompt.strip())
|
||||
if camera_cfg:
|
||||
prompt_fragments.append(camera_cfg)
|
||||
if iso_cfg:
|
||||
prompt_fragments.append(iso_cfg)
|
||||
if fstop_cfg:
|
||||
prompt_fragments.append(fstop_cfg)
|
||||
if shutter_cfg:
|
||||
prompt_fragments.append(shutter_cfg)
|
||||
if lens_cfg:
|
||||
prompt_fragments.append(lens_cfg)
|
||||
if wb_cfg:
|
||||
prompt_fragments.append(wb_cfg)
|
||||
|
||||
camera_prompt = ", ".join(fragment for fragment in prompt_fragments if fragment)
|
||||
if camera_prompt and not camera_prompt.endswith("."):
|
||||
camera_prompt += "."
|
||||
|
||||
return camera_prompt, preset_status
|
||||
|
||||
def _resolve_option(self, selection: str, custom: str, options: Dict[str, str]) -> str:
|
||||
if selection == "Custom":
|
||||
return custom.strip()
|
||||
return options.get(selection, "").strip()
|
||||
|
||||
|
||||
__all__ = ["NoxPromptCameraSelector"]
|
||||
@@ -1,8 +1,19 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Tuple
|
||||
from ..common import PresetMixin, _resolve_with_custom, _split_tokens
|
||||
from ..constants import * # noqa: F403
|
||||
from ..common import PresetMixin, _resolve_option, option_keys, _split_tokens
|
||||
from ..constants import (
|
||||
CHARACTER_ALIGNMENT_OPTIONS,
|
||||
CHARACTER_ARCHETYPE_OPTIONS,
|
||||
CHARACTER_BODY_TYPE_OPTIONS,
|
||||
CHARACTER_ERA_OPTIONS,
|
||||
CHARACTER_POWER_SOURCE_OPTIONS,
|
||||
CHARACTER_PROFESSION_OPTIONS,
|
||||
CHARACTER_ROLE_OPTIONS,
|
||||
CHARACTER_SIGNATURE_GEAR_OPTIONS,
|
||||
CHARACTER_SPECIES_OPTIONS,
|
||||
CHARACTER_TEMPERAMENT_OPTIONS,
|
||||
CHARACTER_VISUAL_MOTIF_OPTIONS,
|
||||
)
|
||||
|
||||
class NoxPromptCharacterCreator(PresetMixin):
|
||||
"""Build richly-detailed character prompts and quick-reference sheets."""
|
||||
@@ -11,36 +22,65 @@ class NoxPromptCharacterCreator(PresetMixin):
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"species": (list(CHARACTER_SPECIES_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Human"}),
|
||||
"archetype": (list(CHARACTER_ARCHETYPE_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Champion"}),
|
||||
"profession": (list(CHARACTER_PROFESSION_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Starship Captain"}),
|
||||
"custom_prompt": (
|
||||
"STRING",
|
||||
{
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
},
|
||||
),
|
||||
"species": (
|
||||
option_keys(CHARACTER_SPECIES_OPTIONS),
|
||||
{"default": "Human"},
|
||||
),
|
||||
"archetype": (
|
||||
option_keys(CHARACTER_ARCHETYPE_OPTIONS),
|
||||
{"default": "Champion"},
|
||||
),
|
||||
"profession": (
|
||||
option_keys(CHARACTER_PROFESSION_OPTIONS),
|
||||
{"default": "Starship Captain"},
|
||||
),
|
||||
},
|
||||
"optional": {
|
||||
"temperament": (list(CHARACTER_TEMPERAMENT_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Stoic"}),
|
||||
"power_source": (list(CHARACTER_POWER_SOURCE_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Arcane Relic"}),
|
||||
"signature_gear": (list(CHARACTER_SIGNATURE_GEAR_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Living Blade"}),
|
||||
"visual_motif": (list(CHARACTER_VISUAL_MOTIF_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Solar Flare"}),
|
||||
"era": (list(CHARACTER_ERA_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Mythic Age"}),
|
||||
"body_type": (list(CHARACTER_BODY_TYPE_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Athletic"}),
|
||||
"alignment": (list(CHARACTER_ALIGNMENT_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Lawful Good"}),
|
||||
"team_role": (list(CHARACTER_ROLE_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Party Leader"}),
|
||||
"temperament": (
|
||||
option_keys(CHARACTER_TEMPERAMENT_OPTIONS),
|
||||
{"default": "Stoic"},
|
||||
),
|
||||
"power_source": (
|
||||
option_keys(CHARACTER_POWER_SOURCE_OPTIONS),
|
||||
{"default": "Arcane Relic"},
|
||||
),
|
||||
"signature_gear": (
|
||||
option_keys(CHARACTER_SIGNATURE_GEAR_OPTIONS),
|
||||
{"default": "Living Blade"},
|
||||
),
|
||||
"visual_motif": (
|
||||
option_keys(CHARACTER_VISUAL_MOTIF_OPTIONS),
|
||||
{"default": "Solar Flare"},
|
||||
),
|
||||
"era": (
|
||||
option_keys(CHARACTER_ERA_OPTIONS),
|
||||
{"default": "Mythic Age"},
|
||||
),
|
||||
"body_type": (
|
||||
option_keys(CHARACTER_BODY_TYPE_OPTIONS),
|
||||
{"default": "Athletic"},
|
||||
),
|
||||
"alignment": (
|
||||
option_keys(CHARACTER_ALIGNMENT_OPTIONS),
|
||||
{"default": "Lawful Good"},
|
||||
),
|
||||
"team_role": (
|
||||
option_keys(CHARACTER_ROLE_OPTIONS),
|
||||
{"default": "Party Leader"},
|
||||
),
|
||||
"catchphrase": ("STRING", {"default": ""}),
|
||||
"signature_move": ("STRING", {"default": ""}),
|
||||
"backstory_snippet": ("STRING", {"multiline": True, "default": ""}),
|
||||
"custom_traits": ("STRING", {"multiline": True, "default": ""}),
|
||||
"negative_prompt": ("STRING", {"multiline": True, "default": ""}),
|
||||
"presence_rating": ("FLOAT", {"default": 0.75, "min": 0.0, "max": 1.0, "step": 0.05}),
|
||||
"species_custom": ("STRING", {"default": ""}),
|
||||
"archetype_custom": ("STRING", {"default": ""}),
|
||||
"profession_custom": ("STRING", {"default": ""}),
|
||||
"temperament_custom": ("STRING", {"default": ""}),
|
||||
"power_source_custom": ("STRING", {"default": ""}),
|
||||
"signature_gear_custom": ("STRING", {"default": ""}),
|
||||
"visual_motif_custom": ("STRING", {"default": ""}),
|
||||
"era_custom": ("STRING", {"default": ""}),
|
||||
"body_type_custom": ("STRING", {"default": ""}),
|
||||
"alignment_custom": ("STRING", {"default": ""}),
|
||||
"team_role_custom": ("STRING", {"default": ""}),
|
||||
"preset_action": (["none", "save", "load", "list"], {"default": "none"}),
|
||||
"preset_name": ("STRING", {"default": ""}),
|
||||
},
|
||||
@@ -53,38 +93,29 @@ class NoxPromptCharacterCreator(PresetMixin):
|
||||
|
||||
def craft(
|
||||
self,
|
||||
species,
|
||||
archetype,
|
||||
profession,
|
||||
temperament="Stoic",
|
||||
power_source="Arcane Relic",
|
||||
signature_gear="Living Blade",
|
||||
visual_motif="Solar Flare",
|
||||
era="Mythic Age",
|
||||
body_type="Athletic",
|
||||
alignment="Lawful Good",
|
||||
team_role="Party Leader",
|
||||
species_custom="",
|
||||
archetype_custom="",
|
||||
profession_custom="",
|
||||
temperament_custom="",
|
||||
power_source_custom="",
|
||||
signature_gear_custom="",
|
||||
visual_motif_custom="",
|
||||
era_custom="",
|
||||
body_type_custom="",
|
||||
alignment_custom="",
|
||||
team_role_custom="",
|
||||
catchphrase="",
|
||||
signature_move="",
|
||||
backstory_snippet="",
|
||||
custom_traits="",
|
||||
negative_prompt="",
|
||||
presence_rating=0.75,
|
||||
preset_action="none",
|
||||
preset_name="",
|
||||
custom_prompt: str = "",
|
||||
species: str = "Human",
|
||||
archetype: str = "Champion",
|
||||
profession: str = "Starship Captain",
|
||||
temperament: str = "Stoic",
|
||||
power_source: str = "Arcane Relic",
|
||||
signature_gear: str = "Living Blade",
|
||||
visual_motif: str = "Solar Flare",
|
||||
era: str = "Mythic Age",
|
||||
body_type: str = "Athletic",
|
||||
alignment: str = "Lawful Good",
|
||||
team_role: str = "Party Leader",
|
||||
catchphrase: str = "",
|
||||
signature_move: str = "",
|
||||
backstory_snippet: str = "",
|
||||
custom_traits: str = "",
|
||||
negative_prompt: str = "",
|
||||
presence_rating: float = 0.75,
|
||||
preset_action: str = "none",
|
||||
preset_name: str = "",
|
||||
):
|
||||
config = {
|
||||
"custom_prompt": custom_prompt,
|
||||
"species": species,
|
||||
"archetype": archetype,
|
||||
"profession": profession,
|
||||
@@ -96,17 +127,6 @@ class NoxPromptCharacterCreator(PresetMixin):
|
||||
"body_type": body_type,
|
||||
"alignment": alignment,
|
||||
"team_role": team_role,
|
||||
"species_custom": species_custom,
|
||||
"archetype_custom": archetype_custom,
|
||||
"profession_custom": profession_custom,
|
||||
"temperament_custom": temperament_custom,
|
||||
"power_source_custom": power_source_custom,
|
||||
"signature_gear_custom": signature_gear_custom,
|
||||
"visual_motif_custom": visual_motif_custom,
|
||||
"era_custom": era_custom,
|
||||
"body_type_custom": body_type_custom,
|
||||
"alignment_custom": alignment_custom,
|
||||
"team_role_custom": team_role_custom,
|
||||
"catchphrase": catchphrase,
|
||||
"signature_move": signature_move,
|
||||
"backstory_snippet": backstory_snippet,
|
||||
@@ -122,6 +142,7 @@ class NoxPromptCharacterCreator(PresetMixin):
|
||||
config,
|
||||
)
|
||||
|
||||
custom_prompt = config.get("custom_prompt", custom_prompt)
|
||||
species = config.get("species", species)
|
||||
archetype = config.get("archetype", archetype)
|
||||
profession = config.get("profession", profession)
|
||||
@@ -133,56 +154,75 @@ class NoxPromptCharacterCreator(PresetMixin):
|
||||
body_type = config.get("body_type", body_type)
|
||||
alignment = config.get("alignment", alignment)
|
||||
team_role = config.get("team_role", team_role)
|
||||
species_custom = config.get("species_custom", species_custom)
|
||||
archetype_custom = config.get("archetype_custom", archetype_custom)
|
||||
profession_custom = config.get("profession_custom", profession_custom)
|
||||
temperament_custom = config.get("temperament_custom", temperament_custom)
|
||||
power_source_custom = config.get("power_source_custom", power_source_custom)
|
||||
signature_gear_custom = config.get("signature_gear_custom", signature_gear_custom)
|
||||
visual_motif_custom = config.get("visual_motif_custom", visual_motif_custom)
|
||||
era_custom = config.get("era_custom", era_custom)
|
||||
body_type_custom = config.get("body_type_custom", body_type_custom)
|
||||
alignment_custom = config.get("alignment_custom", alignment_custom)
|
||||
team_role_custom = config.get("team_role_custom", team_role_custom)
|
||||
catchphrase = config.get("catchphrase", catchphrase)
|
||||
signature_move = config.get("signature_move", signature_move)
|
||||
backstory_snippet = config.get("backstory_snippet", backstory_snippet)
|
||||
custom_traits = config.get("custom_traits", custom_traits)
|
||||
negative_prompt = config.get("negative_prompt", negative_prompt)
|
||||
presence_rating = float(config.get("presence_rating", presence_rating))
|
||||
species_prompt, species_notes = _resolve_with_custom(species, species_custom, CHARACTER_SPECIES_OPTIONS)
|
||||
archetype_prompt, archetype_notes = _resolve_with_custom(archetype, archetype_custom, CHARACTER_ARCHETYPE_OPTIONS)
|
||||
profession_prompt, profession_notes = _resolve_with_custom(profession, profession_custom, CHARACTER_PROFESSION_OPTIONS)
|
||||
temperament_prompt, temperament_notes = _resolve_with_custom(temperament, temperament_custom, CHARACTER_TEMPERAMENT_OPTIONS)
|
||||
power_prompt, power_notes = _resolve_with_custom(power_source, power_source_custom, CHARACTER_POWER_SOURCE_OPTIONS)
|
||||
gear_prompt, gear_notes = _resolve_with_custom(signature_gear, signature_gear_custom, CHARACTER_SIGNATURE_GEAR_OPTIONS)
|
||||
motif_prompt, motif_notes = _resolve_with_custom(visual_motif, visual_motif_custom, CHARACTER_VISUAL_MOTIF_OPTIONS)
|
||||
era_prompt, era_notes = _resolve_with_custom(era, era_custom, CHARACTER_ERA_OPTIONS)
|
||||
body_prompt, body_notes = _resolve_with_custom(body_type, body_type_custom, CHARACTER_BODY_TYPE_OPTIONS)
|
||||
alignment_prompt, alignment_notes = _resolve_with_custom(alignment, alignment_custom, CHARACTER_ALIGNMENT_OPTIONS)
|
||||
role_prompt, role_notes = _resolve_with_custom(team_role, team_role_custom, CHARACTER_ROLE_OPTIONS)
|
||||
species_prompt, species_notes = _resolve_option(species, CHARACTER_SPECIES_OPTIONS)
|
||||
archetype_prompt, archetype_notes = _resolve_option(archetype, CHARACTER_ARCHETYPE_OPTIONS)
|
||||
profession_prompt, profession_notes = _resolve_option(profession, CHARACTER_PROFESSION_OPTIONS)
|
||||
temperament_prompt, temperament_notes = _resolve_option(temperament, CHARACTER_TEMPERAMENT_OPTIONS)
|
||||
power_prompt, power_notes = _resolve_option(power_source, CHARACTER_POWER_SOURCE_OPTIONS)
|
||||
gear_prompt, gear_notes = _resolve_option(signature_gear, CHARACTER_SIGNATURE_GEAR_OPTIONS)
|
||||
motif_prompt, motif_notes = _resolve_option(visual_motif, CHARACTER_VISUAL_MOTIF_OPTIONS)
|
||||
era_prompt, era_notes = _resolve_option(era, CHARACTER_ERA_OPTIONS)
|
||||
body_prompt, body_notes = _resolve_option(body_type, CHARACTER_BODY_TYPE_OPTIONS)
|
||||
alignment_prompt, alignment_notes = _resolve_option(alignment, CHARACTER_ALIGNMENT_OPTIONS)
|
||||
role_prompt, role_notes = _resolve_option(team_role, CHARACTER_ROLE_OPTIONS)
|
||||
|
||||
def _fallback(selection: str, prompt: str) -> str:
|
||||
cleaned_prompt = (prompt or "").strip()
|
||||
if cleaned_prompt:
|
||||
return cleaned_prompt
|
||||
selection_text = (selection or "").strip()
|
||||
if selection_text.lower() == "none":
|
||||
return ""
|
||||
return selection_text
|
||||
|
||||
species_text = _fallback(species, species_prompt)
|
||||
archetype_text = _fallback(archetype, archetype_prompt)
|
||||
profession_text = _fallback(profession, profession_prompt)
|
||||
temperament_text = _fallback(temperament, temperament_prompt)
|
||||
power_text = _fallback(power_source, power_prompt)
|
||||
gear_text = _fallback(signature_gear, gear_prompt)
|
||||
motif_text = _fallback(visual_motif, motif_prompt)
|
||||
era_text = _fallback(era, era_prompt)
|
||||
body_text = _fallback(body_type, body_prompt)
|
||||
alignment_text = _fallback(alignment, alignment_prompt)
|
||||
role_text = _fallback(team_role, role_prompt)
|
||||
|
||||
traits = _split_tokens(custom_traits)
|
||||
trait_prompt = ", ".join(traits)
|
||||
|
||||
presence_descriptor = self._describe_presence(presence_rating)
|
||||
|
||||
prompt_parts = [
|
||||
species_prompt,
|
||||
body_prompt,
|
||||
archetype_prompt,
|
||||
profession_prompt,
|
||||
temperament_prompt,
|
||||
power_prompt,
|
||||
motif_prompt,
|
||||
gear_prompt,
|
||||
alignment_prompt,
|
||||
role_prompt,
|
||||
f"signature move: {signature_move.strip()}" if signature_move.strip() else "",
|
||||
f"operates during {era_prompt}" if era_prompt else "",
|
||||
trait_prompt,
|
||||
f"presence reads as {presence_descriptor}",
|
||||
]
|
||||
custom_fragment = (custom_prompt or "").strip()
|
||||
|
||||
prompt_parts = [custom_fragment] if custom_fragment else []
|
||||
prompt_parts.extend(
|
||||
[
|
||||
species_text,
|
||||
body_text,
|
||||
archetype_text,
|
||||
profession_text,
|
||||
temperament_text,
|
||||
power_text,
|
||||
motif_text,
|
||||
gear_text,
|
||||
alignment_text,
|
||||
role_text,
|
||||
]
|
||||
)
|
||||
prompt_parts.extend(
|
||||
[
|
||||
f"signature move: {signature_move.strip()}" if signature_move.strip() else "",
|
||||
f"operates during {era_text}" if era_text else "",
|
||||
trait_prompt,
|
||||
f"presence reads as {presence_descriptor}",
|
||||
]
|
||||
)
|
||||
|
||||
character_prompt = ", ".join(part for part in prompt_parts if part).strip()
|
||||
if backstory_snippet.strip():
|
||||
@@ -191,17 +231,17 @@ class NoxPromptCharacterCreator(PresetMixin):
|
||||
character_prompt += "."
|
||||
|
||||
sheet_lines = [
|
||||
f"Species Notes: {species_notes}" if species_notes else "",
|
||||
f"Archetype Notes: {archetype_notes}" if archetype_notes else "",
|
||||
f"Profession Notes: {profession_notes}" if profession_notes else "",
|
||||
f"Temperament: {temperament_notes}" if temperament_notes else "",
|
||||
f"Power Source: {power_notes}" if power_notes else "",
|
||||
f"Signature Gear: {gear_notes}" if gear_notes else "",
|
||||
f"Visual Motif: {motif_notes}" if motif_notes else "",
|
||||
f"Era Context: {era_notes}" if era_notes else "",
|
||||
f"Body Type: {body_notes}" if body_notes else "",
|
||||
f"Alignment: {alignment_notes}" if alignment_notes else "",
|
||||
f"Team Role: {role_notes}" if role_notes else "",
|
||||
f"Species Notes: {species_notes}" if species_notes else (f"Species: {species_text}" if species_text else ""),
|
||||
f"Archetype Notes: {archetype_notes}" if archetype_notes else (f"Archetype: {archetype_text}" if archetype_text else ""),
|
||||
f"Profession Notes: {profession_notes}" if profession_notes else (f"Profession: {profession_text}" if profession_text else ""),
|
||||
f"Temperament: {temperament_notes}" if temperament_notes else (f"Temperament: {temperament_text}" if temperament_text else ""),
|
||||
f"Power Source: {power_notes}" if power_notes else (f"Power Source: {power_text}" if power_text else ""),
|
||||
f"Signature Gear: {gear_notes}" if gear_notes else (f"Signature Gear: {gear_text}" if gear_text else ""),
|
||||
f"Visual Motif: {motif_notes}" if motif_notes else (f"Visual Motif: {motif_text}" if motif_text else ""),
|
||||
f"Era Context: {era_notes}" if era_notes else (f"Era: {era_text}" if era_text else ""),
|
||||
f"Body Type: {body_notes}" if body_notes else (f"Body Type: {body_text}" if body_text else ""),
|
||||
f"Alignment: {alignment_notes}" if alignment_notes else (f"Alignment: {alignment_text}" if alignment_text else ""),
|
||||
f"Team Role: {role_notes}" if role_notes else (f"Team Role: {role_text}" if role_text else ""),
|
||||
]
|
||||
if traits:
|
||||
sheet_lines.append("Distinct Traits: " + ", ".join(traits))
|
||||
@@ -211,8 +251,8 @@ class NoxPromptCharacterCreator(PresetMixin):
|
||||
hooks = [
|
||||
f"Catchphrase: \"{catchphrase.strip()}\"" if catchphrase.strip() else "",
|
||||
f"Signature Move: {signature_move.strip()}" if signature_move.strip() else "",
|
||||
f"Team Role: {team_role}" if team_role else "",
|
||||
f"Alignment: {alignment}" if alignment else "",
|
||||
f"Team Role: {role_text}" if role_text else "",
|
||||
f"Alignment: {alignment_text}" if alignment_text else "",
|
||||
f"Presence Rating: {presence_rating:.2f} ({presence_descriptor})",
|
||||
]
|
||||
if negative_prompt.strip():
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ..common import PresetMixin, _resolve_with_custom
|
||||
from ..common import PresetMixin, _resolve_option, option_keys
|
||||
from ..constants import (
|
||||
CUSTOM_OPTION,
|
||||
LIGHT_SOURCE_OPTIONS,
|
||||
LIGHT_QUALITY_OPTIONS,
|
||||
LIGHTING_ATMOSPHERE_OPTIONS,
|
||||
LIGHTING_BACKLIGHT_OPTIONS,
|
||||
LIGHTING_BLUEPRINT_OPTIONS,
|
||||
LIGHTING_CAM_SETTINGS,
|
||||
LIGHTING_COLOR_GEL_OPTIONS,
|
||||
LIGHTING_ENERGY_LEVEL_OPTIONS,
|
||||
LIGHTING_FILL_STYLE_OPTIONS,
|
||||
@@ -26,40 +24,72 @@ class NoxPromptLightingMaster(PresetMixin):
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"blueprint": (list(LIGHTING_BLUEPRINT_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Three-Point"}),
|
||||
"key_style": (list(LIGHTING_KEY_STYLE_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Softbox Key"}),
|
||||
"custom_prompt": (
|
||||
"STRING",
|
||||
{
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
},
|
||||
),
|
||||
"blueprint": (
|
||||
option_keys(LIGHTING_BLUEPRINT_OPTIONS),
|
||||
{"default": "Three-Point"},
|
||||
),
|
||||
"key_style": (
|
||||
option_keys(LIGHTING_KEY_STYLE_OPTIONS),
|
||||
{"default": "Softbox Key"},
|
||||
),
|
||||
},
|
||||
"optional": {
|
||||
"light_source": (list(LIGHT_SOURCE_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Daylight"}),
|
||||
"light_quality": (list(LIGHT_QUALITY_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Soft Light"}),
|
||||
"time_of_day": (list(TIME_OF_DAY_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Dusk"}),
|
||||
"fill_style": (list(LIGHTING_FILL_STYLE_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Soft Fill"}),
|
||||
"backlight": (list(LIGHTING_BACKLIGHT_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Rim Strip"}),
|
||||
"practical": (list(LIGHTING_PRACTICAL_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Lantern Cluster"}),
|
||||
"atmosphere": (list(LIGHTING_ATMOSPHERE_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Haze"}),
|
||||
"color_gel": (list(LIGHTING_COLOR_GEL_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Teal & Orange"}),
|
||||
"camera_settings": (list(LIGHTING_CAM_SETTINGS.keys()) + [CUSTOM_OPTION], {"default": "None"}),
|
||||
"special_technique": (list(LIGHTING_SPECIAL_TECHNIQUES.keys()) + [CUSTOM_OPTION], {"default": "Shutter Drag"}),
|
||||
"energy_level": (list(LIGHTING_ENERGY_LEVEL_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Dynamic"}),
|
||||
"safety_profile": (list(["None"] + list(LIGHTING_SAFETY_NOTES.keys()) + [CUSTOM_OPTION]), {"default": "None"}),
|
||||
"light_source": (
|
||||
option_keys(LIGHT_SOURCE_OPTIONS),
|
||||
{"default": "Daylight"},
|
||||
),
|
||||
"light_quality": (
|
||||
option_keys(LIGHT_QUALITY_OPTIONS),
|
||||
{"default": "Soft Light"},
|
||||
),
|
||||
"time_of_day": (
|
||||
option_keys(TIME_OF_DAY_OPTIONS),
|
||||
{"default": "Dusk"},
|
||||
),
|
||||
"fill_style": (
|
||||
option_keys(LIGHTING_FILL_STYLE_OPTIONS),
|
||||
{"default": "Soft Fill"},
|
||||
),
|
||||
"backlight": (
|
||||
option_keys(LIGHTING_BACKLIGHT_OPTIONS),
|
||||
{"default": "Rim Strip"},
|
||||
),
|
||||
"practical": (
|
||||
option_keys(LIGHTING_PRACTICAL_OPTIONS),
|
||||
{"default": "Lantern Cluster"},
|
||||
),
|
||||
"atmosphere": (
|
||||
option_keys(LIGHTING_ATMOSPHERE_OPTIONS),
|
||||
{"default": "Haze"},
|
||||
),
|
||||
"color_gel": (
|
||||
option_keys(LIGHTING_COLOR_GEL_OPTIONS),
|
||||
{"default": "Teal & Orange"},
|
||||
),
|
||||
"special_technique": (
|
||||
option_keys(LIGHTING_SPECIAL_TECHNIQUES),
|
||||
{"default": "Shutter Drag"},
|
||||
),
|
||||
"energy_level": (
|
||||
option_keys(LIGHTING_ENERGY_LEVEL_OPTIONS),
|
||||
{"default": "Dynamic"},
|
||||
),
|
||||
"safety_profile": (
|
||||
option_keys(LIGHTING_SAFETY_NOTES),
|
||||
{"default": "None"},
|
||||
),
|
||||
"subject_description": ("STRING", {"multiline": True, "default": ""}),
|
||||
"environment_description": ("STRING", {"multiline": True, "default": ""}),
|
||||
"accent_notes": ("STRING", {"multiline": True, "default": ""}),
|
||||
"camera_notes": ("STRING", {"multiline": True, "default": ""}),
|
||||
"intensity_bias": ("FLOAT", {"default": 0.6, "min": 0.0, "max": 1.0, "step": 0.05}),
|
||||
"blueprint_custom": ("STRING", {"default": ""}),
|
||||
"key_style_custom": ("STRING", {"default": ""}),
|
||||
"light_source_custom": ("STRING", {"default": ""}),
|
||||
"light_quality_custom": ("STRING", {"default": ""}),
|
||||
"time_of_day_custom": ("STRING", {"default": ""}),
|
||||
"fill_style_custom": ("STRING", {"default": ""}),
|
||||
"backlight_custom": ("STRING", {"default": ""}),
|
||||
"practical_custom": ("STRING", {"default": ""}),
|
||||
"atmosphere_custom": ("STRING", {"default": ""}),
|
||||
"color_gel_custom": ("STRING", {"default": ""}),
|
||||
"camera_settings_custom": ("STRING", {"default": ""}),
|
||||
"special_technique_custom": ("STRING", {"default": ""}),
|
||||
"energy_level_custom": ("STRING", {"default": ""}),
|
||||
"safety_profile_custom": ("STRING", {"default": ""}),
|
||||
"preset_action": (["none", "save", "load", "list"], {"default": "none"}),
|
||||
"preset_name": ("STRING", {"default": ""}),
|
||||
},
|
||||
@@ -72,42 +102,30 @@ class NoxPromptLightingMaster(PresetMixin):
|
||||
|
||||
def illuminate(
|
||||
self,
|
||||
blueprint,
|
||||
key_style,
|
||||
light_source="Daylight",
|
||||
light_quality="Soft Light",
|
||||
time_of_day="Dusk",
|
||||
fill_style="Soft Fill",
|
||||
backlight="Rim Strip",
|
||||
practical="Lantern Cluster",
|
||||
atmosphere="Haze",
|
||||
color_gel="Teal & Orange",
|
||||
camera_settings="None",
|
||||
special_technique="Shutter Drag",
|
||||
energy_level="Dynamic",
|
||||
safety_profile="None",
|
||||
blueprint_custom="",
|
||||
key_style_custom="",
|
||||
light_source_custom="",
|
||||
light_quality_custom="",
|
||||
time_of_day_custom="",
|
||||
fill_style_custom="",
|
||||
backlight_custom="",
|
||||
practical_custom="",
|
||||
atmosphere_custom="",
|
||||
color_gel_custom="",
|
||||
camera_settings_custom="",
|
||||
special_technique_custom="",
|
||||
energy_level_custom="",
|
||||
safety_profile_custom="",
|
||||
custom_prompt: str,
|
||||
blueprint: str = "Three-Point",
|
||||
key_style: str = "Softbox Key",
|
||||
light_source: str = "Daylight",
|
||||
light_quality: str = "Soft Light",
|
||||
time_of_day: str = "Dusk",
|
||||
fill_style: str = "Soft Fill",
|
||||
backlight: str = "Rim Strip",
|
||||
practical: str = "Lantern Cluster",
|
||||
atmosphere: str = "Haze",
|
||||
color_gel: str = "Teal & Orange",
|
||||
special_technique: str = "Shutter Drag",
|
||||
energy_level: str = "Dynamic",
|
||||
safety_profile: str = "None",
|
||||
subject_description="",
|
||||
environment_description="",
|
||||
accent_notes="",
|
||||
camera_notes="",
|
||||
intensity_bias=0.6,
|
||||
preset_action="none",
|
||||
preset_name="",
|
||||
):
|
||||
config = {
|
||||
"custom_prompt": custom_prompt,
|
||||
"blueprint": blueprint,
|
||||
"key_style": key_style,
|
||||
"light_source": light_source,
|
||||
@@ -118,27 +136,13 @@ class NoxPromptLightingMaster(PresetMixin):
|
||||
"practical": practical,
|
||||
"atmosphere": atmosphere,
|
||||
"color_gel": color_gel,
|
||||
"camera_settings": camera_settings,
|
||||
"special_technique": special_technique,
|
||||
"energy_level": energy_level,
|
||||
"safety_profile": safety_profile,
|
||||
"blueprint_custom": blueprint_custom,
|
||||
"key_style_custom": key_style_custom,
|
||||
"light_source_custom": light_source_custom,
|
||||
"light_quality_custom": light_quality_custom,
|
||||
"time_of_day_custom": time_of_day_custom,
|
||||
"fill_style_custom": fill_style_custom,
|
||||
"backlight_custom": backlight_custom,
|
||||
"practical_custom": practical_custom,
|
||||
"atmosphere_custom": atmosphere_custom,
|
||||
"color_gel_custom": color_gel_custom,
|
||||
"camera_settings_custom": camera_settings_custom,
|
||||
"special_technique_custom": special_technique_custom,
|
||||
"energy_level_custom": energy_level_custom,
|
||||
"safety_profile_custom": safety_profile_custom,
|
||||
"subject_description": subject_description,
|
||||
"environment_description": environment_description,
|
||||
"accent_notes": accent_notes,
|
||||
"camera_notes": camera_notes,
|
||||
"intensity_bias": intensity_bias,
|
||||
}
|
||||
|
||||
@@ -149,6 +153,7 @@ class NoxPromptLightingMaster(PresetMixin):
|
||||
config,
|
||||
)
|
||||
|
||||
custom_prompt = config.get("custom_prompt", custom_prompt)
|
||||
blueprint = config.get("blueprint", blueprint)
|
||||
key_style = config.get("key_style", key_style)
|
||||
fill_style = config.get("fill_style", fill_style)
|
||||
@@ -156,48 +161,34 @@ class NoxPromptLightingMaster(PresetMixin):
|
||||
practical = config.get("practical", practical)
|
||||
atmosphere = config.get("atmosphere", atmosphere)
|
||||
color_gel = config.get("color_gel", color_gel)
|
||||
camera_settings = config.get("camera_settings", camera_settings)
|
||||
special_technique = config.get("special_technique", special_technique)
|
||||
energy_level = config.get("energy_level", energy_level)
|
||||
safety_profile = config.get("safety_profile", safety_profile)
|
||||
blueprint_custom = config.get("blueprint_custom", blueprint_custom)
|
||||
key_style_custom = config.get("key_style_custom", key_style_custom)
|
||||
light_source = config.get("light_source", light_source)
|
||||
light_quality = config.get("light_quality", light_quality)
|
||||
time_of_day = config.get("time_of_day", time_of_day)
|
||||
light_source_custom = config.get("light_source_custom", light_source_custom)
|
||||
light_quality_custom = config.get("light_quality_custom", light_quality_custom)
|
||||
time_of_day_custom = config.get("time_of_day_custom", time_of_day_custom)
|
||||
fill_style_custom = config.get("fill_style_custom", fill_style_custom)
|
||||
backlight_custom = config.get("backlight_custom", backlight_custom)
|
||||
practical_custom = config.get("practical_custom", practical_custom)
|
||||
atmosphere_custom = config.get("atmosphere_custom", atmosphere_custom)
|
||||
color_gel_custom = config.get("color_gel_custom", color_gel_custom)
|
||||
camera_settings_custom = config.get("camera_settings_custom", camera_settings_custom)
|
||||
special_technique_custom = config.get("special_technique_custom", special_technique_custom)
|
||||
energy_level_custom = config.get("energy_level_custom", energy_level_custom)
|
||||
safety_profile_custom = config.get("safety_profile_custom", safety_profile_custom)
|
||||
subject_description = config.get("subject_description", subject_description)
|
||||
environment_description = config.get("environment_description", environment_description)
|
||||
accent_notes = config.get("accent_notes", accent_notes)
|
||||
camera_notes = config.get("camera_notes", camera_notes)
|
||||
intensity_bias = float(config.get("intensity_bias", intensity_bias))
|
||||
blueprint_prompt, blueprint_notes = _resolve_with_custom(blueprint, blueprint_custom, LIGHTING_BLUEPRINT_OPTIONS)
|
||||
key_prompt, key_notes = _resolve_with_custom(key_style, key_style_custom, LIGHTING_KEY_STYLE_OPTIONS)
|
||||
source_prompt, source_notes = _resolve_with_custom(light_source, light_source_custom, LIGHT_SOURCE_OPTIONS)
|
||||
quality_prompt, quality_notes = _resolve_with_custom(light_quality, light_quality_custom, LIGHT_QUALITY_OPTIONS)
|
||||
time_prompt, time_notes = _resolve_with_custom(time_of_day, time_of_day_custom, TIME_OF_DAY_OPTIONS)
|
||||
fill_prompt, fill_notes = _resolve_with_custom(fill_style, fill_style_custom, LIGHTING_FILL_STYLE_OPTIONS)
|
||||
back_prompt, back_notes = _resolve_with_custom(backlight, backlight_custom, LIGHTING_BACKLIGHT_OPTIONS)
|
||||
practical_prompt, practical_notes = _resolve_with_custom(practical, practical_custom, LIGHTING_PRACTICAL_OPTIONS)
|
||||
atmosphere_prompt, atmosphere_notes = _resolve_with_custom(atmosphere, atmosphere_custom, LIGHTING_ATMOSPHERE_OPTIONS)
|
||||
gel_prompt, gel_notes = _resolve_with_custom(color_gel, color_gel_custom, LIGHTING_COLOR_GEL_OPTIONS)
|
||||
camera_prompt, camera_notes = _resolve_with_custom(camera_settings, camera_settings_custom, LIGHTING_CAM_SETTINGS)
|
||||
technique_prompt, technique_notes = _resolve_with_custom(special_technique, special_technique_custom, LIGHTING_SPECIAL_TECHNIQUES)
|
||||
energy_prompt, energy_notes = _resolve_with_custom(energy_level, energy_level_custom, LIGHTING_ENERGY_LEVEL_OPTIONS)
|
||||
blueprint_prompt, blueprint_notes = _resolve_option(blueprint, LIGHTING_BLUEPRINT_OPTIONS)
|
||||
key_prompt, key_notes = _resolve_option(key_style, LIGHTING_KEY_STYLE_OPTIONS)
|
||||
source_prompt, source_notes = _resolve_option(light_source, LIGHT_SOURCE_OPTIONS)
|
||||
quality_prompt, quality_notes = _resolve_option(light_quality, LIGHT_QUALITY_OPTIONS)
|
||||
time_prompt, time_notes = _resolve_option(time_of_day, TIME_OF_DAY_OPTIONS)
|
||||
fill_prompt, fill_notes = _resolve_option(fill_style, LIGHTING_FILL_STYLE_OPTIONS)
|
||||
back_prompt, back_notes = _resolve_option(backlight, LIGHTING_BACKLIGHT_OPTIONS)
|
||||
practical_prompt, practical_notes = _resolve_option(practical, LIGHTING_PRACTICAL_OPTIONS)
|
||||
atmosphere_prompt, atmosphere_notes = _resolve_option(atmosphere, LIGHTING_ATMOSPHERE_OPTIONS)
|
||||
gel_prompt, gel_notes = _resolve_option(color_gel, LIGHTING_COLOR_GEL_OPTIONS)
|
||||
technique_prompt, technique_notes = _resolve_option(special_technique, LIGHTING_SPECIAL_TECHNIQUES)
|
||||
energy_prompt, energy_notes = _resolve_option(energy_level, LIGHTING_ENERGY_LEVEL_OPTIONS)
|
||||
|
||||
intensity_text = self._describe_intensity(intensity_bias)
|
||||
|
||||
lighting_fragments = [
|
||||
(custom_prompt or "").strip(),
|
||||
source_prompt,
|
||||
quality_prompt,
|
||||
time_prompt,
|
||||
@@ -209,7 +200,6 @@ class NoxPromptLightingMaster(PresetMixin):
|
||||
atmosphere_prompt,
|
||||
gel_prompt,
|
||||
technique_prompt,
|
||||
f"camera setup: {camera_prompt}" if camera_prompt else "",
|
||||
f"energy mode: {energy_prompt}" if energy_prompt else "",
|
||||
f"subject: {subject_description.strip()}" if subject_description.strip() else "",
|
||||
f"environment: {environment_description.strip()}" if environment_description.strip() else "",
|
||||
@@ -239,28 +229,22 @@ class NoxPromptLightingMaster(PresetMixin):
|
||||
mood_notes = " | ".join(section for section in mood_sections if section)
|
||||
|
||||
technical_sections = []
|
||||
if camera_prompt:
|
||||
technical_sections.append(f"Camera Settings: {camera_prompt}")
|
||||
if technique_prompt:
|
||||
technical_sections.append(f"Technique: {technique_prompt}")
|
||||
if technique_notes:
|
||||
technical_sections.append(f"Technique Notes: {technique_notes}")
|
||||
if energy_prompt:
|
||||
technical_sections.append(f"Energy Mode: {energy_prompt}")
|
||||
safety_label = ""
|
||||
safety_text = ""
|
||||
if safety_profile == CUSTOM_OPTION:
|
||||
safety_label = (safety_profile_custom or "").strip()
|
||||
if camera_notes:
|
||||
technical_sections.append(f"Camera Notes: {camera_notes}")
|
||||
if safety_profile and safety_profile != "None":
|
||||
safety_label = safety_profile.strip()
|
||||
safety_text = (LIGHTING_SAFETY_NOTES.get(safety_profile, "") or "").strip()
|
||||
if safety_label:
|
||||
safety_text = safety_label
|
||||
elif safety_profile and safety_profile != "None":
|
||||
safety_label = safety_profile
|
||||
safety_text = LIGHTING_SAFETY_NOTES.get(safety_profile, "")
|
||||
if safety_label:
|
||||
if safety_text and safety_text != safety_label:
|
||||
technical_sections.append(f"Safety: {safety_label} — {safety_text}")
|
||||
else:
|
||||
technical_sections.append(f"Safety: {safety_label}")
|
||||
if safety_text and safety_text != safety_label:
|
||||
technical_sections.append(f"Safety: {safety_label} — {safety_text}")
|
||||
else:
|
||||
technical_sections.append(f"Safety: {safety_label}")
|
||||
technical_notes = " | ".join(section for section in technical_sections if section)
|
||||
|
||||
return (lighting_prompt, mood_notes, technical_notes, preset_status)
|
||||
|
||||
@@ -37,10 +37,6 @@ class NoxPromptNSFWDesigner(PresetMixin):
|
||||
list(NSFW_LIGHTING_SETUPS.keys()),
|
||||
{"default": "Candlelit Warmth"},
|
||||
),
|
||||
"camera_framing": (
|
||||
list(NSFW_CAMERA_FRAMING.keys()),
|
||||
{"default": "Intimate Portrait"},
|
||||
),
|
||||
"explicitness_level": (
|
||||
list(NSFW_EXPLICITNESS_LEVELS.keys()),
|
||||
{"default": "Implied Nude"},
|
||||
@@ -85,14 +81,6 @@ class NoxPromptNSFWDesigner(PresetMixin):
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_camera_prompt": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_camera_notes": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_set_prompt": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
@@ -125,6 +113,10 @@ class NoxPromptNSFWDesigner(PresetMixin):
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"camera_notes": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"detail_accent": (
|
||||
"STRING",
|
||||
{
|
||||
@@ -158,7 +150,6 @@ class NoxPromptNSFWDesigner(PresetMixin):
|
||||
wardrobe_style,
|
||||
tone_profile,
|
||||
lighting_setup="Candlelit Warmth",
|
||||
camera_framing="Intimate Portrait",
|
||||
explicitness_level="Implied Nude",
|
||||
set_design="None",
|
||||
heat_profile="Sensual",
|
||||
@@ -175,8 +166,6 @@ class NoxPromptNSFWDesigner(PresetMixin):
|
||||
custom_tone_notes="",
|
||||
custom_lighting_prompt="",
|
||||
custom_lighting_notes="",
|
||||
custom_camera_prompt="",
|
||||
custom_camera_notes="",
|
||||
custom_set_prompt="",
|
||||
custom_set_notes="",
|
||||
custom_explicitness_prompt="",
|
||||
@@ -186,6 +175,7 @@ class NoxPromptNSFWDesigner(PresetMixin):
|
||||
custom_heat_prompt="",
|
||||
custom_heat_notes="",
|
||||
custom_heat_negative="",
|
||||
camera_notes="",
|
||||
preset_action="none",
|
||||
preset_name="",
|
||||
):
|
||||
@@ -196,7 +186,6 @@ class NoxPromptNSFWDesigner(PresetMixin):
|
||||
"wardrobe_style": wardrobe_style,
|
||||
"tone_profile": tone_profile,
|
||||
"lighting_setup": lighting_setup,
|
||||
"camera_framing": camera_framing,
|
||||
"explicitness_level": explicitness_level,
|
||||
"set_design": set_design,
|
||||
"heat_profile": heat_profile,
|
||||
@@ -213,8 +202,6 @@ class NoxPromptNSFWDesigner(PresetMixin):
|
||||
"custom_tone_notes": custom_tone_notes,
|
||||
"custom_lighting_prompt": custom_lighting_prompt,
|
||||
"custom_lighting_notes": custom_lighting_notes,
|
||||
"custom_camera_prompt": custom_camera_prompt,
|
||||
"custom_camera_notes": custom_camera_notes,
|
||||
"custom_set_prompt": custom_set_prompt,
|
||||
"custom_set_notes": custom_set_notes,
|
||||
"custom_explicitness_prompt": custom_explicitness_prompt,
|
||||
@@ -224,6 +211,7 @@ class NoxPromptNSFWDesigner(PresetMixin):
|
||||
"custom_heat_prompt": custom_heat_prompt,
|
||||
"custom_heat_notes": custom_heat_notes,
|
||||
"custom_heat_negative": custom_heat_negative,
|
||||
"camera_notes": camera_notes,
|
||||
}
|
||||
|
||||
config, preset_status = self._apply_preset_action(
|
||||
@@ -239,7 +227,6 @@ class NoxPromptNSFWDesigner(PresetMixin):
|
||||
wardrobe_style = config.get("wardrobe_style", wardrobe_style)
|
||||
tone_profile = config.get("tone_profile", tone_profile)
|
||||
lighting_setup = config.get("lighting_setup", lighting_setup)
|
||||
camera_framing = config.get("camera_framing", camera_framing)
|
||||
explicitness_level = config.get("explicitness_level", explicitness_level)
|
||||
set_design = config.get("set_design", set_design)
|
||||
heat_profile = config.get("heat_profile", heat_profile)
|
||||
@@ -256,8 +243,6 @@ class NoxPromptNSFWDesigner(PresetMixin):
|
||||
custom_tone_notes = config.get("custom_tone_notes", custom_tone_notes)
|
||||
custom_lighting_prompt = config.get("custom_lighting_prompt", custom_lighting_prompt)
|
||||
custom_lighting_notes = config.get("custom_lighting_notes", custom_lighting_notes)
|
||||
custom_camera_prompt = config.get("custom_camera_prompt", custom_camera_prompt)
|
||||
custom_camera_notes = config.get("custom_camera_notes", custom_camera_notes)
|
||||
custom_set_prompt = config.get("custom_set_prompt", custom_set_prompt)
|
||||
custom_set_notes = config.get("custom_set_notes", custom_set_notes)
|
||||
custom_explicitness_prompt = config.get("custom_explicitness_prompt", custom_explicitness_prompt)
|
||||
@@ -267,6 +252,7 @@ class NoxPromptNSFWDesigner(PresetMixin):
|
||||
custom_heat_prompt = config.get("custom_heat_prompt", custom_heat_prompt)
|
||||
custom_heat_notes = config.get("custom_heat_notes", custom_heat_notes)
|
||||
custom_heat_negative = config.get("custom_heat_negative", custom_heat_negative)
|
||||
camera_notes = config.get("camera_notes", camera_notes)
|
||||
|
||||
pose_cfg = self._resolve_nsfw_option(
|
||||
pose_profile,
|
||||
@@ -292,12 +278,6 @@ class NoxPromptNSFWDesigner(PresetMixin):
|
||||
custom_prompt=custom_lighting_prompt,
|
||||
custom_notes=custom_lighting_notes,
|
||||
)
|
||||
camera_cfg = self._resolve_nsfw_option(
|
||||
camera_framing,
|
||||
NSFW_CAMERA_FRAMING,
|
||||
custom_prompt=custom_camera_prompt,
|
||||
custom_notes=custom_camera_notes,
|
||||
)
|
||||
explicit_cfg = self._resolve_nsfw_option(
|
||||
explicitness_level,
|
||||
NSFW_EXPLICITNESS_LEVELS,
|
||||
@@ -345,10 +325,11 @@ class NoxPromptNSFWDesigner(PresetMixin):
|
||||
pose_cfg.get("prompt", ""),
|
||||
wardrobe_cfg.get("prompt", ""),
|
||||
lighting_cfg.get("prompt", ""),
|
||||
camera_cfg.get("prompt", ""),
|
||||
]
|
||||
if detail_accent.strip():
|
||||
descriptive_chunks.append(detail_accent.strip())
|
||||
if camera_notes.strip():
|
||||
descriptive_chunks.append(camera_notes.strip())
|
||||
descriptive_line = ", ".join(chunk for chunk in descriptive_chunks if chunk)
|
||||
if descriptive_line:
|
||||
fragments.append(descriptive_line)
|
||||
@@ -378,11 +359,12 @@ class NoxPromptNSFWDesigner(PresetMixin):
|
||||
wardrobe_cfg.get("notes"),
|
||||
tone_cfg.get("notes"),
|
||||
lighting_cfg.get("notes"),
|
||||
camera_cfg.get("notes"),
|
||||
explicit_cfg.get("notes"),
|
||||
set_cfg.get("notes"),
|
||||
heat_cfg.get("notes"),
|
||||
]
|
||||
if camera_notes.strip():
|
||||
notes_sources.append(camera_notes.strip())
|
||||
curated_notes = [note for note in notes_sources if note]
|
||||
if curated_notes:
|
||||
safety_parts.extend(list(dict.fromkeys(curated_notes)))
|
||||
|
||||
@@ -2,10 +2,9 @@ from __future__ import annotations
|
||||
|
||||
from typing import Dict, List
|
||||
|
||||
from ..common import PresetMixin
|
||||
from ..common import PresetMixin, option_keys
|
||||
from ..constants import (
|
||||
NSFW_BASELINE_NEGATIVES,
|
||||
NSFW_CAMERA_FRAMING,
|
||||
NSFW_DEFAULT_SAFETY_NOTE,
|
||||
NSFW_EXPLICITNESS_LEVELS,
|
||||
NSFW_HEAT_LEVELS,
|
||||
@@ -33,6 +32,13 @@ class NoxPromptNSFWPoseMaster(PresetMixin):
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"custom_prompt": (
|
||||
"STRING",
|
||||
{
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
},
|
||||
),
|
||||
"subject_focus": (
|
||||
"STRING",
|
||||
{
|
||||
@@ -41,13 +47,9 @@ class NoxPromptNSFWPoseMaster(PresetMixin):
|
||||
},
|
||||
),
|
||||
"pose_profile": (
|
||||
list(NSFW_POSE_PROFILES.keys()),
|
||||
option_keys(NSFW_POSE_PROFILES),
|
||||
{"default": "Reclined Elegance"},
|
||||
),
|
||||
"camera_framing": (
|
||||
list(NSFW_CAMERA_FRAMING.keys()),
|
||||
{"default": "Intimate Portrait"},
|
||||
),
|
||||
},
|
||||
"optional": {
|
||||
"pose_intent": (
|
||||
@@ -94,78 +96,22 @@ class NoxPromptNSFWPoseMaster(PresetMixin):
|
||||
{"multiline": True, "default": "adult, empowered, confident"},
|
||||
),
|
||||
"lighting_setup": (
|
||||
list(NSFW_LIGHTING_SETUPS.keys()),
|
||||
option_keys(NSFW_LIGHTING_SETUPS),
|
||||
{"default": "Candlelit Warmth"},
|
||||
),
|
||||
"set_design": (
|
||||
list(NSFW_SET_DESIGNS.keys()),
|
||||
option_keys(NSFW_SET_DESIGNS),
|
||||
{"default": "Sensual Lounge"},
|
||||
),
|
||||
"heat_profile": (
|
||||
list(NSFW_HEAT_LEVELS.keys()),
|
||||
option_keys(NSFW_HEAT_LEVELS),
|
||||
{"default": "Sensual"},
|
||||
),
|
||||
"explicitness_level": (
|
||||
list(NSFW_EXPLICITNESS_LEVELS.keys()),
|
||||
option_keys(NSFW_EXPLICITNESS_LEVELS),
|
||||
{"default": "Implied Nude"},
|
||||
),
|
||||
"custom_pose_prompt": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_pose_notes": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_camera_prompt": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_camera_notes": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_lighting_prompt": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_lighting_notes": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_set_prompt": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_set_notes": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_heat_prompt": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_heat_notes": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_heat_negative": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_explicitness_prompt": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_explicitness_notes": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_explicitness_negative": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_negative_prompt": (
|
||||
"camera_notes": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
@@ -184,10 +130,6 @@ class NoxPromptNSFWPoseMaster(PresetMixin):
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_safety_note": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"include_negative_baseline": (
|
||||
"BOOLEAN",
|
||||
{"default": True},
|
||||
@@ -209,9 +151,9 @@ class NoxPromptNSFWPoseMaster(PresetMixin):
|
||||
|
||||
def stage_pose(
|
||||
self,
|
||||
custom_prompt: str,
|
||||
subject_focus: str,
|
||||
pose_profile: str,
|
||||
camera_framing: str,
|
||||
pose_profile: str = "Reclined Elegance",
|
||||
pose_intent: str = "Celebrate adult confidence with graceful, empowered staging",
|
||||
body_highlights: str = "elongated lines, flattering curvature, controlled angles",
|
||||
expression_focus: str = "inviting gaze, self-assured smile",
|
||||
@@ -223,34 +165,19 @@ class NoxPromptNSFWPoseMaster(PresetMixin):
|
||||
set_design: str = "Sensual Lounge",
|
||||
heat_profile: str = "Sensual",
|
||||
explicitness_level: str = "Implied Nude",
|
||||
custom_pose_prompt: str = "",
|
||||
custom_pose_notes: str = "",
|
||||
custom_camera_prompt: str = "",
|
||||
custom_camera_notes: str = "",
|
||||
custom_lighting_prompt: str = "",
|
||||
custom_lighting_notes: str = "",
|
||||
custom_set_prompt: str = "",
|
||||
custom_set_notes: str = "",
|
||||
custom_heat_prompt: str = "",
|
||||
custom_heat_notes: str = "",
|
||||
custom_heat_negative: str = "",
|
||||
custom_explicitness_prompt: str = "",
|
||||
custom_explicitness_notes: str = "",
|
||||
custom_explicitness_negative: str = "",
|
||||
custom_negative_prompt: str = "",
|
||||
consent_focus: str = "affirmed adult consent, ongoing verbal check-ins, respectful pacing",
|
||||
camera_notes: str = "",
|
||||
consent_focus: str = "affirmed adult consent, ongoing verbal check-ins, respectful pacing",
|
||||
boundary_notes: str = "",
|
||||
safety_equipment: str = "",
|
||||
custom_safety_note: str = "",
|
||||
include_negative_baseline: bool = True,
|
||||
include_safety_note: bool = True,
|
||||
preset_action: str = "none",
|
||||
preset_name: str = "",
|
||||
):
|
||||
config = {
|
||||
"custom_prompt": custom_prompt,
|
||||
"subject_focus": subject_focus,
|
||||
"pose_profile": pose_profile,
|
||||
"camera_framing": camera_framing,
|
||||
"pose_intent": pose_intent,
|
||||
"body_highlights": body_highlights,
|
||||
"expression_focus": expression_focus,
|
||||
@@ -262,25 +189,10 @@ class NoxPromptNSFWPoseMaster(PresetMixin):
|
||||
"set_design": set_design,
|
||||
"heat_profile": heat_profile,
|
||||
"explicitness_level": explicitness_level,
|
||||
"custom_pose_prompt": custom_pose_prompt,
|
||||
"custom_pose_notes": custom_pose_notes,
|
||||
"custom_camera_prompt": custom_camera_prompt,
|
||||
"custom_camera_notes": custom_camera_notes,
|
||||
"custom_lighting_prompt": custom_lighting_prompt,
|
||||
"custom_lighting_notes": custom_lighting_notes,
|
||||
"custom_set_prompt": custom_set_prompt,
|
||||
"custom_set_notes": custom_set_notes,
|
||||
"custom_heat_prompt": custom_heat_prompt,
|
||||
"custom_heat_notes": custom_heat_notes,
|
||||
"custom_heat_negative": custom_heat_negative,
|
||||
"custom_explicitness_prompt": custom_explicitness_prompt,
|
||||
"custom_explicitness_notes": custom_explicitness_notes,
|
||||
"custom_explicitness_negative": custom_explicitness_negative,
|
||||
"custom_negative_prompt": custom_negative_prompt,
|
||||
"camera_notes": camera_notes,
|
||||
"consent_focus": consent_focus,
|
||||
"boundary_notes": boundary_notes,
|
||||
"safety_equipment": safety_equipment,
|
||||
"custom_safety_note": custom_safety_note,
|
||||
"include_negative_baseline": include_negative_baseline,
|
||||
"include_safety_note": include_safety_note,
|
||||
}
|
||||
@@ -292,9 +204,9 @@ class NoxPromptNSFWPoseMaster(PresetMixin):
|
||||
config,
|
||||
)
|
||||
|
||||
custom_prompt = config.get("custom_prompt", custom_prompt)
|
||||
subject_focus = config.get("subject_focus", subject_focus)
|
||||
pose_profile = config.get("pose_profile", pose_profile)
|
||||
camera_framing = config.get("camera_framing", camera_framing)
|
||||
pose_intent = config.get("pose_intent", pose_intent)
|
||||
body_highlights = config.get("body_highlights", body_highlights)
|
||||
expression_focus = config.get("expression_focus", expression_focus)
|
||||
@@ -306,68 +218,38 @@ class NoxPromptNSFWPoseMaster(PresetMixin):
|
||||
set_design = config.get("set_design", set_design)
|
||||
heat_profile = config.get("heat_profile", heat_profile)
|
||||
explicitness_level = config.get("explicitness_level", explicitness_level)
|
||||
custom_pose_prompt = config.get("custom_pose_prompt", custom_pose_prompt)
|
||||
custom_pose_notes = config.get("custom_pose_notes", custom_pose_notes)
|
||||
custom_camera_prompt = config.get("custom_camera_prompt", custom_camera_prompt)
|
||||
custom_camera_notes = config.get("custom_camera_notes", custom_camera_notes)
|
||||
custom_lighting_prompt = config.get("custom_lighting_prompt", custom_lighting_prompt)
|
||||
custom_lighting_notes = config.get("custom_lighting_notes", custom_lighting_notes)
|
||||
custom_set_prompt = config.get("custom_set_prompt", custom_set_prompt)
|
||||
custom_set_notes = config.get("custom_set_notes", custom_set_notes)
|
||||
custom_heat_prompt = config.get("custom_heat_prompt", custom_heat_prompt)
|
||||
custom_heat_notes = config.get("custom_heat_notes", custom_heat_notes)
|
||||
custom_heat_negative = config.get("custom_heat_negative", custom_heat_negative)
|
||||
custom_explicitness_prompt = config.get("custom_explicitness_prompt", custom_explicitness_prompt)
|
||||
custom_explicitness_notes = config.get("custom_explicitness_notes", custom_explicitness_notes)
|
||||
custom_explicitness_negative = config.get("custom_explicitness_negative", custom_explicitness_negative)
|
||||
custom_negative_prompt = config.get("custom_negative_prompt", custom_negative_prompt)
|
||||
camera_notes = config.get("camera_notes", camera_notes)
|
||||
consent_focus = config.get("consent_focus", consent_focus)
|
||||
boundary_notes = config.get("boundary_notes", boundary_notes)
|
||||
safety_equipment = config.get("safety_equipment", safety_equipment)
|
||||
custom_safety_note = config.get("custom_safety_note", custom_safety_note)
|
||||
include_negative_baseline = bool(config.get("include_negative_baseline", include_negative_baseline))
|
||||
include_safety_note = bool(config.get("include_safety_note", include_safety_note))
|
||||
|
||||
pose_cfg = self._resolve_nsfw_option(
|
||||
pose_profile,
|
||||
NSFW_POSE_PROFILES,
|
||||
custom_prompt=custom_pose_prompt,
|
||||
custom_notes=custom_pose_notes,
|
||||
)
|
||||
camera_cfg = self._resolve_nsfw_option(
|
||||
camera_framing,
|
||||
NSFW_CAMERA_FRAMING,
|
||||
custom_prompt=custom_camera_prompt,
|
||||
custom_notes=custom_camera_notes,
|
||||
)
|
||||
lighting_cfg = self._resolve_nsfw_option(
|
||||
lighting_setup,
|
||||
NSFW_LIGHTING_SETUPS,
|
||||
custom_prompt=custom_lighting_prompt,
|
||||
custom_notes=custom_lighting_notes,
|
||||
)
|
||||
set_cfg = self._resolve_nsfw_option(
|
||||
set_design,
|
||||
NSFW_SET_DESIGNS,
|
||||
custom_prompt=custom_set_prompt,
|
||||
custom_notes=custom_set_notes,
|
||||
)
|
||||
heat_cfg = self._resolve_nsfw_option(
|
||||
heat_profile,
|
||||
NSFW_HEAT_LEVELS,
|
||||
custom_prompt=custom_heat_prompt,
|
||||
custom_notes=custom_heat_notes,
|
||||
custom_negative=custom_heat_negative,
|
||||
)
|
||||
explicit_cfg = self._resolve_nsfw_option(
|
||||
explicitness_level,
|
||||
NSFW_EXPLICITNESS_LEVELS,
|
||||
custom_prompt=custom_explicitness_prompt,
|
||||
custom_notes=custom_explicitness_notes,
|
||||
custom_negative=custom_explicitness_negative,
|
||||
)
|
||||
|
||||
prompt_fragments: List[str] = []
|
||||
custom_fragment = (custom_prompt or "").strip()
|
||||
if custom_fragment:
|
||||
prompt_fragments.append(custom_fragment)
|
||||
primary_parts = [subject_focus.strip()]
|
||||
if pose_intent.strip():
|
||||
primary_parts.append(pose_intent.strip())
|
||||
@@ -401,11 +283,11 @@ class NoxPromptNSFWPoseMaster(PresetMixin):
|
||||
environment_parts.append(set_cfg["prompt"])
|
||||
if lighting_cfg.get("prompt"):
|
||||
environment_parts.append(lighting_cfg["prompt"])
|
||||
if camera_cfg.get("prompt"):
|
||||
environment_parts.append(camera_cfg["prompt"])
|
||||
environment_sentence = ", ".join(part for part in environment_parts if part)
|
||||
if environment_sentence:
|
||||
prompt_fragments.append(environment_sentence)
|
||||
if camera_notes.strip():
|
||||
prompt_fragments.append(f"Camera guidance: {camera_notes.strip()}")
|
||||
|
||||
tag_list = self._split_keywords(pose_tags)
|
||||
if tag_list:
|
||||
@@ -418,14 +300,11 @@ class NoxPromptNSFWPoseMaster(PresetMixin):
|
||||
negatives.extend(NSFW_BASELINE_NEGATIVES)
|
||||
negatives.extend(self._split_keywords(explicit_cfg.get("negative", "")))
|
||||
negatives.extend(self._split_keywords(heat_cfg.get("negative", "")))
|
||||
negatives.extend(self._split_keywords(custom_negative_prompt))
|
||||
pose_negative = ", ".join(self._unique_sequence(negatives))
|
||||
|
||||
brief_parts: List[str] = []
|
||||
if pose_cfg.get("notes"):
|
||||
brief_parts.append(f"Pose note: {pose_cfg['notes']}")
|
||||
if camera_cfg.get("notes"):
|
||||
brief_parts.append(f"Camera: {camera_cfg['notes']}")
|
||||
if lighting_cfg.get("notes"):
|
||||
brief_parts.append(f"Lighting: {lighting_cfg['notes']}")
|
||||
if set_cfg.get("notes"):
|
||||
@@ -434,6 +313,8 @@ class NoxPromptNSFWPoseMaster(PresetMixin):
|
||||
brief_parts.append(f"Heat: {heat_cfg['notes']}")
|
||||
if explicit_cfg.get("notes"):
|
||||
brief_parts.append(f"Explicitness: {explicit_cfg['notes']}")
|
||||
if camera_notes.strip():
|
||||
brief_parts.append(f"Camera: {camera_notes.strip()}")
|
||||
if expression_focus.strip():
|
||||
brief_parts.append(f"Expression: {expression_focus.strip()}")
|
||||
if movement_cue.strip():
|
||||
@@ -460,7 +341,6 @@ class NoxPromptNSFWPoseMaster(PresetMixin):
|
||||
safety_parts.append(f"Safety gear: {safety_equipment.strip()}")
|
||||
for source in (
|
||||
pose_cfg.get("notes"),
|
||||
camera_cfg.get("notes"),
|
||||
lighting_cfg.get("notes"),
|
||||
set_cfg.get("notes"),
|
||||
heat_cfg.get("notes"),
|
||||
@@ -468,8 +348,8 @@ class NoxPromptNSFWPoseMaster(PresetMixin):
|
||||
):
|
||||
if source:
|
||||
safety_parts.append(source)
|
||||
if custom_safety_note.strip():
|
||||
safety_parts.append(custom_safety_note.strip())
|
||||
if camera_notes.strip():
|
||||
safety_parts.append(f"Camera: {camera_notes.strip()}")
|
||||
safety_notes = " | ".join(self._unique_sequence(safety_parts))
|
||||
|
||||
return pose_prompt, pose_negative, pose_brief, safety_notes, preset_status
|
||||
@@ -515,30 +395,11 @@ class NoxPromptNSFWPoseMaster(PresetMixin):
|
||||
self,
|
||||
selection: str,
|
||||
options: Dict[str, Dict[str, str]],
|
||||
*,
|
||||
custom_prompt: str = "",
|
||||
custom_notes: str = "",
|
||||
custom_negative: str | None = None,
|
||||
) -> Dict[str, str]:
|
||||
key = self._match_option_key(selection, options)
|
||||
fallback = "None" if "None" in options else next(iter(options))
|
||||
source_key = key or fallback
|
||||
base = dict(options.get(source_key, {}))
|
||||
|
||||
prompt_parts = [part.strip() for part in [base.get("prompt", ""), custom_prompt] if part and part.strip()]
|
||||
base["prompt"] = ", ".join(self._unique_sequence(prompt_parts)) if prompt_parts else ""
|
||||
|
||||
notes_parts = [part.strip() for part in [base.get("notes", ""), custom_notes] if part and part.strip()]
|
||||
if notes_parts:
|
||||
base["notes"] = " | ".join(self._unique_sequence(notes_parts))
|
||||
elif "notes" in base:
|
||||
base["notes"] = base.get("notes", "")
|
||||
|
||||
if custom_negative is not None:
|
||||
negative_parts = [part.strip() for part in [base.get("negative", ""), custom_negative] if part and part.strip()]
|
||||
base["negative"] = ", ".join(self._unique_sequence(negative_parts)) if negative_parts else ""
|
||||
|
||||
return base
|
||||
return dict(options.get(source_key, {}))
|
||||
|
||||
def _match_option_key(self, selection: str, options: Dict[str, Dict[str, str]]) -> str | None:
|
||||
normalized = (selection or "").strip().lower()
|
||||
|
||||
@@ -2,10 +2,9 @@ from __future__ import annotations
|
||||
|
||||
from typing import Dict, List
|
||||
|
||||
from ..common import PresetMixin
|
||||
from ..common import PresetMixin, option_keys
|
||||
from ..constants import (
|
||||
POSE_BASELINE_NEGATIVES,
|
||||
POSE_CAMERA_FRAMING,
|
||||
POSE_LIGHTING_SETUPS,
|
||||
POSE_MASTER_PROFILES,
|
||||
POSE_SET_DESIGNS,
|
||||
@@ -31,6 +30,13 @@ class NoxPromptPoseMaster(PresetMixin):
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"custom_prompt": (
|
||||
"STRING",
|
||||
{
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
},
|
||||
),
|
||||
"subject_focus": (
|
||||
"STRING",
|
||||
{
|
||||
@@ -39,13 +45,9 @@ class NoxPromptPoseMaster(PresetMixin):
|
||||
},
|
||||
),
|
||||
"pose_profile": (
|
||||
list(POSE_MASTER_PROFILES.keys()),
|
||||
option_keys(POSE_MASTER_PROFILES),
|
||||
{"default": "Heroic Stand"},
|
||||
),
|
||||
"camera_framing": (
|
||||
list(POSE_CAMERA_FRAMING.keys()),
|
||||
{"default": "Full Figure"},
|
||||
),
|
||||
},
|
||||
"optional": {
|
||||
"pose_intent": (
|
||||
@@ -86,61 +88,17 @@ class NoxPromptPoseMaster(PresetMixin):
|
||||
{"multiline": True, "default": "pose, confident, cinematic"},
|
||||
),
|
||||
"lighting_setup": (
|
||||
list(POSE_LIGHTING_SETUPS.keys()),
|
||||
option_keys(POSE_LIGHTING_SETUPS),
|
||||
{"default": "Soft Studio"},
|
||||
),
|
||||
"set_design": (
|
||||
list(POSE_SET_DESIGNS.keys()),
|
||||
option_keys(POSE_SET_DESIGNS),
|
||||
{"default": "Studio Seamless"},
|
||||
),
|
||||
"energy_level": (
|
||||
list(POSE_ENERGY_LEVELS.keys()),
|
||||
option_keys(POSE_ENERGY_LEVELS),
|
||||
{"default": "Focused Drive"},
|
||||
),
|
||||
"custom_pose_prompt": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_pose_notes": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_camera_prompt": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_camera_notes": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_lighting_prompt": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_lighting_notes": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_set_prompt": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_set_notes": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_energy_prompt": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_energy_notes": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_negative_prompt": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"coaching_focus": (
|
||||
"STRING",
|
||||
{
|
||||
@@ -159,7 +117,7 @@ class NoxPromptPoseMaster(PresetMixin):
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"custom_guidance_note": (
|
||||
"camera_notes": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
@@ -180,9 +138,9 @@ class NoxPromptPoseMaster(PresetMixin):
|
||||
|
||||
def stage_pose(
|
||||
self,
|
||||
subject_focus: str,
|
||||
pose_profile: str,
|
||||
camera_framing: str,
|
||||
custom_prompt: str = "",
|
||||
subject_focus: str = "Lead performer demonstrating confident posture",
|
||||
pose_profile: str = "Heroic Stand",
|
||||
pose_intent: str = "Capture a confident, story-driven stance for key art coverage",
|
||||
body_highlights: str = "elongated lines, balanced weight, expressive hands",
|
||||
expression_focus: str = "steady gaze, relaxed smile",
|
||||
@@ -193,29 +151,18 @@ class NoxPromptPoseMaster(PresetMixin):
|
||||
lighting_setup: str = "Soft Studio",
|
||||
set_design: str = "Studio Seamless",
|
||||
energy_level: str = "Focused Drive",
|
||||
custom_pose_prompt: str = "",
|
||||
custom_pose_notes: str = "",
|
||||
custom_camera_prompt: str = "",
|
||||
custom_camera_notes: str = "",
|
||||
custom_lighting_prompt: str = "",
|
||||
custom_lighting_notes: str = "",
|
||||
custom_set_prompt: str = "",
|
||||
custom_set_notes: str = "",
|
||||
custom_energy_prompt: str = "",
|
||||
custom_energy_notes: str = "",
|
||||
custom_negative_prompt: str = "",
|
||||
camera_notes: str = "",
|
||||
coaching_focus: str = "Offer clear coaching cues and celebrate confident delivery.",
|
||||
comfort_considerations: str = "Monitor posture comfort, rotate breaks, and provide hydration reminders.",
|
||||
safety_equipment: str = "",
|
||||
custom_guidance_note: str = "",
|
||||
include_negative_baseline: bool = True,
|
||||
preset_action: str = "none",
|
||||
preset_name: str = "",
|
||||
):
|
||||
config = {
|
||||
"custom_prompt": custom_prompt,
|
||||
"subject_focus": subject_focus,
|
||||
"pose_profile": pose_profile,
|
||||
"camera_framing": camera_framing,
|
||||
"pose_intent": pose_intent,
|
||||
"body_highlights": body_highlights,
|
||||
"expression_focus": expression_focus,
|
||||
@@ -226,21 +173,10 @@ class NoxPromptPoseMaster(PresetMixin):
|
||||
"lighting_setup": lighting_setup,
|
||||
"set_design": set_design,
|
||||
"energy_level": energy_level,
|
||||
"custom_pose_prompt": custom_pose_prompt,
|
||||
"custom_pose_notes": custom_pose_notes,
|
||||
"custom_camera_prompt": custom_camera_prompt,
|
||||
"custom_camera_notes": custom_camera_notes,
|
||||
"custom_lighting_prompt": custom_lighting_prompt,
|
||||
"custom_lighting_notes": custom_lighting_notes,
|
||||
"custom_set_prompt": custom_set_prompt,
|
||||
"custom_set_notes": custom_set_notes,
|
||||
"custom_energy_prompt": custom_energy_prompt,
|
||||
"custom_energy_notes": custom_energy_notes,
|
||||
"custom_negative_prompt": custom_negative_prompt,
|
||||
"camera_notes": camera_notes,
|
||||
"coaching_focus": coaching_focus,
|
||||
"comfort_considerations": comfort_considerations,
|
||||
"safety_equipment": safety_equipment,
|
||||
"custom_guidance_note": custom_guidance_note,
|
||||
"include_negative_baseline": include_negative_baseline,
|
||||
}
|
||||
|
||||
@@ -251,9 +187,9 @@ class NoxPromptPoseMaster(PresetMixin):
|
||||
config,
|
||||
)
|
||||
|
||||
custom_prompt = config.get("custom_prompt", custom_prompt)
|
||||
subject_focus = config.get("subject_focus", subject_focus)
|
||||
pose_profile = config.get("pose_profile", pose_profile)
|
||||
camera_framing = config.get("camera_framing", camera_framing)
|
||||
pose_intent = config.get("pose_intent", pose_intent)
|
||||
body_highlights = config.get("body_highlights", body_highlights)
|
||||
expression_focus = config.get("expression_focus", expression_focus)
|
||||
@@ -264,21 +200,10 @@ class NoxPromptPoseMaster(PresetMixin):
|
||||
lighting_setup = config.get("lighting_setup", lighting_setup)
|
||||
set_design = config.get("set_design", set_design)
|
||||
energy_level = config.get("energy_level", energy_level)
|
||||
custom_pose_prompt = config.get("custom_pose_prompt", custom_pose_prompt)
|
||||
custom_pose_notes = config.get("custom_pose_notes", custom_pose_notes)
|
||||
custom_camera_prompt = config.get("custom_camera_prompt", custom_camera_prompt)
|
||||
custom_camera_notes = config.get("custom_camera_notes", custom_camera_notes)
|
||||
custom_lighting_prompt = config.get("custom_lighting_prompt", custom_lighting_prompt)
|
||||
custom_lighting_notes = config.get("custom_lighting_notes", custom_lighting_notes)
|
||||
custom_set_prompt = config.get("custom_set_prompt", custom_set_prompt)
|
||||
custom_set_notes = config.get("custom_set_notes", custom_set_notes)
|
||||
custom_energy_prompt = config.get("custom_energy_prompt", custom_energy_prompt)
|
||||
custom_energy_notes = config.get("custom_energy_notes", custom_energy_notes)
|
||||
custom_negative_prompt = config.get("custom_negative_prompt", custom_negative_prompt)
|
||||
camera_notes = config.get("camera_notes", camera_notes)
|
||||
coaching_focus = config.get("coaching_focus", coaching_focus)
|
||||
comfort_considerations = config.get("comfort_considerations", comfort_considerations)
|
||||
safety_equipment = config.get("safety_equipment", safety_equipment)
|
||||
custom_guidance_note = config.get("custom_guidance_note", custom_guidance_note)
|
||||
include_negative_baseline = bool(
|
||||
config.get("include_negative_baseline", include_negative_baseline)
|
||||
)
|
||||
@@ -286,35 +211,24 @@ class NoxPromptPoseMaster(PresetMixin):
|
||||
pose_cfg = self._resolve_pose_option(
|
||||
pose_profile,
|
||||
POSE_MASTER_PROFILES,
|
||||
custom_prompt=custom_pose_prompt,
|
||||
custom_notes=custom_pose_notes,
|
||||
)
|
||||
camera_cfg = self._resolve_pose_option(
|
||||
camera_framing,
|
||||
POSE_CAMERA_FRAMING,
|
||||
custom_prompt=custom_camera_prompt,
|
||||
custom_notes=custom_camera_notes,
|
||||
)
|
||||
lighting_cfg = self._resolve_pose_option(
|
||||
lighting_setup,
|
||||
POSE_LIGHTING_SETUPS,
|
||||
custom_prompt=custom_lighting_prompt,
|
||||
custom_notes=custom_lighting_notes,
|
||||
)
|
||||
set_cfg = self._resolve_pose_option(
|
||||
set_design,
|
||||
POSE_SET_DESIGNS,
|
||||
custom_prompt=custom_set_prompt,
|
||||
custom_notes=custom_set_notes,
|
||||
)
|
||||
energy_cfg = self._resolve_pose_option(
|
||||
energy_level,
|
||||
POSE_ENERGY_LEVELS,
|
||||
custom_prompt=custom_energy_prompt,
|
||||
custom_notes=custom_energy_notes,
|
||||
)
|
||||
|
||||
prompt_fragments: List[str] = []
|
||||
custom_fragment = (custom_prompt or "").strip()
|
||||
if custom_fragment:
|
||||
prompt_fragments.append(custom_fragment)
|
||||
primary_parts = [subject_focus.strip()]
|
||||
if pose_intent.strip():
|
||||
primary_parts.append(pose_intent.strip())
|
||||
@@ -346,11 +260,11 @@ class NoxPromptPoseMaster(PresetMixin):
|
||||
environment_parts.append(set_cfg["prompt"])
|
||||
if lighting_cfg.get("prompt"):
|
||||
environment_parts.append(lighting_cfg["prompt"])
|
||||
if camera_cfg.get("prompt"):
|
||||
environment_parts.append(camera_cfg["prompt"])
|
||||
environment_sentence = ", ".join(part for part in environment_parts if part)
|
||||
if environment_sentence:
|
||||
prompt_fragments.append(environment_sentence)
|
||||
if camera_notes.strip():
|
||||
prompt_fragments.append(f"Camera guidance: {camera_notes.strip()}")
|
||||
|
||||
tag_list = self._split_keywords(pose_tags)
|
||||
if tag_list:
|
||||
@@ -361,20 +275,19 @@ class NoxPromptPoseMaster(PresetMixin):
|
||||
negatives: List[str] = []
|
||||
if include_negative_baseline:
|
||||
negatives.extend(POSE_BASELINE_NEGATIVES)
|
||||
negatives.extend(self._split_keywords(custom_negative_prompt))
|
||||
pose_negative = ", ".join(self._unique_sequence(negatives))
|
||||
|
||||
brief_parts: List[str] = []
|
||||
if pose_cfg.get("notes"):
|
||||
brief_parts.append(f"Pose note: {pose_cfg['notes']}")
|
||||
if camera_cfg.get("notes"):
|
||||
brief_parts.append(f"Camera: {camera_cfg['notes']}")
|
||||
if lighting_cfg.get("notes"):
|
||||
brief_parts.append(f"Lighting: {lighting_cfg['notes']}")
|
||||
if set_cfg.get("notes"):
|
||||
brief_parts.append(f"Set: {set_cfg['notes']}")
|
||||
if energy_cfg.get("notes"):
|
||||
brief_parts.append(f"Energy: {energy_cfg['notes']}")
|
||||
if camera_notes.strip():
|
||||
brief_parts.append(f"Camera: {camera_notes.strip()}")
|
||||
if expression_focus.strip():
|
||||
brief_parts.append(f"Expression: {expression_focus.strip()}")
|
||||
if movement_cue.strip():
|
||||
@@ -399,15 +312,14 @@ class NoxPromptPoseMaster(PresetMixin):
|
||||
guidance_parts.append(f"Safety gear: {safety_equipment.strip()}")
|
||||
for source in (
|
||||
pose_cfg.get("notes"),
|
||||
camera_cfg.get("notes"),
|
||||
lighting_cfg.get("notes"),
|
||||
set_cfg.get("notes"),
|
||||
energy_cfg.get("notes"),
|
||||
):
|
||||
if source:
|
||||
guidance_parts.append(source)
|
||||
if custom_guidance_note.strip():
|
||||
guidance_parts.append(custom_guidance_note.strip())
|
||||
if camera_notes.strip():
|
||||
guidance_parts.append(f"Camera: {camera_notes.strip()}")
|
||||
guidance_notes = " | ".join(self._unique_sequence(guidance_parts))
|
||||
|
||||
return pose_prompt, pose_negative, pose_brief, guidance_notes, preset_status
|
||||
@@ -453,31 +365,17 @@ class NoxPromptPoseMaster(PresetMixin):
|
||||
self,
|
||||
selection: str,
|
||||
options: Dict[str, Dict[str, str]],
|
||||
*,
|
||||
custom_prompt: str = "",
|
||||
custom_notes: str = "",
|
||||
) -> Dict[str, str]:
|
||||
key = self._match_option_key(selection, options)
|
||||
fallback = "None" if "None" in options else next(iter(options))
|
||||
source_key = key or fallback
|
||||
base = dict(options.get(source_key, {}))
|
||||
|
||||
prompt_parts = [
|
||||
part.strip()
|
||||
for part in [base.get("prompt", ""), custom_prompt]
|
||||
if part and part.strip()
|
||||
]
|
||||
base["prompt"] = ", ".join(self._unique_sequence(prompt_parts)) if prompt_parts else ""
|
||||
prompt = (base.get("prompt") or "").strip()
|
||||
base["prompt"] = prompt
|
||||
|
||||
notes_parts = [
|
||||
part.strip()
|
||||
for part in [base.get("notes", base.get("summary", "")), custom_notes]
|
||||
if part and part.strip()
|
||||
]
|
||||
if notes_parts:
|
||||
base["notes"] = " | ".join(self._unique_sequence(notes_parts))
|
||||
elif "notes" in base:
|
||||
base["notes"] = base.get("notes", "")
|
||||
notes_value = base.get("notes", base.get("summary", ""))
|
||||
base["notes"] = (notes_value or "").strip()
|
||||
|
||||
return base
|
||||
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Iterable, List, Sequence, Tuple
|
||||
|
||||
from ..common import (
|
||||
PresetMixin,
|
||||
PromptFragmentFilter,
|
||||
detect_prompt_contradictions,
|
||||
)
|
||||
|
||||
|
||||
class NoxPromptPromptInspector(PresetMixin):
|
||||
"""Clean, analyze, and summarize prompt fragments for reuse."""
|
||||
|
||||
_CATEGORY = "NoxPrompter/Analysis"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
profile_names = sorted(PromptFragmentFilter.PROFILE_PRESETS.keys())
|
||||
return {
|
||||
"required": {
|
||||
"prompt_text": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": "Detailed portrait of a fearless explorer."},
|
||||
),
|
||||
"split_mode": (
|
||||
["auto", "comma", "newline", "sentence"],
|
||||
{"default": "auto"},
|
||||
),
|
||||
"filter_profile": (
|
||||
profile_names + ["none"],
|
||||
{"default": "balanced"},
|
||||
),
|
||||
"normalize_case": ("BOOLEAN", {"default": False}),
|
||||
"remove_duplicates": ("BOOLEAN", {"default": True}),
|
||||
"max_fragments": ("INT", {"default": 24, "min": 1, "max": 99, "step": 1}),
|
||||
},
|
||||
"optional": {
|
||||
"include_negative": ("BOOLEAN", {"default": True}),
|
||||
"negative_prompt": ("STRING", {"multiline": True, "default": "low quality, blurry"}),
|
||||
"preset_action": (["none", "save", "load", "list"], {"default": "none"}),
|
||||
"preset_name": ("STRING", {"default": ""}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING")
|
||||
RETURN_NAMES = (
|
||||
"organized_prompt",
|
||||
"fragment_list",
|
||||
"insights",
|
||||
"metrics",
|
||||
"preset_status",
|
||||
)
|
||||
FUNCTION = "inspect_prompt"
|
||||
CATEGORY = _CATEGORY
|
||||
|
||||
def inspect_prompt(
|
||||
self,
|
||||
prompt_text: str,
|
||||
split_mode: str,
|
||||
filter_profile: str,
|
||||
normalize_case: bool,
|
||||
remove_duplicates: bool,
|
||||
max_fragments: int,
|
||||
include_negative: bool = True,
|
||||
negative_prompt: str = "",
|
||||
preset_action: str = "none",
|
||||
preset_name: str = "",
|
||||
) -> Tuple[str, str, str, str, str]:
|
||||
config = {
|
||||
"prompt_text": prompt_text,
|
||||
"split_mode": split_mode,
|
||||
"filter_profile": filter_profile,
|
||||
"normalize_case": normalize_case,
|
||||
"remove_duplicates": remove_duplicates,
|
||||
"max_fragments": max_fragments,
|
||||
"include_negative": include_negative,
|
||||
"negative_prompt": negative_prompt,
|
||||
}
|
||||
|
||||
config, preset_status = self._apply_preset_action(
|
||||
"prompt_inspector",
|
||||
preset_action,
|
||||
preset_name,
|
||||
config,
|
||||
)
|
||||
|
||||
prompt_text = config.get("prompt_text", prompt_text)
|
||||
split_mode = config.get("split_mode", split_mode)
|
||||
filter_profile = config.get("filter_profile", filter_profile)
|
||||
normalize_case = bool(config.get("normalize_case", normalize_case))
|
||||
remove_duplicates = bool(config.get("remove_duplicates", remove_duplicates))
|
||||
max_fragments = int(config.get("max_fragments", max_fragments))
|
||||
include_negative = bool(config.get("include_negative", include_negative))
|
||||
negative_prompt = config.get("negative_prompt", negative_prompt)
|
||||
|
||||
positive_fragments = self._split_fragments(prompt_text, split_mode)
|
||||
negative_fragments = (
|
||||
self._split_fragments(negative_prompt, split_mode)
|
||||
if include_negative and negative_prompt.strip()
|
||||
else []
|
||||
)
|
||||
|
||||
positive_fragments, removed_positive = self._normalize_fragments(
|
||||
positive_fragments,
|
||||
normalize_case,
|
||||
remove_duplicates,
|
||||
)
|
||||
negative_fragments, removed_negative = self._normalize_fragments(
|
||||
negative_fragments,
|
||||
normalize_case,
|
||||
remove_duplicates,
|
||||
)
|
||||
|
||||
organized_positive, filter_summary = self._apply_filter(
|
||||
positive_fragments,
|
||||
filter_profile,
|
||||
max_fragments,
|
||||
)
|
||||
|
||||
organized_prompt = self._assemble_prompt(organized_positive)
|
||||
fragment_list = self._render_fragment_list(organized_positive, negative_fragments)
|
||||
|
||||
warning_messages = detect_prompt_contradictions(organized_positive)
|
||||
insight_parts: List[str] = []
|
||||
if filter_summary:
|
||||
insight_parts.append(filter_summary)
|
||||
if removed_positive or removed_negative:
|
||||
insight_parts.append(
|
||||
f"Removed duplicates: +{removed_positive} positive, +{removed_negative} negative"
|
||||
)
|
||||
if warning_messages:
|
||||
insight_parts.extend(f"Warning: {warning}" for warning in warning_messages)
|
||||
if not insight_parts:
|
||||
insight_parts.append("Prompt fragments left unchanged.")
|
||||
insights = " | ".join(insight_parts)
|
||||
|
||||
metrics = self._build_metrics(
|
||||
prompt_text,
|
||||
organized_positive,
|
||||
negative_fragments,
|
||||
removed_positive + removed_negative,
|
||||
)
|
||||
|
||||
return organized_prompt, fragment_list, insights, metrics, preset_status
|
||||
|
||||
def _split_fragments(self, text: str, mode: str) -> List[str]:
|
||||
if not text:
|
||||
return []
|
||||
normalized = text.replace("\r", "\n")
|
||||
if mode == "comma":
|
||||
tokens = re.split(r"[\n,]", normalized)
|
||||
elif mode == "newline":
|
||||
tokens = re.split(r"\n+", normalized)
|
||||
elif mode == "sentence":
|
||||
tokens = re.split(r"(?<=[.!?])\s+|\n+", normalized)
|
||||
else: # auto
|
||||
tokens = re.split(r"[\n,]|(?<=[.!?])\s+", normalized)
|
||||
return [token.strip() for token in tokens if token and token.strip()]
|
||||
|
||||
def _normalize_fragments(
|
||||
self,
|
||||
fragments: Iterable[str],
|
||||
normalize_case: bool,
|
||||
remove_duplicates: bool,
|
||||
) -> Tuple[List[str], int]:
|
||||
prepared: List[str] = []
|
||||
seen = set()
|
||||
removed = 0
|
||||
for fragment in fragments:
|
||||
cleaned = fragment.strip()
|
||||
if not cleaned:
|
||||
continue
|
||||
if normalize_case:
|
||||
cleaned = cleaned.lower()
|
||||
key = cleaned.lower()
|
||||
if remove_duplicates and key in seen:
|
||||
removed += 1
|
||||
continue
|
||||
seen.add(key)
|
||||
prepared.append(cleaned)
|
||||
return prepared, removed
|
||||
|
||||
def _apply_filter(
|
||||
self,
|
||||
fragments: Sequence[str],
|
||||
profile: str,
|
||||
max_fragments: int,
|
||||
) -> Tuple[List[str], str]:
|
||||
profile = (profile or "").strip().lower()
|
||||
if not fragments:
|
||||
return [], ""
|
||||
if profile in {"", "none"}:
|
||||
limited = list(fragments)[:max_fragments]
|
||||
return limited, ""
|
||||
filterer = PromptFragmentFilter(profile)
|
||||
organized = filterer.organize(list(fragments))
|
||||
limited = organized[:max_fragments]
|
||||
return limited, filterer.summarize()
|
||||
|
||||
def _assemble_prompt(self, fragments: Sequence[str]) -> str:
|
||||
if not fragments:
|
||||
return ""
|
||||
sentences = []
|
||||
for fragment in fragments:
|
||||
cleaned = fragment.strip()
|
||||
if not cleaned:
|
||||
continue
|
||||
if cleaned[-1] not in ".!?":
|
||||
cleaned = f"{cleaned}."
|
||||
sentences.append(cleaned)
|
||||
return " ".join(sentences)
|
||||
|
||||
def _render_fragment_list(
|
||||
self,
|
||||
positive: Sequence[str],
|
||||
negative: Sequence[str],
|
||||
) -> str:
|
||||
lines: List[str] = []
|
||||
index = 1
|
||||
for fragment in positive:
|
||||
lines.append(f"{index}. [pos] {fragment}")
|
||||
index += 1
|
||||
for fragment in negative:
|
||||
lines.append(f"{index}. [neg] {fragment}")
|
||||
index += 1
|
||||
return "\n".join(lines)
|
||||
|
||||
def _build_metrics(
|
||||
self,
|
||||
prompt_text: str,
|
||||
positive: Sequence[str],
|
||||
negative: Sequence[str],
|
||||
duplicates_removed: int,
|
||||
) -> str:
|
||||
character_count = len(prompt_text.strip())
|
||||
word_count = len(prompt_text.split())
|
||||
positive_count = len(positive)
|
||||
negative_count = len(negative)
|
||||
avg_length = (
|
||||
sum(len(item) for item in positive) / positive_count
|
||||
if positive_count
|
||||
else 0
|
||||
)
|
||||
segments = [
|
||||
f"Positive fragments: {positive_count}",
|
||||
f"Negative fragments: {negative_count}",
|
||||
f"Characters: {character_count}",
|
||||
f"Words: {word_count}",
|
||||
f"Avg fragment length: {avg_length:.1f}",
|
||||
]
|
||||
if duplicates_removed:
|
||||
segments.append(f"Duplicates removed: {duplicates_removed}")
|
||||
return " | ".join(segments)
|
||||
|
||||
|
||||
__all__ = ["NoxPromptPromptInspector"]
|
||||
@@ -1,8 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Tuple
|
||||
from ..common import PresetMixin, _resolve_with_custom, _split_tokens, _format_notes
|
||||
from ..constants import * # noqa: F403
|
||||
from ..common import PresetMixin, _resolve_option, option_keys, _split_tokens, _format_notes
|
||||
from ..constants import (
|
||||
WARDROBE_ACCESSORY_OPTIONS,
|
||||
WARDROBE_BASE_GARMENT_OPTIONS,
|
||||
WARDROBE_COLORWAY_OPTIONS,
|
||||
WARDROBE_CULTURE_OPTIONS,
|
||||
WARDROBE_FABRIC_OPTIONS,
|
||||
WARDROBE_FOOTWEAR_OPTIONS,
|
||||
WARDROBE_LAYER_OPTIONS,
|
||||
WARDROBE_PATTERN_OPTIONS,
|
||||
WARDROBE_SILHOUETTE_OPTIONS,
|
||||
WARDROBE_WEAR_STATE_OPTIONS,
|
||||
)
|
||||
|
||||
class NoxPromptWardrobeDesigner(PresetMixin):
|
||||
"""Generate advanced wardrobe breakdowns with styling guidance."""
|
||||
@@ -11,35 +21,64 @@ class NoxPromptWardrobeDesigner(PresetMixin):
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"base_garment": (list(WARDROBE_BASE_GARMENT_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Battle Dress"}),
|
||||
"silhouette": (list(WARDROBE_SILHOUETTE_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "A-Line"}),
|
||||
"colorway": (list(WARDROBE_COLORWAY_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Monochrome Obsidian"}),
|
||||
"custom_prompt": (
|
||||
"STRING",
|
||||
{
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
},
|
||||
),
|
||||
"base_garment": (
|
||||
option_keys(WARDROBE_BASE_GARMENT_OPTIONS),
|
||||
{"default": "Battle Dress"},
|
||||
),
|
||||
"silhouette": (
|
||||
option_keys(WARDROBE_SILHOUETTE_OPTIONS),
|
||||
{"default": "A-Line"},
|
||||
),
|
||||
"colorway": (
|
||||
option_keys(WARDROBE_COLORWAY_OPTIONS),
|
||||
{"default": "Monochrome Obsidian"},
|
||||
),
|
||||
},
|
||||
"optional": {
|
||||
"layering": (list(WARDROBE_LAYER_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Armor Plating"}),
|
||||
"fabric": (list(WARDROBE_FABRIC_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Ballistic Weave"}),
|
||||
"pattern": (list(WARDROBE_PATTERN_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Fractal"}),
|
||||
"wear_state": (list(WARDROBE_WEAR_STATE_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Battle-Worn"}),
|
||||
"culture_inspiration": (list(WARDROBE_CULTURE_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Solar Court"}),
|
||||
"footwear": (list(WARDROBE_FOOTWEAR_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Combat Boots"}),
|
||||
"primary_accessory": (list(WARDROBE_ACCESSORY_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Relic Pendant"}),
|
||||
"secondary_accessory": (list(WARDROBE_ACCESSORY_OPTIONS.keys()) + [CUSTOM_OPTION], {"default": "Utility Belt"}),
|
||||
"layering": (
|
||||
option_keys(WARDROBE_LAYER_OPTIONS),
|
||||
{"default": "Armor Plating"},
|
||||
),
|
||||
"fabric": (
|
||||
option_keys(WARDROBE_FABRIC_OPTIONS),
|
||||
{"default": "Ballistic Weave"},
|
||||
),
|
||||
"pattern": (
|
||||
option_keys(WARDROBE_PATTERN_OPTIONS),
|
||||
{"default": "Fractal"},
|
||||
),
|
||||
"wear_state": (
|
||||
option_keys(WARDROBE_WEAR_STATE_OPTIONS),
|
||||
{"default": "Battle-Worn"},
|
||||
),
|
||||
"culture_inspiration": (
|
||||
option_keys(WARDROBE_CULTURE_OPTIONS),
|
||||
{"default": "Solar Court"},
|
||||
),
|
||||
"footwear": (
|
||||
option_keys(WARDROBE_FOOTWEAR_OPTIONS),
|
||||
{"default": "Combat Boots"},
|
||||
),
|
||||
"primary_accessory": (
|
||||
option_keys(WARDROBE_ACCESSORY_OPTIONS),
|
||||
{"default": "Relic Pendant"},
|
||||
),
|
||||
"secondary_accessory": (
|
||||
option_keys(WARDROBE_ACCESSORY_OPTIONS),
|
||||
{"default": "Utility Belt"},
|
||||
),
|
||||
"functional_focus": ("STRING", {"default": ""}),
|
||||
"accent_materials": ("STRING", {"multiline": True, "default": ""}),
|
||||
"motion_considerations": ("STRING", {"multiline": True, "default": ""}),
|
||||
"finishing_details": ("STRING", {"multiline": True, "default": ""}),
|
||||
"additional_accessories": ("STRING", {"multiline": True, "default": ""}),
|
||||
"base_garment_custom": ("STRING", {"default": ""}),
|
||||
"silhouette_custom": ("STRING", {"default": ""}),
|
||||
"colorway_custom": ("STRING", {"default": ""}),
|
||||
"layering_custom": ("STRING", {"default": ""}),
|
||||
"fabric_custom": ("STRING", {"default": ""}),
|
||||
"pattern_custom": ("STRING", {"default": ""}),
|
||||
"wear_state_custom": ("STRING", {"default": ""}),
|
||||
"culture_inspiration_custom": ("STRING", {"default": ""}),
|
||||
"footwear_custom": ("STRING", {"default": ""}),
|
||||
"primary_accessory_custom": ("STRING", {"default": ""}),
|
||||
"secondary_accessory_custom": ("STRING", {"default": ""}),
|
||||
"preset_action": (["none", "save", "load", "list"], {"default": "none"}),
|
||||
"preset_name": ("STRING", {"default": ""}),
|
||||
},
|
||||
@@ -52,37 +91,28 @@ class NoxPromptWardrobeDesigner(PresetMixin):
|
||||
|
||||
def design(
|
||||
self,
|
||||
base_garment,
|
||||
silhouette,
|
||||
colorway,
|
||||
layering="Armor Plating",
|
||||
fabric="Ballistic Weave",
|
||||
pattern="Fractal",
|
||||
wear_state="Battle-Worn",
|
||||
culture_inspiration="Solar Court",
|
||||
footwear="Combat Boots",
|
||||
primary_accessory="Relic Pendant",
|
||||
secondary_accessory="Utility Belt",
|
||||
functional_focus="",
|
||||
accent_materials="",
|
||||
motion_considerations="",
|
||||
finishing_details="",
|
||||
additional_accessories="",
|
||||
base_garment_custom="",
|
||||
silhouette_custom="",
|
||||
colorway_custom="",
|
||||
layering_custom="",
|
||||
fabric_custom="",
|
||||
pattern_custom="",
|
||||
wear_state_custom="",
|
||||
culture_inspiration_custom="",
|
||||
footwear_custom="",
|
||||
primary_accessory_custom="",
|
||||
secondary_accessory_custom="",
|
||||
preset_action="none",
|
||||
preset_name="",
|
||||
custom_prompt: str,
|
||||
base_garment: str = "Battle Dress",
|
||||
silhouette: str = "A-Line",
|
||||
colorway: str = "Monochrome Obsidian",
|
||||
layering: str = "Armor Plating",
|
||||
fabric: str = "Ballistic Weave",
|
||||
pattern: str = "Fractal",
|
||||
wear_state: str = "Battle-Worn",
|
||||
culture_inspiration: str = "Solar Court",
|
||||
footwear: str = "Combat Boots",
|
||||
primary_accessory: str = "Relic Pendant",
|
||||
secondary_accessory: str = "Utility Belt",
|
||||
functional_focus: str = "",
|
||||
accent_materials: str = "",
|
||||
motion_considerations: str = "",
|
||||
finishing_details: str = "",
|
||||
additional_accessories: str = "",
|
||||
preset_action: str = "none",
|
||||
preset_name: str = "",
|
||||
):
|
||||
config = {
|
||||
"custom_prompt": custom_prompt,
|
||||
"base_garment": base_garment,
|
||||
"silhouette": silhouette,
|
||||
"colorway": colorway,
|
||||
@@ -99,17 +129,6 @@ class NoxPromptWardrobeDesigner(PresetMixin):
|
||||
"motion_considerations": motion_considerations,
|
||||
"finishing_details": finishing_details,
|
||||
"additional_accessories": additional_accessories,
|
||||
"base_garment_custom": base_garment_custom,
|
||||
"silhouette_custom": silhouette_custom,
|
||||
"colorway_custom": colorway_custom,
|
||||
"layering_custom": layering_custom,
|
||||
"fabric_custom": fabric_custom,
|
||||
"pattern_custom": pattern_custom,
|
||||
"wear_state_custom": wear_state_custom,
|
||||
"culture_inspiration_custom": culture_inspiration_custom,
|
||||
"footwear_custom": footwear_custom,
|
||||
"primary_accessory_custom": primary_accessory_custom,
|
||||
"secondary_accessory_custom": secondary_accessory_custom,
|
||||
}
|
||||
|
||||
config, preset_status = self._apply_preset_action(
|
||||
@@ -119,6 +138,7 @@ class NoxPromptWardrobeDesigner(PresetMixin):
|
||||
config,
|
||||
)
|
||||
|
||||
custom_prompt = config.get("custom_prompt", custom_prompt)
|
||||
base_garment = config.get("base_garment", base_garment)
|
||||
silhouette = config.get("silhouette", silhouette)
|
||||
colorway = config.get("colorway", colorway)
|
||||
@@ -135,52 +155,67 @@ class NoxPromptWardrobeDesigner(PresetMixin):
|
||||
motion_considerations = config.get("motion_considerations", motion_considerations)
|
||||
finishing_details = config.get("finishing_details", finishing_details)
|
||||
additional_accessories = config.get("additional_accessories", additional_accessories)
|
||||
base_garment_custom = config.get("base_garment_custom", base_garment_custom)
|
||||
silhouette_custom = config.get("silhouette_custom", silhouette_custom)
|
||||
colorway_custom = config.get("colorway_custom", colorway_custom)
|
||||
layering_custom = config.get("layering_custom", layering_custom)
|
||||
fabric_custom = config.get("fabric_custom", fabric_custom)
|
||||
pattern_custom = config.get("pattern_custom", pattern_custom)
|
||||
wear_state_custom = config.get("wear_state_custom", wear_state_custom)
|
||||
culture_inspiration_custom = config.get("culture_inspiration_custom", culture_inspiration_custom)
|
||||
footwear_custom = config.get("footwear_custom", footwear_custom)
|
||||
primary_accessory_custom = config.get("primary_accessory_custom", primary_accessory_custom)
|
||||
secondary_accessory_custom = config.get("secondary_accessory_custom", secondary_accessory_custom)
|
||||
|
||||
base_prompt, base_notes = _resolve_with_custom(base_garment, base_garment_custom, WARDROBE_BASE_GARMENT_OPTIONS)
|
||||
silhouette_prompt, silhouette_notes = _resolve_with_custom(silhouette, silhouette_custom, WARDROBE_SILHOUETTE_OPTIONS)
|
||||
color_prompt, color_notes = _resolve_with_custom(colorway, colorway_custom, WARDROBE_COLORWAY_OPTIONS)
|
||||
layering_prompt, layering_notes = _resolve_with_custom(layering, layering_custom, WARDROBE_LAYER_OPTIONS)
|
||||
fabric_prompt, fabric_notes = _resolve_with_custom(fabric, fabric_custom, WARDROBE_FABRIC_OPTIONS)
|
||||
pattern_prompt, pattern_notes = _resolve_with_custom(pattern, pattern_custom, WARDROBE_PATTERN_OPTIONS)
|
||||
wear_prompt, wear_notes = _resolve_with_custom(wear_state, wear_state_custom, WARDROBE_WEAR_STATE_OPTIONS)
|
||||
culture_prompt, culture_notes = _resolve_with_custom(culture_inspiration, culture_inspiration_custom, WARDROBE_CULTURE_OPTIONS)
|
||||
footwear_prompt, footwear_notes = _resolve_with_custom(footwear, footwear_custom, WARDROBE_FOOTWEAR_OPTIONS)
|
||||
primary_prompt, primary_notes = _resolve_with_custom(primary_accessory, primary_accessory_custom, WARDROBE_ACCESSORY_OPTIONS)
|
||||
secondary_prompt, secondary_notes = _resolve_with_custom(secondary_accessory, secondary_accessory_custom, WARDROBE_ACCESSORY_OPTIONS)
|
||||
base_prompt, base_notes = _resolve_option(base_garment, WARDROBE_BASE_GARMENT_OPTIONS)
|
||||
silhouette_prompt, silhouette_notes = _resolve_option(silhouette, WARDROBE_SILHOUETTE_OPTIONS)
|
||||
color_prompt, color_notes = _resolve_option(colorway, WARDROBE_COLORWAY_OPTIONS)
|
||||
layering_prompt, layering_notes = _resolve_option(layering, WARDROBE_LAYER_OPTIONS)
|
||||
fabric_prompt, fabric_notes = _resolve_option(fabric, WARDROBE_FABRIC_OPTIONS)
|
||||
pattern_prompt, pattern_notes = _resolve_option(pattern, WARDROBE_PATTERN_OPTIONS)
|
||||
wear_prompt, wear_notes = _resolve_option(wear_state, WARDROBE_WEAR_STATE_OPTIONS)
|
||||
culture_prompt, culture_notes = _resolve_option(culture_inspiration, WARDROBE_CULTURE_OPTIONS)
|
||||
footwear_prompt, footwear_notes = _resolve_option(footwear, WARDROBE_FOOTWEAR_OPTIONS)
|
||||
primary_prompt, primary_notes = _resolve_option(primary_accessory, WARDROBE_ACCESSORY_OPTIONS)
|
||||
secondary_prompt, secondary_notes = _resolve_option(secondary_accessory, WARDROBE_ACCESSORY_OPTIONS)
|
||||
|
||||
accent_list = _split_tokens(accent_materials)
|
||||
motion_list = _split_tokens(motion_considerations)
|
||||
finishing_list = _split_tokens(finishing_details)
|
||||
extra_accessories = _split_tokens(additional_accessories)
|
||||
|
||||
wardrobe_prompt_parts = [
|
||||
base_prompt,
|
||||
silhouette_prompt,
|
||||
fabric_prompt,
|
||||
layering_prompt,
|
||||
color_prompt,
|
||||
pattern_prompt,
|
||||
wear_prompt,
|
||||
culture_prompt,
|
||||
footwear_prompt,
|
||||
]
|
||||
if accent_list:
|
||||
wardrobe_prompt_parts.append("accents: " + ", ".join(accent_list))
|
||||
if finishing_list:
|
||||
wardrobe_prompt_parts.append("finishing: " + ", ".join(finishing_list))
|
||||
def _with_fallback(prompt: str, selection: str) -> str:
|
||||
cleaned = (prompt or "").strip()
|
||||
if cleaned:
|
||||
return cleaned
|
||||
selection_text = (selection or "").strip()
|
||||
if selection_text.lower() == "none":
|
||||
return ""
|
||||
return selection_text
|
||||
|
||||
wardrobe_prompt = ", ".join(part for part in wardrobe_prompt_parts if part).strip()
|
||||
base_text = _with_fallback(base_prompt, base_garment)
|
||||
silhouette_text = _with_fallback(silhouette_prompt, silhouette)
|
||||
fabric_text = _with_fallback(fabric_prompt, fabric)
|
||||
layering_text = _with_fallback(layering_prompt, layering)
|
||||
color_text = _with_fallback(color_prompt, colorway)
|
||||
pattern_text = _with_fallback(pattern_prompt, pattern)
|
||||
wear_text = _with_fallback(wear_prompt, wear_state)
|
||||
culture_text = _with_fallback(culture_prompt, culture_inspiration)
|
||||
footwear_text = _with_fallback(footwear_prompt, footwear)
|
||||
primary_text = _with_fallback(primary_prompt, primary_accessory)
|
||||
secondary_text = _with_fallback(secondary_prompt, secondary_accessory)
|
||||
|
||||
prompt_fragments = []
|
||||
custom_fragment = (custom_prompt or "").strip()
|
||||
if custom_fragment:
|
||||
prompt_fragments.append(custom_fragment)
|
||||
prompt_fragments.extend(
|
||||
[
|
||||
base_text,
|
||||
silhouette_text,
|
||||
fabric_text,
|
||||
layering_text,
|
||||
color_text,
|
||||
pattern_text,
|
||||
wear_text,
|
||||
culture_text,
|
||||
footwear_text,
|
||||
]
|
||||
)
|
||||
if accent_list:
|
||||
prompt_fragments.append("accents: " + ", ".join(accent_list))
|
||||
if finishing_list:
|
||||
prompt_fragments.append("finishing: " + ", ".join(finishing_list))
|
||||
|
||||
wardrobe_prompt = ", ".join(part for part in prompt_fragments if part).strip()
|
||||
if wardrobe_prompt and wardrobe_prompt[-1] not in ".!?":
|
||||
wardrobe_prompt += "."
|
||||
if functional_focus.strip():
|
||||
@@ -189,22 +224,23 @@ class NoxPromptWardrobeDesigner(PresetMixin):
|
||||
wardrobe_prompt += f" Movement considerations: {', '.join(motion_list)}."
|
||||
|
||||
styling_segments = [
|
||||
_format_notes("Base", base_notes, base_prompt),
|
||||
_format_notes("Silhouette", silhouette_notes, silhouette_prompt),
|
||||
_format_notes("Fabric", fabric_notes, fabric_prompt),
|
||||
_format_notes("Layering", layering_notes, layering_prompt),
|
||||
_format_notes("Colorway", color_notes, color_prompt),
|
||||
_format_notes("Pattern", pattern_notes, pattern_prompt),
|
||||
_format_notes("Wear State", wear_notes, wear_prompt),
|
||||
_format_notes("Culture", culture_notes, culture_prompt),
|
||||
_format_notes("Footwear", footwear_notes, footwear_prompt),
|
||||
_format_notes("Base", base_notes, base_text),
|
||||
_format_notes("Silhouette", silhouette_notes, silhouette_text),
|
||||
_format_notes("Fabric", fabric_notes, fabric_text),
|
||||
_format_notes("Layering", layering_notes, layering_text),
|
||||
_format_notes("Colorway", color_notes, color_text),
|
||||
_format_notes("Pattern", pattern_notes, pattern_text),
|
||||
_format_notes("Wear State", wear_notes, wear_text),
|
||||
_format_notes("Culture", culture_notes, culture_text),
|
||||
_format_notes("Footwear", footwear_notes, footwear_text),
|
||||
]
|
||||
styling_notes = " | ".join(segment for segment in styling_segments if segment)
|
||||
|
||||
accessory_items = [
|
||||
primary_prompt,
|
||||
secondary_prompt if secondary_accessory else "",
|
||||
]
|
||||
accessory_items = []
|
||||
if primary_text:
|
||||
accessory_items.append(primary_text)
|
||||
if secondary_text:
|
||||
accessory_items.append(secondary_text)
|
||||
accessory_notes = [note for note in [primary_notes, secondary_notes] if note]
|
||||
if extra_accessories:
|
||||
accessory_items.append("Additional: " + ", ".join(extra_accessories))
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# Prompt Generator
|
||||
|
||||
This project is a simple offline webpage for generating prompts. It consists of an HTML file, a CSS file for styling, and a JavaScript file for functionality.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
prompt-generator
|
||||
├── css
|
||||
│ └── styles.css
|
||||
├── js
|
||||
│ └── app.js
|
||||
├── index.html
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Files Description
|
||||
|
||||
- **index.html**: The main HTML document for the prompt generator webpage. It includes the structure of the webpage and links to the CSS and JavaScript files.
|
||||
|
||||
- **css/styles.css**: Contains the styles for the webpage, defining the layout, colors, fonts, and other visual aspects.
|
||||
|
||||
- **js/app.js**: Contains the JavaScript code that handles user interactions, generates prompts based on user input, and updates the webpage dynamically.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Open `index.html` in a web browser to access the prompt generator.
|
||||
2. Enter your desired inputs in the provided fields.
|
||||
3. Click the generate button to create a prompt based on your inputs.
|
||||
4. The generated prompt will be displayed on the webpage.
|
||||
|
||||
## Setup
|
||||
|
||||
No additional setup is required. Simply download the project files and open `index.html` in your preferred web browser to start using the prompt generator.
|
||||
@@ -0,0 +1,50 @@
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.prompt-output {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
background-color: #e9ecef;
|
||||
border-left: 5px solid #007bff;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Prompt Generator</title>
|
||||
<link rel="stylesheet" href="css/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Prompt Generator</h1>
|
||||
<p>Generate creative prompts for your projects!</p>
|
||||
</header>
|
||||
<main>
|
||||
<section>
|
||||
<label for="promptInput">Enter your prompt:</label>
|
||||
<input type="text" id="promptInput" placeholder="Type your prompt here...">
|
||||
<button id="generateButton">Generate Prompt</button>
|
||||
</section>
|
||||
<section id="outputSection">
|
||||
<h2>Your Generated Prompt:</h2>
|
||||
<p id="generatedPrompt"></p>
|
||||
</section>
|
||||
</main>
|
||||
<footer>
|
||||
<p>© 2023 Prompt Generator. All rights reserved.</p>
|
||||
</footer>
|
||||
<script src="js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,20 @@
|
||||
// This file contains the JavaScript code for the prompt generator.
|
||||
// It handles user interactions, generates prompts based on user input, and updates the webpage dynamically.
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const promptButton = document.getElementById("generate-prompt");
|
||||
const promptOutput = document.getElementById("prompt-output");
|
||||
|
||||
const prompts = [
|
||||
"What inspires you today?",
|
||||
"Describe your ideal day.",
|
||||
"What is a challenge you want to overcome?",
|
||||
"Write about a memorable experience.",
|
||||
"What are your goals for the next month?"
|
||||
];
|
||||
|
||||
promptButton.addEventListener("click", function() {
|
||||
const randomIndex = Math.floor(Math.random() * prompts.length);
|
||||
promptOutput.textContent = prompts[randomIndex];
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(REPO_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(REPO_ROOT))
|
||||
|
||||
import noxprompter.nodes as nodes # noqa: E402
|
||||
|
||||
|
||||
def collect_input_keys() -> Dict[str, Dict[str, list[str]]]:
|
||||
summary: Dict[str, Dict[str, list[str]]] = {}
|
||||
for cls in nodes.NODE_CLASSES:
|
||||
if not hasattr(cls, "INPUT_TYPES"):
|
||||
continue
|
||||
input_types = cls.INPUT_TYPES()
|
||||
normalized: Dict[str, list[str]] = {}
|
||||
for bucket, mapping in input_types.items():
|
||||
normalized[bucket] = list(mapping.keys())
|
||||
summary[cls.__name__] = normalized
|
||||
return summary
|
||||
|
||||
|
||||
def main() -> None:
|
||||
summary = collect_input_keys()
|
||||
print(json.dumps(summary, indent=2, sort_keys=True))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user