Author SHA1 Message Date
jetthuangai f4843d30d3 docs: bilingual EN/VI guide, node cards, examples and new README
- README rewritten: banner, highlights with real outputs, install incl.
  ONNX Runtime and extra_model_paths keys, quick start, full catalog,
  licence summary, PDF guide links
- docs/en and docs/vi: guide home + 13 pages covering all 69 nodes (card,
  inputs/outputs from the live definitions, behaviour, recipes, tips)
- docs/assets: 69 node cards generated from INPUT_TYPES, 14 example images
  and a banner rendered from real node outputs on public-domain/CC0 photos
- docs/_tools: card, example, spec and PDF builders (reportlab) so the docs
  can be regenerated from the code
2026-10-18 09:17:48 +07:00
jetthuangai 8eb38a81bf fix: matching context base is the parent of the search roots 2026-10-18 09:12:33 +07:00
jetthuangai e9ff4c2390 fix: score matching context against one base per request
The base was the common folder of whatever happened to match, so an unrelated
folder elsewhere in the library could move it and flip the pick. It is now the
common folder of the request's search roots, passed explicitly to every
ranking. Adds tests for the unrelated-folder and twin-root cases.
2026-10-18 09:12:33 +07:00
jetthuangai 1687e14d08 fix: Load images matching ranks context below shared ancestor folders (1.6.2)
The context score compared absolute paths, so folders the source and the
library share (D:/Photos) counted as much as the Nu/Nam group and a source in
Source/Nu matched Library/Nam/<subject> by alphabetical tie-break. Candidates
are now scored on their path below the folder they all share.
2026-10-18 09:12:33 +07:00
jetthuangai e28b9c3810 Bump version 1.6.1 2026-10-18 08:37:20 +07:00
jetthuangai 5d2e6c5b9c refactor: split image tools, share helpers, harden routes (no node changes)
- nodes/image/image_tools_nodes.py (1776 lines) split into save_image_path,
  folder_loaders, layer_composite, image_label, image_compare and
  image_resize_unit; folder helpers in core/image_folders.py (set-based
  dedupe), image helpers in core/image_ops.py
- One tensor -> PIL converter (core.utils.tensor_to_pil, rgb=/keep_alpha=)
  that keeps the tensor's dtype; bf16 supported; duplicates removed from grid,
  large save and preview cache
- core/mask_ops.mask_bbox replaces five bbox copies; model helpers moved to
  core/model_files.py; Pillow fromarray(mode=) uses removed
- Routes moved to core/folder_routes.py: no absolute paths in responses,
  recursive honoured, proper status codes, scans off the event loop; large
  preview routes reject malformed JSON and hide paths
- web/nh_widget_utils.js shared by all extensions; widgets hidden via the
  frontend's `hidden` property when available; folder image count honours
  recursive
- A wired Load Images Folder path no longer fingerprints ComfyUI's input folder
- Node ids, inputs and outputs unchanged (69 nodes); new tests for routes,
  web helpers, mask ops, channel handling and Pillow usage
2026-10-18 08:37:20 +07:00
jetthuangai e8a0f8c005 Bump version 1.6.0 2026-10-18 07:31:58 +07:00
jetthuangai d128024dff perf: lazy switches, faster morphology, smarter caching and VTON VRAM release
- If/Else, Switch N and Random Choice use lazy inputs: only the chosen
  branch is executed (same choice as before; connected inputs count even
  when their upstream returns nothing)
- Mask Morphology rewritten on torch: soft masks kept, replicate borders,
  even kernels act as the next odd size; large kernels ~100x faster
- Layer Stack is cached like a pure node; Load Images Matching caches on a
  file signature, searches only inside its root, never walks drive/share
  roots or the models folder, and on a miss returns found=False plus the
  new optional lazy fallback_images (or blocks images)
- Indexed loaders reuse the model ComfyUI still holds via a weak reference
  (strong only with --cache-classic); VAEs keep file metadata
- VTON: keep_model_loaded (default off) releases ONNX Runtime GPU sessions
  after runs that really used CUDA; ComfyUI is asked to make room first
- SegFormer free_memory now targets ComfyUI's device (cuda:0) so models are
  actually unloaded, and adds extra_reserved_memory
- Model downloads pinned to tested Hugging Face revisions with
  allow_patterns; partial VTON folders are repaired
- nh_nodes (.safetensors) and vton_mask (.onnx) folder names honour
  extra_model_paths.yaml; existing installs keep working
- Large preview/compare viewer: batch navigation with buttons and arrow keys
- Real PromptExecutor integration tests (CPU) for lazy and blocking paths
2026-10-18 07:31:58 +07:00
jetthuangai e21e2ee7ef Bump version 1.5.0 2026-10-18 04:40:24 +07:00
jetthuangai 71e42b303b fix: correctness fixes across image, resolution, logic, text and mask nodes
Image
- VTON, Face Paste and Agnostic Image process the whole batch (batch-1
  inputs broadcast); VTON converts input to RGB and names the batch index
  when no person is detected
- Values are clipped before 8-bit conversion (no wrap-around to black)
- Mask-Aware Resize honours the mode with an empty mask and clips Lanczos
  overshoot; Agnostic Image resizes/accepts 2-D masks, keeps soft masks and
  uses deterministic noise
- Layer Layout/Stack keep RGBA layer alpha; pad colours accept #RRGGBBAA
  (opaque by default instead of copying the blue value into alpha)
- Save Image Path: prefix/new folder cannot escape the chosen folder
  (Windows colons refused); position expressions use the shared safe
  evaluator (no more 9**9**9 freeze, no ast.Num)
- Save Large Image checks WEBP/JPEG limits before writing and respects
  --disable-metadata; Image Label caches fonts; Grid guards on free memory;
  Cache Manager re-runs every queue

Resolution
- Remove Resize Padding no longer stretches cover_crop results: with
  original_size the image is scaled back uniformly into the original frame
- Lanczos/box/hamming keep float precision, clipped between passes
- Picker reports stale presets/model names in info

Logic, text, lists, masks
- New core/safe_eval.py: float-only arithmetic, whitelist, no runaway ints;
  Math Eval errors name the expression, x/0 -> 0 with a warning
- Random Choice weights follow slots; literal seeds in four seeded nodes
- Prompt Template no longer glues words around empty variables; Regex
  Extract handles unmatched groups; Pack Universal packs by slot; Compare
  'in' is a plain membership test; Batch Merge aligns RGB/RGBA and never
  rounds a side to 0
- Mask Aspect Ratio Match placement fixed and batched; VTON garment boxes
  ignore undetected keypoints and fall back to the garment contour
- Universal Slider Builder marked DEPRECATED

Adds docs/CHANGELOG.md and ~460 regression tests.
2026-10-18 04:40:24 +07:00
187 changed files with 25950 additions and 2748 deletions
+113 -319
View File
@@ -1,354 +1,148 @@
<p align="center"><img src="docs/assets/banner.jpg" alt="NH-Nodes — image, mask, resolution and workflow nodes for ComfyUI" width="100%"></p>
<p align="center">
<a href="https://registry.comfy.org/nodes/nh-nodes"><img src="https://img.shields.io/badge/ComfyUI_Registry-nh--nodes-2563eb" alt="ComfyUI Registry"></a>
<a href="https://github.com/jetthuangai/NH-Nodes/actions/workflows/tests.yml"><img src="https://github.com/jetthuangai/NH-Nodes/actions/workflows/tests.yml/badge.svg" alt="Tests"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT_(see_notices)-16a34a" alt="License"></a>
<a href="docs/en/README.md"><img src="https://img.shields.io/badge/Docs-EN_%7C_VI-0f766e" alt="Documentation"></a>
</p>
<p align="center"><b>English</b> · <a href="docs/vi/README.md">Tiếng Việt</a></p>
# NH-Nodes for ComfyUI
Production-focused custom nodes for ComfyUI workflows: image loading, tiling,
compositing, mask utilities, smart resize, logic routing, prompt/text tooling,
batch helpers, indexed model loaders, garment/portrait segmentation, and VTON
preprocessing.
**69 production-focused nodes** for ComfyUI: load and save image sets with their metadata, tile and composite large
images, pick the right resolution for current models and put results back into the original frame, refine masks,
segment garments and faces, build VTON masks, route workflows with lazy switches, generate prompts, and test many
LoRAs or models from one node.
[![ComfyUI Registry](https://img.shields.io/badge/ComfyUI_Registry-nh--nodes-2563eb)](https://registry.comfy.org/nodes/nh-nodes)
[![GitHub](https://img.shields.io/badge/GitHub-jetthuangai%2FNH--Nodes-111827)](https://github.com/jetthuangai/NH-Nodes)
[![License: MIT](https://img.shields.io/badge/License-MIT-16a34a.svg)](LICENSE)
Every node is documented with a visual card, its inputs and outputs, and examples — in
[English](docs/en/README.md) and [Vietnamese](docs/vi/README.md).
- **Author:** [jetthuang.com](https://jetthuang.com)
- **Hugging Face:** [nhathoangfoto](https://huggingface.co/nhathoangfoto)
- **Support:** [PayPal](https://paypal.me/nhathoangfoto)
## Highlights
---
## Why NH-Nodes
NH-Nodes is built for practical workflow construction. It focuses on reusable
building blocks that remove repetitive graph work:
- Load images with filename/path metadata.
- Split images into tiles, upscale or process tiles, then stitch them back.
- Build image grids, layer composites, comparisons, and labeled previews.
- Resize images and masks with predictable aspect-ratio behavior.
- Route workflow branches with boolean logic and generic data switches.
- Generate prompts from templates, regex, lists, schedules, and text utilities.
- Pick indexed LoRA/diffusion models for larger configurable workflows.
- Prepare VTON masks, agnostic images, DensePose/parsing outputs, and related masks.
---
<table>
<tr>
<td width="50%"><a href="docs/en/resolution.md"><img src="docs/assets/examples/resize-modes.jpg" alt="Resize modes"></a><br>
<b>Resolution done right</b> — presets for FLUX.2, Qwen-Image and Z-Image, cover / contain / stretch resizing, and an
exact round trip back to the original frame.</td>
<td width="50%"><a href="docs/en/image-compose.md"><img src="docs/assets/examples/tile-untile.jpg" alt="Tile and untile"></a><br>
<b>Large images</b> — split into overlapping tiles, process them, and blend them back seamlessly; deep-zoom preview
for images too big for the canvas.</td>
</tr>
<tr>
<td><a href="docs/en/vision.md"><img src="docs/assets/examples/garment-segment.jpg" alt="Garment segmentation"></a><br>
<b>Segmentation</b> — garment and face-part masks from SegFormer models, plus VTON garment masks with pose and
parsing.</td>
<td><a href="docs/en/mask.md"><img src="docs/assets/examples/mask-morphology.jpg" alt="Mask morphology"></a><br>
<b>Mask tools</b> — fast grow / shrink on the CPU or the GPU, hole filling, soft edges, bounding boxes and
aspect-ratio matching.</td>
</tr>
<tr>
<td><a href="docs/en/image-compose.md"><img src="docs/assets/examples/layer-layout.jpg" alt="Layer layout"></a><br>
<b>Compositing</b> — layouts, layer stacks with alpha, labelled grids and side-by-side comparisons.</td>
<td><a href="docs/en/logic.md"><img src="docs/assets/examples/grid-composite.jpg" alt="Grid composite"></a><br>
<b>Workflow logic</b> — lazy If/Else and switches that run only the chosen branch, safe math, counters, lists and
prompt tools.</td>
</tr>
</table>
## Installation
### ComfyUI Manager
**ComfyUI Manager** — search for `NH-Nodes`, install, restart ComfyUI.
Search for `NH-Nodes`, install, then restart ComfyUI.
### Comfy CLI
**Comfy CLI**
```bash
comfy node install nh-nodes
```
### Manual
**Manual**
```bash
cd ComfyUI/custom_nodes
git clone https://github.com/jetthuangai/NH-Nodes.git
cd NH-Nodes
pip install -r requirements.txt
python -m pip install -r requirements.txt
```
Restart ComfyUI after installation.
Restart ComfyUI afterwards. Requirements: a recent ComfyUI (tested with 0.41) and Python 3.10+.
---
### Optional: VTON and segmentation models
## Featured Workflows
- **VTON Ultimate Processor** needs ONNX Runtime, which is not installed automatically (the CPU wheel would overwrite an
existing GPU build). Install one of them into ComfyUI's Python:
`python -m pip install onnxruntime-gpu` (NVIDIA) or `python -m pip install onnxruntime` (CPU).
- **Garment Segment**, **Portrait Segment** and **VTON** download their models from Hugging Face on first use (pinned
revisions, only the needed files). To keep models elsewhere, add these keys to `extra_model_paths.yaml`:
### Tile, Process, Untile
```yaml
my_models:
base_path: D:/ai-models/
nh_nodes: NH-Nodes/ # SegFormer models (garment, face parsing)
vton_mask: ComfyUI-Vton-Mask/ # VTON pose + parsing ONNX models
```
Use this when an image is too large for a model, or when every tile needs the
same enhancement/upscale step.
## Quick start
```text
Load Image -> Image Tile (NH) -> upscale/process tiles -> Image Untile (NH)
```
| Goal | Chain | Guide |
|---|---|---|
| Upscale or refine a huge image piece by piece | Load Image → **Image Tile** → your sampler/upscaler per tile → **Image Untile** | [image-compose](docs/en/image-compose.md#image-tile-nh) |
| Generate at the model's native size, then restore the original framing | Load Image → **NH Smart Ratio Image Resize** → edit → **NH Restore Image Geometry** | [resolution](docs/en/resolution.md) |
| Inpaint only the clothes | Load Image → **Garment Segment** → **Mask Morphology** (grow) → inpaint | [vision](docs/en/vision.md) |
| Compare many LoRAs, one per queue | **Counter** → **Load LoRA Model Index** (index) → sampler → **Image Label** (prints the LoRA name) → Save Image | [loaders](docs/en/loaders.md) |
| Run one of two branches without executing the other | condition → **If/Else** (lazy) → … | [logic](docs/en/logic.md#ifelse-nh) |
`Image Tile (NH)` supports:
## Node catalog
- `original_ratio`: derive tile dimensions from the original image aspect ratio.
- `custom`: use explicit tile width and height.
- `square`: force square tiles, for example `1024 x 1024`.
- Automatic overlap calculation.
- Edge padding so square/custom tile outputs keep the requested tile size.
| Group | Nodes | Guide |
|---|---|---|
| Image — load & save | Load Image Info, Load Images Folder, Load images matching, Save Image Path, NH Save Large Image | [EN](docs/en/image-io.md) · [VI](docs/vi/image-io.md) |
| Image — compose | Image Tile, Image Untile, Image Grid Composite, Layer Layout Composite, Layer Stack Composite, Image Compare, Image Label, Image Resize Unit | [EN](docs/en/image-compose.md) · [VI](docs/vi/image-compose.md) |
| Image — large preview | NH Large Image Preview, NH Large Image Compare, NH Large Preview Cache Manager | [EN](docs/en/image-preview.md) · [VI](docs/vi/image-preview.md) |
| Image — mask-based retouch | NH Mask-Aware Resize, NH Agnostic Image Generator, NH Simple Face Paste | [EN](docs/en/image-retouch.md) · [VI](docs/vi/image-retouch.md) |
| Resolution | NH Smart Resolution Picker, NH Smart Ratio Image Resize, NH Ratio Preset Image Resize, NH Remove Resize Padding, NH Restore Image Geometry | [EN](docs/en/resolution.md) · [VI](docs/vi/resolution.md) |
| Mask | Mask Morphology, Mask Properties, NH Create Box Mask, NH Mask Aspect Ratio Match | [EN](docs/en/mask.md) · [VI](docs/vi/mask.md) |
| Vision | Garment Segment, Portrait Segment | [EN](docs/en/vision.md) · [VI](docs/vi/vision.md) |
| VTON | VTON Ultimate Processor | [EN](docs/en/vton.md) · [VI](docs/vi/vton.md) |
| Logic | Compare, Logic Gate, If/Else, Switch N, Any Switch, Any Branch Switch, Gate Switch, Value Match Index, Math Eval, Random Choice | [EN](docs/en/logic.md) · [VI](docs/vi/logic.md) |
| Text | Prompt Template, Prompt Scheduler, String Operations, Prompt Join, Text Split, Regex Extract, Text Index, Text Concatenate, Text Split Lines, Text Random Line | [EN](docs/en/text.md) · [VI](docs/vi/text.md) |
| Batch & lists | Batch Index, Batch Merge, Counter, List Create, List Index, List Filter, Any List Split | [EN](docs/en/batch-lists.md) · [VI](docs/vi/batch-lists.md) |
| Loaders | Load LoRA Model Index, Load LoRA Clip Index, Load Diffusion Model Index, Load VAE Index, Load Clip Index | [EN](docs/en/loaders.md) · [VI](docs/vi/loaders.md) |
| Utils | Multi-Slider (FLOAT/INT), Boolean Switch, Pack Universal, Unpack Universal, Universal Slider Builder *(deprecated)* | [EN](docs/en/utils.md) · [VI](docs/vi/utils.md) |
`Image Untile (NH)` uses `tile_data` from the tile node to stitch results back.
It also handles tiles that were resized or upscaled, such as 2x or 3x per tile.
All nodes live under **NH-Nodes** in the node menu; search by name or by node id (for example `NH_ImageTile`).
### Grid Contact Sheet
## Documentation
```text
Image batch -> Image Grid Composite (NH) -> Preview Image
```
Use manual cell size or first-image cell size. Resize modes include `stretch`,
`pad`, and `fill`.
### Metadata-Aware Image Load
```text
Load Image Info (NH) -> image / mask / file_name / path
```
Useful for naming outputs, logging source images, or routing by filename.
### Smart Resolution Resize
```text
Load Image -> NH Smart Ratio Image Resize -> (your pipeline) -> NH Remove Resize Padding
```
`NH Smart Ratio Image Resize` picks the closest model preset itself from
`model_family` + `resolution_level`, so it does not need `NH Smart Resolution
Picker` upstream. Resize with cover crop, contain pad, or stretch behavior.
In `contain_pad` mode it also emits `resize_data` describing the content region,
padding, and scale factor. Feed that into `NH Remove Resize Padding` at the end
of the pipeline to crop the padded border back off (`content_region`) or to
restore the original input size (`original_size`).
For `cover_crop`, use `NH Restore Image Geometry` after processing. Connect the
processed image to `image`, the source image to `original_image`, and the resize
node's `resize_data` to the matching input. It returns an image at the exact
original dimensions, with processed content placed back in its original region
and cropped-away borders retained from the source. Its `mask` is white in the
restored region and black in the retained borders; use it for masked blending
with the original, optionally softening the mask edges first.
Processed images may be upscaled, including by a non-integer factor, provided
their framing is unchanged. Additional crops, rotations, or model-generated
changes in geometry cannot be reversed by this metadata. The restore node also
accepts `contain_pad` and `stretch` metadata and supports matching batches or
singleton batch broadcasting. Restoration uses bicubic (default), bilinear, or
nearest sampling; it does not recover details lost during downsampling.
Use `NH Smart Resolution Picker` when you need target dimensions and a latent
for text-to-image, instead of resizing an existing image.
### Garment and Portrait Segmentation
```text
Load Image -> Garment Segment (NH) [part=upper_garment] -> mask / cutout_rgba / mask_image
Load Image -> Portrait Segment (NH) [hair + face] -> mask / cutout_rgba / mask_image
```
SegFormer-based masks at input resolution. `cutout_rgba` is the input image
with the mask as alpha (RGB is left untouched outside the mask); `mask_image`
is the mask as an RGB preview. Dresses and jumpsuits belong to `upper_garment`. Both nodes share `mask_expand`, `mask_blur`, and `fill_holes`.
Choose `part=all_garments` to combine upper/lower garments, footwear and headwear
in one mask, for example a shirt and pants together. `attach_parts` still controls
whether touching pockets, zippers and other garment decorations are included.
Models are auto-downloaded to `models/NH-Nodes/` on first use (existing
`models/face_parsing` or `models/RMBG/segformer_fashion` copies are reused).
---
## Node Catalog
### Image Loading and Saving
| Node | Purpose |
|---|---|
| `Load Image Info (NH)` | Load one image and output `IMAGE`, `MASK`, file name, and path. |
| `Save Image Path (NH)` | Save image batches to a chosen folder and output saved filenames/path/count. |
| `NH Save Large Image` | Save full-resolution images without attaching heavy full-res previews to the canvas. |
| `Load Images Folder (NH)` | Load images from a folder by index/step, with filename and path outputs. |
| `Load images matching` | Load source/matching images by text, folder context, and matching rules. |
### Image Tiling, Grid, and Composition
| Node | Purpose |
|---|---|
| `Image Tile (NH)` | Split image batches into overlapping tiles with metadata for reconstruction. |
| `Image Untile (NH)` | Rebuild full images from processed/upscaled tiles and tile metadata. |
| `Image Grid Composite (NH)` | Combine an image batch into a configurable grid. |
| `NH Large Image Preview` | Preview large images with lightweight canvas thumbnails and passthrough output. |
| `NH Large Image Compare` | Compare large A/B images with lightweight canvas proxy and tiled slider/side/difference viewer. |
| `NH Large Preview Cache Manager` | Inspect and clean the NH large preview tile cache by age, size, or cache id. |
| `Layer Layout Composite (NH)` | Place layers into preset, grid, or custom layout slots. |
| `Layer Stack Composite (NH)` | Paste multiple layers onto a background using explicit coordinates. |
| `Image Resize Unit (NH)` | Resize by pixel/mm/cm using stretch, pad, crop, or lock-ratio modes. |
| `Image Compare (NH)` | Compare two images side-by-side, top/bottom, split, difference, or overlay. |
| `Image Label (NH)` | Add header/bottom labels with font, padding, color, and outline controls. |
| `NH Agnostic Image Generator` | Create agnostic/masked/composite images for inpaint and VTON workflows. |
| `NH Simple Face Paste` | Paste a source face region into a target using mask feathering. |
### Smart Resolution
| Node | Purpose |
|---|---|
| `NH Smart Resolution Picker` | Pick model/preset-aware dimensions and latent setup. |
| `NH Smart Ratio Image Resize` | Auto-pick the closest model preset for the source aspect, then resize with cover, contain, or stretch; outputs `resize_data` for padding removal. |
| `NH Ratio Preset Image Resize` | Resize to a chosen model preset ratio with crop, pad, or fill behavior. |
| `NH Remove Resize Padding` | Remove the padded border from processed images using resize metadata. |
| `NH Restore Image Geometry` | Restore processed or upscaled images to original coordinates with a blend mask; keep source pixels outside the crop. |
### Vision Segmentation
| Node | Purpose |
|---|---|
| `Garment Segment (NH)` | Mask `upper_garment` (tops, jackets, coats, dresses, ties, scarves, collars, sleeves), `lower_garment` (pants, shorts, skirts, belts, tights; no shoes), `footwear`, `headwear`, or combine all four with `all_garments`. Attached parts (pockets, zippers, bows...) merge only when touching the selected garments. |
| `Portrait Segment (NH)` | Checklist mask of face, neck, hair, eyes, lips, nose, eyebrows, ears (+ optional mouth interior). `face` is the solid face region including features. |
### Mask Tools
| Node | Purpose |
|---|---|
| `Mask Morphology (NH)` | Expand/shrink masks, fill holes, and blur edges. |
| `Mask Properties (NH)` | Output mask bounding box, dimensions, and related measurements. |
| `NH Create Box Mask` | Convert a mask area into a rectangular box mask. |
| `NH Mask Aspect Ratio Match` | Adjust one mask to match another mask's aspect ratio. |
| `NH Mask-Aware Resize` | Resize an image/mask pair while preserving the masked subject region. |
### Logic and Flow Control
| Node | Purpose |
|---|---|
| `Compare (NH)` | Compare values with typed operators. |
| `Logic Gate (NH)` | AND, OR, NOT, XOR, NAND, and NOR boolean logic. |
| `If/Else (NH)` | Select between two inputs by condition. |
| `Switch N (NH)` | Pick one of several generic inputs by index. |
| `Any Switch (NH)` | Boolean switch helper. |
| `Any Branch Switch (NH)` | Route generic data through selected branches. |
| `Gate Switch (NH)` | Pass or block a generic input by boolean state. |
| `Value Match Index (NH)` | Return index of matching configured value. |
| `Math Eval (NH)` | Safe math expression evaluator with numeric/string outputs. |
| `Random Choice (NH)` | Weighted random choice from generic inputs. |
### Text and Prompt Tools
| Node | Purpose |
|---|---|
| `String Operations (NH)` | Strip, case convert, replace, slice, inspect, and test strings. |
| `Prompt Join (NH)` | Join text fragments with separators and empty-line handling. |
| `Text Split (NH)` | Split text by delimiter and expose count/first/last values. |
| `Regex Extract (NH)` | Extract, replace, split, and match with regex patterns. |
| `Text Index (NH)` | Select a line/item from text by index. |
| `Text Concatenate (NH)` | Concatenate multiple text inputs. |
| `Text Split Lines (NH)` | Split multiline text into selected parts and metadata. |
| `Text Random Line (NH)` | Pick a random line from multiline text. |
| `Prompt Template (NH)` | Fill `{placeholder}` variables from connected text values. |
| `Prompt Scheduler (NH)` | Select prompts by step, sequence, ping-pong, or seed. |
### Lists and Batch Helpers
| Node | Purpose |
|---|---|
| `List Create (NH)` | Build an `NH_LIST` from multiline or delimited text. |
| `List Index (NH)` | Select an item from an `NH_LIST`. |
| `List Filter (NH)` | Filter lists with contains, regex, equals, length, and related conditions. |
| `Batch Index (NH)` | Extract one or more images from an image batch. |
| `Batch Merge (NH)` | Merge image batches with resize handling. |
| `Counter (NH)` | Stateful counter for repeated queue runs. |
| `Any List Split (NH)` | Split any incoming list into an `All` output plus up to 10 indexed outputs. |
### Indexed Loaders
| Node | Purpose |
|---|---|
| `Load LoRA Model Index (NH)` | Apply one selected LoRA to `MODEL` by 1-based index. |
| `Load LoRA Clip Index (NH)` | Apply one selected LoRA to `CLIP` by 1-based index. |
| `Load Diffusion Model Index (NH)` | Load one diffusion model by 1-based index. |
| `Load VAE Index (NH)` | Load one VAE by 1-based index or boolean toggle. |
| `Load Clip Index (NH)` | Load one CLIP by 1-based index or boolean toggle, with CLIP type selection. |
### Workflow Utilities
| Node | Purpose |
|---|---|
| `Multi-Slider (FLOAT)` | Five float controls in one node. |
| `Multi-Slider (INT)` | Five integer controls in one node. |
| `Universal Slider Builder (NH)` | Generate dynamic slider outputs from text config. |
| `Boolean Switch (NH)` | Simple boolean toggle. |
| `Pack Universal (NH)` | Bundle generic values into an `NH_UNIVERSAL_PIPE`. |
| `Unpack Universal (NH)` | Extract values from an `NH_UNIVERSAL_PIPE`. |
### VTON Preprocessing
| Node | Purpose |
|---|---|
| `VTON Ultimate Processor (NH)` | Generate masks, agnostic image data, DensePose/parsing outputs, hair/hands masks, and related VTON preprocessing assets. |
---
## Menu Structure
```text
NH-Nodes/
Image/
Load/save helpers
Tile/untile
Grid and layer composition
Resize, compare, label
Mask/
Morphology, properties, bbox, aspect matching
Vision/
Garment and portrait segmentation
Logic/
Compare, gates, switches, branching, math, random choice
Text/
String, regex, split, concatenate, templates, scheduling
Batch/
Image batch, list helpers, any-list split
Resolution/
Smart resolution picker, ratio resize, preset resize, padding removal
Loaders/
Indexed LoRA, diffusion model, VAE, and CLIP loaders
Utils/
Sliders, booleans, universal pipe
VTON/
VTON Ultimate Processor
```
---
## Requirements
```text
numpy
Pillow
scipy
opencv-python
scikit-image>=0.18.0
transformers>=4.20.0
tqdm>=4.62.0
huggingface_hub>=0.16.0
```
`VTON Ultimate Processor (NH)` also needs ONNX Runtime, which is not installed
automatically (the CPU wheel would overwrite an existing GPU build). Install one:
`python -m pip install onnxruntime-gpu` (NVIDIA) or `python -m pip install onnxruntime` (CPU).
Most image/text/logic nodes use common ComfyUI dependencies. VTON and Vision
nodes download model assets on first use (`transformers` SegFormer weights for
Vision: ~190 MB fashion, ~340 MB face).
---
## Development Notes
- Layout: `core/` holds shared helpers and model backends (no nodes); `nodes/<category>/`
mirrors the ComfyUI menu, and every module there that defines `NODE_CLASS_MAPPINGS`
is registered automatically by `__init__.py` - drop a file in the right folder
and restart. VTON's vendored preprocessing lives in `nodes/vton/preprocess/`.
- Tests: `python -m pytest custom_nodes/NH-Nodes/tests -q` from the ComfyUI root.
- Restart ComfyUI after installing or updating nodes.
- If a node appears cached in the browser, refresh the ComfyUI page after restart.
- `Image Tile (NH)` and `Image Untile (NH)` must pass `tile_data` directly through
the workflow so reconstruction uses the original tile layout.
---
- **Guide:** [English](docs/en/README.md) · [Tiếng Việt](docs/vi/README.md)
- **PDF guides:** [English](https://github.com/jetthuangai/NH-Nodes/releases/latest/download/NH-Nodes-Guide-EN.pdf) ·
[Tiếng Việt](https://github.com/jetthuangai/NH-Nodes/releases/latest/download/NH-Nodes-Huong-dan-VI.pdf)
- **Changelog:** [docs/CHANGELOG.md](docs/CHANGELOG.md) — read it when updating: some fixes change outputs on purpose.
## License
NH-Nodes' own code is [MIT](LICENSE). `VTON Ultimate Processor (NH)` and
`Portrait Segment (NH)` depend on third-party code or models that are licensed for
**non-commercial use only**, and the model behind `Garment Segment (NH)` declares no
license. See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) before using these
three nodes commercially.
NH-Nodes' own code is [MIT](LICENSE). Some features rely on third-party code or models with other terms — see
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md):
If NH-Nodes helps your workflow, consider starring the repository:
[github.com/jetthuangai/NH-Nodes](https://github.com/jetthuangai/NH-Nodes)
- **VTON Ultimate Processor** and **Portrait Segment** are for **non-commercial use only** (CC BY-NC-SA 4.0 code and
parsing models; non-commercial face-parsing model).
- **Garment Segment**'s model does not declare a licence.
## Development
- Layout: `core/` holds shared helpers and model backends (no nodes); `nodes/<category>/` mirrors the menu, and every
module there that defines `NODE_CLASS_MAPPINGS` is registered automatically; `web/` holds the frontend extensions.
- Tests and lint (run from the ComfyUI root / the pack folder):
```bash
python -m pytest custom_nodes/NH-Nodes/tests -q
ruff check .
```
- Docs assets are generated from the live code: `docs/_tools/build_node_cards.py` (node cards) and
`docs/_tools/build_examples.py` (example images), both run from the ComfyUI root.
## Author
[jetthuang.com](https://jetthuang.com) · [Hugging Face](https://huggingface.co/nhathoangfoto) ·
[Support via PayPal](https://paypal.me/nhathoangfoto). If NH-Nodes helps your work, a ⭐ on GitHub is appreciated.
+6 -4
View File
@@ -44,14 +44,16 @@ example, ONNX Runtime sessions in the vendored code are created through
## Models downloaded at runtime
These files are **not** stored in this repository. The nodes download them from
Hugging Face on first use.
Hugging Face on first use, pinned to the revisions below (only the files the nodes need).
They are looked up first in the folders registered as `vton_mask` and `nh_nodes`
(configurable in `extra_model_paths.yaml`).
| Node | Hugging Face repository | Contents | Terms |
|------|-------------------------|----------|-------|
| VTON Ultimate Processor | [`kg-09/kg-vton-mask`](https://huggingface.co/kg-09/kg-vton-mask) | `dwpose/yolox_l.onnx`, `dwpose/dw-ll_ucoco_384.onnx` | DWPose / YOLOX weights, Apache-2.0 upstream |
| VTON Ultimate Processor | [`kg-09/kg-vton-mask`](https://huggingface.co/kg-09/kg-vton-mask) @ `4648778` | `dwpose/yolox_l.onnx`, `dwpose/dw-ll_ucoco_384.onnx` | DWPose / YOLOX weights, Apache-2.0 upstream |
| VTON Ultimate Processor | same | `humanparsing/parsing_atr.onnx`, `humanparsing/parsing_lip.onnx` | From the OOTDiffusion/FitDiT checkpoints (CC BY-NC-SA 4.0); trained on the ATR and LIP datasets, which are distributed for research use |
| Portrait Segment | [`jonathandinu/face-parsing`](https://huggingface.co/jonathandinu/face-parsing) | SegFormer fine-tuned on CelebAMask-HQ | Model card: "non-commercial research and educational purposes" |
| Garment Segment | [`1038lab/segformer_fashion`](https://huggingface.co/1038lab/segformer_fashion) | SegFormer trained for Fashionpedia classes | No license declared by the uploader (Fashionpedia annotations are CC BY 4.0) |
| Portrait Segment | [`jonathandinu/face-parsing`](https://huggingface.co/jonathandinu/face-parsing) @ `758b82e` | SegFormer fine-tuned on CelebAMask-HQ | Model card: "non-commercial research and educational purposes" |
| Garment Segment | [`1038lab/segformer_fashion`](https://huggingface.co/1038lab/segformer_fashion) @ `e5bae2d` | SegFormer trained for Fashionpedia classes | No license declared by the uploader (Fashionpedia annotations are CC BY 4.0) |
## Python dependencies
+3 -45
View File
@@ -45,58 +45,16 @@ for _category in pkgutil.iter_modules(_nodes_pkg.__path__):
NODE_DISPLAY_NAME_MAPPINGS.update(getattr(_module, "NODE_DISPLAY_NAME_MAPPINGS", {}))
_module_count += 1
# Route-only module: registers /nh-nodes/large-preview/* on import.
# Route-only modules: they register the /nh-nodes/* HTTP routes on import.
try:
from .core import large_preview_routes # noqa: F401
except Exception as e:
print(f"[NH-Nodes] Failed to import core.large_preview_routes: {e}")
try:
from aiohttp import web
from server import PromptServer
@PromptServer.instance.routes.get("/nh-nodes/load-images-folder/count")
async def nh_load_images_folder_count(request):
folder_path = request.query.get("folder_path", "")
recursive = request.query.get("recursive", "").lower() in {"1", "true", "yes", "on"}
try:
from .nodes.image.image_tools_nodes import _collect_image_files, _resolve_load_dir
resolved_folder = _resolve_load_dir(folder_path)
image_count = len(_collect_image_files(resolved_folder, recursive=recursive))
return web.json_response({"count": image_count, "path": resolved_folder})
except Exception as exc:
return web.json_response({"count": 0, "error": str(exc)})
@PromptServer.instance.routes.get("/nh-nodes/smart-resolution-picker/presets")
async def nh_smart_resolution_picker_presets(request):
try:
from .core.resolution_data import DEFAULT_MODEL_LABEL, DEFAULT_PRESET, MODEL_LABELS, PRESET_LABELS_BY_MODEL
from .nodes.resolution.smart_resolution_picker import TARGET_RESOLUTION_LEVELS, _resolution_candidates
ratios_by_model_level = {}
for model_label in MODEL_LABELS:
ratios_by_model_level[model_label] = {}
for resolution_level in TARGET_RESOLUTION_LEVELS:
candidates = _resolution_candidates(model_label, resolution_level)
ratios = []
for entry in candidates:
if entry["aspect"] not in ratios:
ratios.append(entry["aspect"])
ratios_by_model_level[model_label][resolution_level] = ratios
return web.json_response({
"models": MODEL_LABELS,
"default_model": DEFAULT_MODEL_LABEL,
"default_preset": DEFAULT_PRESET,
"presets_by_model": PRESET_LABELS_BY_MODEL,
"resolution_levels": TARGET_RESOLUTION_LEVELS,
"ratios_by_model_level": ratios_by_model_level,
})
except Exception as exc:
return web.json_response({"error": str(exc)}, status=500)
from .core import folder_routes # noqa: F401
except Exception as e:
print(f"[NH-Nodes] Failed to register web routes: {e}")
print(f"[NH-Nodes] Failed to import core.folder_routes: {e}")
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
+103
View File
@@ -0,0 +1,103 @@
"""HTTP routes behind the NH-Nodes frontend extensions.
GET /nh-nodes/load-images-folder/count how many images a folder loader sees (auto increment / wrap)
GET /nh-nodes/smart-resolution-picker/presets the preset lists for the resolution pickers' dropdowns
Importing this module registers both on ComfyUI's PromptServer, like core.large_preview_routes. The handlers are plain
module-level functions, so a test can serve them from any aiohttp app through ``register_routes``.
"""
import asyncio
import os
from aiohttp import web
from .image_folders import collect_image_files, resolve_load_dir
COUNT_PATH = "/nh-nodes/load-images-folder/count"
PRESETS_PATH = "/nh-nodes/smart-resolution-picker/presets"
_MAX_FOLDER_PATH_CHARS = 4096
# Spellings of the ``recursive`` query parameter. Anything else is a bad request, not a silent "false".
_FLAGS = {"1": True, "true": True, "yes": True, "on": True, "": False, "0": False, "false": False, "no": False, "off": False}
def _error(status, message):
# Fixed short texts only: ComfyUI can be reachable from the network (--listen), so an answer never carries a
# resolved path or the text of an exception.
return web.json_response({"error": message}, status=status)
def _count_images(folder_path, recursive):
"""How many images a loader with these settings sees. Blocking (a recursive walk): run it in a worker thread."""
folder = resolve_load_dir(folder_path)
if not os.path.isdir(folder):
raise FileNotFoundError("folder not found")
try:
return len(collect_image_files(folder, recursive=recursive))
except ValueError:
return 0 # the folder exists but holds no image
async def nh_load_images_folder_count(request):
folder_path = request.query.get("folder_path", "")
recursive = _FLAGS.get(request.query.get("recursive", "").strip().lower())
if recursive is None:
return _error(400, "recursive must be true or false")
if len(folder_path) > _MAX_FOLDER_PATH_CHARS or "\0" in folder_path:
return _error(400, "invalid folder_path")
try:
count = await asyncio.to_thread(_count_images, folder_path, recursive)
except FileNotFoundError:
return _error(404, "folder not found")
except PermissionError:
return _error(403, "folder is not readable")
except Exception as exc:
print(f"[NH-Nodes] load-images-folder/count failed: {type(exc).__name__}: {exc}")
return _error(500, "could not read the folder")
return web.json_response({"count": count})
async def nh_smart_resolution_picker_presets(request):
try:
# Imported here: core must not depend on the node modules at import time.
from ..nodes.resolution.smart_resolution_picker import TARGET_RESOLUTION_LEVELS, _resolution_candidates
from .resolution_data import DEFAULT_MODEL_LABEL, DEFAULT_PRESET, MODEL_LABELS, PRESET_LABELS_BY_MODEL
ratios_by_model_level = {}
for model_label in MODEL_LABELS:
ratios_by_model_level[model_label] = {}
for resolution_level in TARGET_RESOLUTION_LEVELS:
candidates = _resolution_candidates(model_label, resolution_level)
ratios = []
for entry in candidates:
if entry["aspect"] not in ratios:
ratios.append(entry["aspect"])
ratios_by_model_level[model_label][resolution_level] = ratios
return web.json_response({
"models": MODEL_LABELS,
"default_model": DEFAULT_MODEL_LABEL,
"default_preset": DEFAULT_PRESET,
"presets_by_model": PRESET_LABELS_BY_MODEL,
"resolution_levels": TARGET_RESOLUTION_LEVELS,
"ratios_by_model_level": ratios_by_model_level,
})
except Exception as exc:
print(f"[NH-Nodes] smart-resolution-picker/presets failed: {type(exc).__name__}: {exc}")
return _error(500, "could not build the preset lists")
def register_routes(routes):
"""Attach the handlers to an aiohttp RouteTableDef (ComfyUI's ``PromptServer.instance.routes``)."""
routes.get(COUNT_PATH)(nh_load_images_folder_count)
routes.get(PRESETS_PATH)(nh_smart_resolution_picker_presets)
try:
from server import PromptServer
register_routes(PromptServer.instance.routes)
except Exception as exc:
print(f"[NH-Nodes] Failed to register folder routes: {exc}")
+78
View File
@@ -0,0 +1,78 @@
"""Image folder helpers shared by the folder loader nodes and the folder-count HTTP route.
``resolve_load_dir`` turns the folder text of a loader into a directory, ``collect_image_files`` lists the images in
it in natural order. Nothing here needs torch or Pillow.
"""
import os
import re
from pathlib import Path
import folder_paths
IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".webp", ".avif", ".bmp", ".tif", ".tiff"}
def natural_key(value):
"""Sort key that puts 'img2' before 'img10' and ignores case."""
return [int(part) if part.isdigit() else part.lower() for part in re.split(r"(\d+)", value)]
def resolve_load_dir(folder_path, image="", upload_probe=""):
"""The folder a loader reads.
``folder_path`` when given (relative to ComfyUI's input folder unless absolute; a file stands for its folder),
else the folder of the uploaded ``image`` / ``upload_probe``, else the input folder.
"""
folder_path = (folder_path or "").strip()
if folder_path:
resolved = folder_path
if not os.path.isabs(resolved):
resolved = os.path.join(folder_paths.get_input_directory(), resolved)
if os.path.isfile(resolved):
resolved = os.path.dirname(resolved)
return resolved
upload_name = image or upload_probe
if upload_name:
return os.path.dirname(folder_paths.get_annotated_filepath(upload_name))
return folder_paths.get_input_directory()
def collect_image_files(folder_path, recursive=False):
"""The images of ``folder_path`` in natural order, as names (paths relative to the folder below it).
Sub-folders are read when ``recursive`` is set, and also when the folder holds no image itself. ValueError for a
folder that does not exist or has no images.
"""
if not os.path.isdir(folder_path):
raise ValueError(f"Folder does not exist: {folder_path}")
files = []
for entry in os.listdir(folder_path):
full_path = os.path.join(folder_path, entry)
if os.path.isfile(full_path) and Path(entry).suffix.lower() in IMAGE_EXTENSIONS:
files.append(entry)
if recursive or not files:
seen = set(files)
models_dir = os.path.normcase(os.path.abspath(folder_paths.models_dir))
for current_root, dir_names, file_names in os.walk(folder_path):
# Natural order, and never into the ComfyUI models folder (a walk from one of its parents would crawl it).
dir_names[:] = sorted(
(name for name in dir_names if os.path.normcase(os.path.abspath(os.path.join(current_root, name))) != models_dir),
key=natural_key,
)
for file_name in sorted(file_names, key=natural_key):
full_path = os.path.join(current_root, file_name)
if os.path.isfile(full_path) and Path(file_name).suffix.lower() in IMAGE_EXTENSIONS:
rel_path = os.path.relpath(full_path, folder_path)
if rel_path not in seen:
seen.add(rel_path)
files.append(rel_path)
files.sort(key=natural_key)
if not files:
raise ValueError(f"No images found in folder: {folder_path}")
return files
+47
View File
@@ -0,0 +1,47 @@
"""Pillow / tensor helpers shared by the image nodes (save, load, compare, label, resize, layer composite).
IMAGE tensors become PIL images with core.utils.tensor_to_pil (rgb=True, plus keep_alpha=True for layers);
``pil_to_image_tensor`` converts back in ComfyUI's intermediate dtype.
"""
from PIL import Image, ImageColor
import comfy.model_management
from .utils import parse_hex_color, pil_to_tensor
RESAMPLING = getattr(Image, "Resampling", Image)
def parse_color(value, default):
"""Parse a colour into an (r, g, b, a) tuple of 0-255 ints; ``default`` when the text is not a colour.
'#RGB', '#RRGGBB' and '#RRGGBBAA' go through core.utils.parse_hex_color (opaque unless AA is given). Other CSS
colours Pillow understands ('red', 'rgb(1, 2, 3)') keep working, as they did before.
"""
text = str(value or "").strip()
color = parse_hex_color(text, ())
if color:
return color
try:
return ImageColor.getcolor(text, "RGBA")
except ValueError:
return tuple(default) + (255,) * (4 - len(default))
def pil_to_image_tensor(image):
"""A PIL image as a [1, H, W, C] float IMAGE in ComfyUI's intermediate dtype."""
return pil_to_tensor(image).to(dtype=comfy.model_management.intermediate_dtype())
def resize_pil(image, size, method):
"""``image`` resized to ``size`` (width, height) with the 'lanczos', 'bicubic', 'bilinear' or 'nearest' filter."""
resample = {
"lanczos": RESAMPLING.LANCZOS,
"bicubic": RESAMPLING.BICUBIC,
"bilinear": RESAMPLING.BILINEAR,
"nearest": RESAMPLING.NEAREST,
}[method]
return image.resize(size, resample)
+5 -17
View File
@@ -10,10 +10,11 @@ import uuid
from pathlib import Path
import folder_paths
import numpy as np
import torch
from PIL import Image, ImageChops, ImageDraw
from .utils import tensor_to_pil
CACHE_SUBDIR = "nh_large_preview"
CACHE_ID_RE = re.compile(r"^[a-f0-9]{32}$")
@@ -139,21 +140,6 @@ def delete_cache(cache_id):
}
def _tensor_to_pil(image_tensor):
if not isinstance(image_tensor, torch.Tensor):
raise TypeError(f"Expected IMAGE tensor, got {type(image_tensor).__name__}")
array = image_tensor.detach().cpu().numpy()
array = np.clip(array * 255.0, 0, 255).astype(np.uint8)
if array.ndim == 2:
return Image.fromarray(array).convert("RGB")
if array.ndim == 3 and array.shape[-1] == 1:
return Image.fromarray(array[:, :, 0]).convert("RGB")
if array.ndim == 3 and array.shape[-1] == 4:
return Image.fromarray(array).convert("RGBA")
return Image.fromarray(array).convert("RGB")
def _preview_dimensions(width, height, preview_megapixels):
width = max(1, int(width))
height = max(1, int(height))
@@ -292,7 +278,9 @@ def create_preview_cache(
batch_index=0,
batch_count=1,
):
source = _tensor_to_pil(image_tensor)
if not isinstance(image_tensor, torch.Tensor):
raise TypeError(f"Expected IMAGE tensor, got {type(image_tensor).__name__}")
source = tensor_to_pil(image_tensor, rgb=True, keep_alpha=True)
source_width, source_height = source.size
preview_width, preview_height = _preview_dimensions(source_width, source_height, preview_megapixels)
preview = source.copy()
+95 -56
View File
@@ -1,67 +1,106 @@
"""HTTP routes for NH large image preview cache."""
"""HTTP routes for NH large image preview cache.
Importing this module registers the routes on ComfyUI's PromptServer. The handlers are plain module-level functions,
so a test can serve them from any aiohttp app through ``register_routes``.
"""
import asyncio
import json
from aiohttp import web
from .large_preview_cache import cache_info, cleanup_cache, delete_cache, get_thumb_path, get_tile_path, read_manifest
def _without_paths(value):
"""Answers go to the browser: drop absolute filesystem paths (the Cache Manager node still shows them)."""
if isinstance(value, dict):
return {key: _without_paths(item) for key, item in value.items() if key not in ("path", "root")}
if isinstance(value, list):
return [_without_paths(item) for item in value]
return value
async def _read_json_object(request):
"""The JSON object in the request body ({} when the body is empty). ValueError for anything else."""
try:
text = await request.text()
payload = json.loads(text) if text.strip() else {}
except ValueError: # not JSON, or not UTF-8 text: json.JSONDecodeError and UnicodeDecodeError both are ValueErrors
raise ValueError("request body is not valid JSON") from None
if not isinstance(payload, dict):
raise ValueError("request body must be a JSON object")
return payload
async def nh_large_preview_manifest(request):
try:
manifest = read_manifest(request.match_info["cache_id"])
return web.json_response(manifest)
except FileNotFoundError:
return web.json_response({"error": "manifest not found"}, status=404)
except ValueError as exc:
return web.json_response({"error": str(exc)}, status=400)
async def nh_large_preview_thumb(request):
try:
return web.FileResponse(get_thumb_path(request.match_info["cache_id"]))
except FileNotFoundError:
return web.json_response({"error": "thumbnail not found"}, status=404)
except ValueError as exc:
return web.json_response({"error": str(exc)}, status=400)
async def nh_large_preview_tile(request):
try:
return web.FileResponse(get_tile_path(
request.match_info["cache_id"],
request.match_info["level"],
request.match_info["tile"],
))
except FileNotFoundError:
return web.json_response({"error": "tile not available in thumbnail-only preview"}, status=404)
except ValueError as exc:
return web.json_response({"error": str(exc)}, status=400)
async def nh_large_preview_cache_info(request):
try:
# Directory walks run in a worker thread so a large cache never stalls the server's event loop.
return web.json_response(_without_paths(await asyncio.to_thread(cache_info)))
except ValueError as exc:
return web.json_response({"error": str(exc)}, status=400)
async def nh_large_preview_cleanup(request):
try:
payload = await _read_json_object(request)
except ValueError as exc:
return web.json_response({"error": str(exc)}, status=400)
try:
cache_id = payload.get("cache_id", "")
if cache_id:
result = await asyncio.to_thread(delete_cache, cache_id)
else:
result = await asyncio.to_thread(cleanup_cache, payload.get("max_age_hours", 24), payload.get("max_total_mb", 0))
return web.json_response(_without_paths(result))
except (ValueError, TypeError, OverflowError) as exc: # a bad id, or a limit that is not a number
return web.json_response({"error": str(exc)}, status=400)
def register_routes(routes):
"""Attach the handlers to an aiohttp RouteTableDef (ComfyUI's ``PromptServer.instance.routes``)."""
routes.get("/nh-nodes/large-preview/manifest/{cache_id}")(nh_large_preview_manifest)
routes.get("/nh-nodes/large-preview/thumb/{cache_id}")(nh_large_preview_thumb)
routes.get("/nh-nodes/large-preview/tile/{cache_id}/{level}/{tile}")(nh_large_preview_tile)
routes.get("/nh-nodes/large-preview/cache-info")(nh_large_preview_cache_info)
routes.post("/nh-nodes/large-preview/cleanup")(nh_large_preview_cleanup)
try:
from server import PromptServer
@PromptServer.instance.routes.get("/nh-nodes/large-preview/manifest/{cache_id}")
async def nh_large_preview_manifest(request):
try:
manifest = read_manifest(request.match_info["cache_id"])
return web.json_response(manifest)
except FileNotFoundError:
return web.json_response({"error": "manifest not found"}, status=404)
except ValueError as exc:
return web.json_response({"error": str(exc)}, status=400)
@PromptServer.instance.routes.get("/nh-nodes/large-preview/thumb/{cache_id}")
async def nh_large_preview_thumb(request):
try:
return web.FileResponse(get_thumb_path(request.match_info["cache_id"]))
except FileNotFoundError:
return web.json_response({"error": "thumbnail not found"}, status=404)
except ValueError as exc:
return web.json_response({"error": str(exc)}, status=400)
@PromptServer.instance.routes.get("/nh-nodes/large-preview/tile/{cache_id}/{level}/{tile}")
async def nh_large_preview_tile(request):
try:
return web.FileResponse(get_tile_path(
request.match_info["cache_id"],
request.match_info["level"],
request.match_info["tile"],
))
except FileNotFoundError:
return web.json_response({"error": "tile not available in thumbnail-only preview"}, status=404)
except ValueError as exc:
return web.json_response({"error": str(exc)}, status=400)
@PromptServer.instance.routes.get("/nh-nodes/large-preview/cache-info")
async def nh_large_preview_cache_info(request):
try:
return web.json_response(cache_info())
except ValueError as exc:
return web.json_response({"error": str(exc)}, status=400)
@PromptServer.instance.routes.post("/nh-nodes/large-preview/cleanup")
async def nh_large_preview_cleanup(request):
try:
payload = await request.json()
except Exception:
payload = {}
try:
cache_id = payload.get("cache_id", "")
if cache_id:
result = delete_cache(cache_id)
else:
result = cleanup_cache(payload.get("max_age_hours", 24), payload.get("max_total_mb", 0))
return web.json_response(result)
except ValueError as exc:
return web.json_response({"error": str(exc)}, status=400)
register_routes(PromptServer.instance.routes)
except Exception as exc:
print(f"[NH-Nodes] Failed to register large preview routes: {exc}")
+25
View File
@@ -0,0 +1,25 @@
"""Mask helpers shared by the NH mask and image nodes.
NH-Nodes' own code (MIT). It is deliberately not part of core/utils_mask.py: that file is third-party
FitDiT code under CC BY-NC-SA 4.0 (see THIRD_PARTY_NOTICES.md), and plain MIT nodes must not depend on it.
"""
import numpy as np
def mask_bbox(mask, threshold=0.5):
"""Inclusive bounding box ``(x1, y1, x2, y2)`` of the pixels of a 2-D mask that are above ``threshold``.
``mask`` is an [H, W] array (a torch mask goes through ``.cpu().numpy()`` first). A pixel counts only when it is
strictly greater than ``threshold``, so a value of exactly 0.5 is outside, as in every NH mask node. The result is
four Python ints; (x2, y2) is the last pixel inside, so width = x2 - x1 + 1 and height = y2 - y1 + 1.
Returns None when no pixel is above the threshold, i.e. for an empty mask. Anything that is not 2-D raises ValueError.
"""
inside = np.asarray(mask) > threshold
if inside.ndim != 2:
raise ValueError(f"mask_bbox expects a 2-D [H, W] mask, got shape {inside.shape}")
rows = np.flatnonzero(inside.any(axis=1))
if rows.size == 0:
return None
cols = np.flatnonzero(inside.any(axis=0))
return int(cols[0]), int(rows[0]), int(cols[-1]), int(rows[-1])
+131
View File
@@ -0,0 +1,131 @@
"""Model-folder, download and VRAM helpers shared by the SegFormer and VTON model loaders.
Both model families follow the same rules:
* Model folders are registered with ComfyUI's ``folder_paths`` under the names
``nh_nodes`` (SegFormer) and ``vton_mask`` (VTON). Users can add more
locations with those names in ``extra_model_paths.yaml``; every registered
location is searched before anything is downloaded. Each name is registered
with the extension of its model files (``.safetensors``, ``.onnx``), so
ComfyUI's model lists show those files and not the Hugging Face cache
metadata that downloads leave in the same folder.
* Downloads always go to the default folder under the models dir, pinned to a
fixed Hugging Face revision.
"""
import os
import torch
try:
import folder_paths
except Exception: # running outside ComfyUI (tests)
folder_paths = None
try:
import comfy.model_management as _mm
except Exception:
_mm = None
def models_dir():
"""ComfyUI's models directory, read on every call (--models-directory, tests)."""
if folder_paths is not None:
return folder_paths.models_dir
return os.path.join(os.path.dirname(__file__), "models")
def unique_dirs(paths):
"""Drop repeats (same folder spelled differently) but keep the first spelling and the order."""
seen, unique = set(), []
for path in paths:
key = os.path.normcase(os.path.abspath(path))
if key not in seen:
seen.add(key)
unique.append(path)
return unique
def _list_only(folder_name, extensions):
"""Add `extensions` to the file types ComfyUI lists for the registered `folder_name`.
add_model_folder_path registers a name with an empty extension set, which ComfyUI reads as "list
every file": /models and the Model Library then show the .cache/huggingface metadata of our
downloads and a repo's .gitattributes. The set is replaced, never changed in place (ComfyUI shares
one set between many folder names), and the locations list stays the very same object, so every
extra_model_paths.yaml entry survives.
"""
if not extensions:
return
registry = folder_paths.folder_names_and_paths
paths, current = registry[folder_name]
merged = {*current, *extensions}
if merged != set(current):
registry[folder_name] = (paths, merged)
def register_model_folder(folder_name, default_dir, extensions=()):
"""Register `folder_name` with ComfyUI and return every folder searched for it, best first.
ComfyUI applies extra_model_paths.yaml before custom nodes load, so locations the user
configured under `folder_name` are already registered; they come first. `default_dir`
is added too (a no-op when already present) and is always part of the result, so models
that live there are found whether or not the user configured anything. Safe to call repeatedly.
`extensions` (".onnx") are the file types of the models in these folders: ComfyUI's lists for
`folder_name` then show only those, also for the locations the user added.
"""
registered = []
if folder_paths is not None:
try:
folder_paths.add_model_folder_path(folder_name, default_dir)
registered = folder_paths.get_folder_paths(folder_name)
_list_only(folder_name, extensions) # last: if it fails, the locations above are still returned
except Exception as exc: # an unusual ComfyUI build: fall back to the default location alone
print(f"[NH-Nodes] Could not register model folder '{folder_name}' with ComfyUI: {exc}")
return unique_dirs([*registered, default_dir])
def download_pinned(repo_id, revision, target, allow_patterns):
"""snapshot_download of `allow_patterns` from `repo_id` at `revision` into `target`.
Finished files are kept and partial ones resume, so calling it again after an interrupted
download completes the folder. Failures are re-raised with where to get the files by hand.
"""
from huggingface_hub import snapshot_download
os.makedirs(target, exist_ok=True)
print(f"[NH-Nodes] Downloading {repo_id}@{revision[:8]} -> {target}")
try:
snapshot_download(repo_id=repo_id, revision=revision, local_dir=target, allow_patterns=list(allow_patterns))
except Exception as exc:
raise RuntimeError(
f"[NH-Nodes] Downloading {repo_id} (revision {revision[:8]}) into {target} failed: {exc}\n"
f"Run again once the connection works (finished files are kept), or download "
f"{', '.join(allow_patterns)} from https://huggingface.co/{repo_id}/tree/{revision} into that folder."
) from exc
def make_room_on_gpu(nbytes):
"""Ask ComfyUI to unload its own models until about `nbytes` of VRAM are free.
ComfyUI's own loaders ask for their size plus the VRAM it keeps in reserve (extra_reserved_memory(),
set by --reserve-vram or its built-in default), so that reserve is added here as well: asking for
`nbytes` alone would leave us that much short. Also hands PyTorch's cached blocks back to the
driver: libraries that allocate outside PyTorch (ONNX Runtime) cannot use them. Nothing happens
without a CUDA device, when ComfyUI itself runs on the CPU, or outside ComfyUI. A failure is
reported, not raised: the caller still tries its allocation.
"""
if _mm is None or not torch.cuda.is_available():
return
try:
# ComfyUI's loaded models carry an indexed device (cuda:0); an index-less
# torch.device("cuda") compares unequal to it and would match none of them.
device = _mm.get_torch_device()
if device.type != "cuda":
return
reserved = getattr(_mm, "extra_reserved_memory", None) # absent in older ComfyUI builds
_mm.free_memory(nbytes + (reserved() if reserved else 0), device)
_mm.soft_empty_cache()
except Exception as exc:
print(f"[NH-Nodes] Could not ask ComfyUI to free VRAM ({exc}); continuing")
+4
View File
@@ -2,6 +2,10 @@
Data is kept separate from node logic so new model families can be added
without changing execution code.
min_res / max_res are the clamp range for *custom* sizes. Curated presets may go
beyond max_res (e.g. 4 MP tiers); their own reliability/warning fields say how
safe they are, and the node reports that warning in its info output.
"""
+126
View File
@@ -0,0 +1,126 @@
"""Arithmetic expression evaluator shared by Math Eval (NH) and layer position expressions.
Accepted: numbers, named variables, + - * / // % **, unary + and -, and an optional
whitelist of functions. Everything is computed in floating point, so no expression can
build a huge integer and freeze the worker (``9**9**9`` simply overflows and is reported).
"""
import ast
import math
import numbers
import operator
# Python's ** returns a complex number for a negative base with a fractional exponent;
# math.pow raises instead, which is what a node input needs.
_BINARY = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.FloorDiv: operator.floordiv,
ast.Mod: operator.mod,
ast.Pow: math.pow,
}
_UNARY = {
ast.UAdd: operator.pos,
ast.USub: operator.neg,
}
_DIVISIONS = (ast.Div, ast.FloorDiv, ast.Mod)
MATH_FUNCTIONS = {
"min": min,
"max": max,
"abs": abs,
"round": lambda value, digits=0: round(value, int(digits)),
"sqrt": math.sqrt,
"floor": math.floor,
"ceil": math.ceil,
"clamp": lambda value, low, high: max(low, min(high, value)),
}
class ExpressionError(ValueError):
"""Raised for any expression that cannot be evaluated to a finite number."""
def evaluate(expression, variables=None, functions=None, *, zero_division="zero",
case_insensitive=False, on_warning=None):
"""Evaluate ``expression`` and return a finite float.
zero_division: "zero" makes x/0, x//0 and x%0 evaluate to 0 (reported through
``on_warning``); "error" raises ExpressionError.
case_insensitive: match variable names with str.casefold (keys must already be folded).
"""
variables = variables or {}
functions = functions or {}
text = str(expression).strip()
if not text:
raise ExpressionError("Expression is empty")
def warn(message):
if on_warning is not None:
on_warning(message)
def number(value, what):
# numpy scalars and 0-d torch tensors arrive from upstream nodes on FLOAT/INT sockets.
if getattr(value, "ndim", None) == 0 and callable(getattr(value, "item", None)):
value = value.item()
if not isinstance(value, numbers.Real): # bool is a Real (True + 1 == 2, as before)
raise ExpressionError(f"{what} must be a number, got {type(value).__name__}")
try:
return float(value)
except OverflowError as exc:
raise ExpressionError(f"{what} is too large") from exc
def lookup(name):
key = name.casefold() if case_insensitive else name
if key in variables:
return number(variables[key], f"Variable '{name}'")
raise ExpressionError(f"Unknown variable '{name}'")
def visit(node):
if isinstance(node, ast.Constant):
return number(node.value, f"Constant {node.value!r}")
if isinstance(node, ast.Name):
return lookup(node.id)
if isinstance(node, ast.BinOp):
op = _BINARY.get(type(node.op))
if op is None:
raise ExpressionError(f"Operator '{type(node.op).__name__}' is not allowed")
left, right = visit(node.left), visit(node.right)
if isinstance(node.op, _DIVISIONS) and right == 0:
if zero_division == "zero":
warn("division by zero, using 0")
return 0.0
raise ExpressionError("Division by zero")
return float(op(left, right))
if isinstance(node, ast.UnaryOp):
op = _UNARY.get(type(node.op))
if op is None:
raise ExpressionError(f"Operator '{type(node.op).__name__}' is not allowed")
return op(visit(node.operand))
if isinstance(node, ast.Call):
name = node.func.id if isinstance(node.func, ast.Name) else None
if name not in functions or node.keywords:
raise ExpressionError(f"Function '{name}' is not allowed")
return number(functions[name](*[visit(arg) for arg in node.args]), f"{name}()")
raise ExpressionError(f"'{type(node).__name__}' is not allowed in an expression")
try:
result = visit(ast.parse(text, mode="eval").body)
except ExpressionError:
raise
except SyntaxError as exc:
raise ExpressionError(f"Invalid expression: {text}") from exc
except OverflowError as exc:
raise ExpressionError(f"Result is too large: {text}") from exc
except (ValueError, TypeError, ZeroDivisionError) as exc:
# math domain errors, wrong argument counts, 0 ** -1 ...
raise ExpressionError(f"Cannot evaluate {text}: {exc}") from exc
except (RecursionError, MemoryError) as exc:
# The parser raises MemoryError ("parser stack overflowed") for absurdly nested input.
raise ExpressionError("Expression is nested too deeply") from exc
if not math.isfinite(result):
raise ExpressionError(f"Result is not a finite number: {text}")
return result
+62 -31
View File
@@ -3,6 +3,14 @@
Handles model discovery (reuse dirs already on disk before downloading),
per-model caching with CPU offload between runs, and full-resolution label
map prediction.
The model-folder, download and VRAM helpers it shares with the VTON node live
in model_files.py, so both model families follow the same rules. SegFormer
models are registered with ComfyUI's ``folder_paths`` under the name
``nh_nodes``; users can add more locations with that name in
``extra_model_paths.yaml`` and every registered location is searched before
anything is downloaded. Downloads always go to the default folder under the
models dir, pinned to a fixed Hugging Face revision.
"""
import os
@@ -12,35 +20,34 @@ import numpy as np
import torch
import torch.nn.functional as F
try:
import folder_paths
_MODELS_DIR = folder_paths.models_dir
except Exception: # running outside ComfyUI (tests)
_MODELS_DIR = os.path.join(os.path.dirname(__file__), "models")
try:
import comfy.model_management as _mm
except Exception:
_mm = None
from .model_files import download_pinned, make_room_on_gpu, models_dir, register_model_folder, unique_dirs
_REQUIRED_FILES = ("config.json", "model.safetensors", "preprocessor_config.json")
# Logits are interpolated at most to this size before argmax; the label map is
# then nearest-resized to the input. Keeps VRAM bounded on very large inputs.
_MAX_LOGIT_SIDE = 2048
# name -> HF repo, local dirs to probe before downloading (relative to models
# dir; other packs may already hold the same weights) and a label signature
# that guards against a different checkpoint sitting in a probed dir.
# extra_model_paths.yaml key for the SegFormer models. Each registered location holds
# one sub-folder per model (see MODEL_SPECS), e.g. <location>/segformer_fashion/config.json.
FOLDER_NAME = "nh_nodes"
FOLDER_EXTENSIONS = (".safetensors",) # what ComfyUI lists for FOLDER_NAME: the weights, not config.json or cache metadata
_DEFAULT_FOLDER = "NH-Nodes" # under the models dir: the download target, searched first by default
# name -> HF repo + pinned revision, legacy local dirs to probe after the registered
# folders (relative to the models dir; older versions of this pack and other packs may
# already hold the same weights) and a label signature that guards against a different
# checkpoint sitting in a probed dir.
MODEL_SPECS = {
"segformer_fashion": {
"repo_id": "1038lab/segformer_fashion",
"probe_dirs": ("NH-Nodes/segformer_fashion", "RMBG/segformer_fashion"),
"revision": "e5bae2d8e3333be9f12eb6aae454c0542c1371d3",
"legacy_dirs": ("RMBG/segformer_fashion",),
"signature": {1: "shirt, blouse", 24: "shoe", 46: "tassel"},
},
"face_parsing": {
"repo_id": "jonathandinu/face-parsing",
"probe_dirs": ("NH-Nodes/face_parsing", "face_parsing", "RMBG/segformer_face"),
"revision": "758b82e15a0178c9db39c1ff666a8b56e3a550c8",
"legacy_dirs": ("face_parsing", "RMBG/segformer_face"),
"signature": {1: "skin", 13: "hair", 17: "neck"},
},
}
@@ -49,35 +56,54 @@ _LOADED = {}
_warned_no_cuda = False
# ---------------------------------------------------------------------------
# Model folders and downloads (the shared helpers are in model_files.py)
# ---------------------------------------------------------------------------
def default_folder():
"""Where SegFormer models are downloaded and searched first: <models dir>/NH-Nodes."""
return os.path.join(models_dir(), _DEFAULT_FOLDER)
def _has_required_files(path):
return all(os.path.isfile(os.path.join(path, name)) for name in _REQUIRED_FILES)
def model_search_dirs(model_name):
"""Folders that may hold `model_name`, best match first.
The registered ``nh_nodes`` locations come first (models/NH-Nodes by default, plus whatever
extra_model_paths.yaml adds), then the legacy dirs under the models dir.
"""
spec = MODEL_SPECS[model_name]
dirs = [os.path.join(base, model_name)
for base in register_model_folder(FOLDER_NAME, default_folder(), FOLDER_EXTENSIONS)]
dirs += [os.path.normpath(os.path.join(models_dir(), *rel.split("/"))) for rel in spec["legacy_dirs"]]
return unique_dirs(dirs)
def resolve_model_dir(model_name, download=True):
"""Return a local dir holding the model, downloading into models/NH-Nodes if absent."""
spec = MODEL_SPECS[model_name]
for rel in spec["probe_dirs"]:
candidate = os.path.join(_MODELS_DIR, rel)
for candidate in model_search_dirs(model_name):
if _has_required_files(candidate):
return candidate
target = os.path.join(_MODELS_DIR, spec["probe_dirs"][0])
if not download:
return None
from huggingface_hub import snapshot_download
os.makedirs(target, exist_ok=True)
print(f"[NH-Nodes] Downloading {spec['repo_id']} -> {target}")
snapshot_download(
repo_id=spec["repo_id"],
local_dir=target,
allow_patterns=list(_REQUIRED_FILES),
)
target = os.path.join(default_folder(), model_name)
download_pinned(spec["repo_id"], spec["revision"], target, _REQUIRED_FILES)
if not _has_required_files(target):
raise RuntimeError(f"[NH-Nodes] Model files missing after download: {target}")
return target
# Make the folder name known to ComfyUI as soon as the pack loads.
register_model_folder(FOLDER_NAME, default_folder(), FOLDER_EXTENSIONS)
# ---------------------------------------------------------------------------
# SegFormer inference
# ---------------------------------------------------------------------------
def _torch_device(device):
global _warned_no_cuda
if device == "cuda":
@@ -121,14 +147,19 @@ def load_segformer(model_name, device="cuda"):
return bundle
def _is_on(model_device, wanted):
"""True when `model_device` satisfies `wanted`; an index-less "cuda" accepts any cuda:N."""
return model_device.type == wanted.type and (wanted.index is None or model_device.index == wanted.index)
def _ensure_on_device(bundle):
model, device = bundle["model"], bundle["device"]
if model.device == device:
if _is_on(model.device, device):
return
if device.type == "cuda" and _mm is not None:
if device.type == "cuda":
# Let ComfyUI evict its own models first instead of OOM-ing on ours.
size = sum(p.numel() * p.element_size() for p in model.parameters())
_mm.free_memory(size * 2, device)
make_room_on_gpu(size * 2)
model.to(device)
+45 -6
View File
@@ -11,12 +11,34 @@ def resize_image(image, target_size=768):
new_h, new_w = target_size, int(w * target_size / h)
return image.resize((new_w, new_h), Image.LANCZOS)
def tensor_to_pil(tensor_image):
"""Convert tensor to PIL image"""
def tensor_to_pil(tensor_image, *, rgb=False, keep_alpha=False):
"""Convert ONE ComfyUI image ([H,W,C] or [1,H,W,C]) to PIL.
Values are clipped to [0, 1] first, so overshoot from resampling cannot wrap around
in uint8. 1/3/4 channels become L/RGB/RGBA. Batches must be converted per image.
``rgb=True`` always gives an RGB picture: grey is expanded and alpha is dropped. With ``keep_alpha=True`` as
well, a 4-channel image stays RGBA instead (every other image is still RGB).
"""
if tensor_image.dim() == 4:
tensor_image = tensor_image.squeeze(0)
np_image = (tensor_image.cpu().numpy() * 255).astype(np.uint8)
return Image.fromarray(np_image)
if tensor_image.shape[0] != 1:
raise ValueError(
f"tensor_to_pil expects a single image but got a batch of {tensor_image.shape[0]}; "
"convert each image of the batch separately"
)
tensor_image = tensor_image[0]
pixels = tensor_image.detach().cpu()
if pixels.dtype == torch.bfloat16: # numpy has no bfloat16; other dtypes keep their own rounding
pixels = pixels.float()
np_image = np.clip(pixels.numpy() * 255.0, 0, 255).astype(np.uint8)
if np_image.ndim == 3 and np_image.shape[2] == 1:
np_image = np_image[:, :, 0]
pil_image = Image.fromarray(np_image)
if rgb:
kept_modes = ("RGB", "RGBA") if keep_alpha else ("RGB",)
if pil_image.mode not in kept_modes:
pil_image = pil_image.convert("RGB")
return pil_image
def pil_to_tensor(pil_image):
"""Convert PIL image to tensor in ComfyUI format for IMAGE"""
@@ -32,4 +54,21 @@ def pil_to_mask(pil_image):
if len(np_image.shape) == 3: # Ensure single channel
np_image = np_image[:,:,0]
tensor = torch.from_numpy(np_image).unsqueeze(0)
return tensor
return tensor
def parse_hex_color(value, default=(0, 0, 0, 255)):
"""Parse '#RGB', '#RRGGBB' or '#RRGGBBAA' (the '#' is optional) into an (r, g, b, a) tuple of 0-255 ints.
Without an alpha part the colour is opaque. Text that is not a valid colour returns ``default``.
"""
text = str(value or "").strip().lstrip("#")
if len(text) == 3:
text = "".join(ch * 2 for ch in text)
if len(text) == 6:
text += "ff"
if len(text) != 8:
return tuple(default)
try:
return tuple(int(text[i:i + 2], 16) for i in range(0, 8, 2))
except ValueError:
return tuple(default)
+92 -103
View File
@@ -1,7 +1,8 @@
# Garment mask construction for VTON Ultimate Processor (NH).
# Third-party code: FitDiT src/utils_mask.py (https://github.com/BoyuanJiang/FitDiT),
# received via ComfyUI-Vton-Mask. License: CC BY-NC-SA 4.0 (non-commercial), not the
# pack's MIT license. NH-Nodes changes: lint cleanups and bug fixes. See THIRD_PARTY_NOTICES.md.
# pack's MIT license. NH-Nodes changes: lint cleanups, bug fixes and Image.fromarray calls without the mode
# argument that Pillow 11.3 deprecated. See THIRD_PARTY_NOTICES.md.
import numpy as np
import cv2
from PIL import Image, ImageDraw
@@ -15,11 +16,27 @@ def remove_small(binary_mask, min_area=10000):
small_white_regions = [region for region in regions if region.area < min_area]
for region in small_white_regions:
min_row, min_col, max_row, max_col = region.bbox
binary_mask[min_row:max_row, min_col:max_col] = 0
# Only this region's pixels: its bounding box can overlap a large region.
binary_mask[tuple(region.coords.T)] = 0
return binary_mask
def _merge_bound(keypoint_bound, cloth_bound, pick):
"""Combine a keypoint bound (None when the whole keypoint group is missing) with the garment contour bound."""
if keypoint_bound is None:
return cloth_bound
return pick(keypoint_bound, cloth_bound)
def _fallback_box(x1, y1, x2, y2, shape):
"""Whole-image edges only for bounds that neither keypoints nor the garment contour provided."""
height, width = shape[:2]
return (0 if x1 is None else x1, 0 if y1 is None else y1,
width if x2 is None else x2, height if y2 is None else y2)
# pose_data rows: body 0-17, foot 18-23, face 24-91, hands 92-133. A keypoint that was not detected is
# negative (DWPose marks it -1 and the node then scales it by the width/height), so only values > 0 count.
def get_img_agnostic_upper_rectangle(im_parse, pose_data, offset_top, offset_bottom, offset_left, offset_right):
faces = pose_data[24:92]
@@ -69,48 +86,37 @@ def get_img_agnostic_upper_rectangle(im_parse, pose_data, offset_top, offset_bot
except Exception:
cloth_exist = False
x1 = [body[i, 0] for i in [2, 3, 4] if body[i, 0] != 0]+\
[hands2[i, 0] for i in [5,9,13] if hands2[i, 0] != 0]+\
[hands1[i, 0] for i in [5,9,13] if hands1[i, 0] != 0]
if x1:
x1 = np.min(x1)
else:
x1 = 0
y1 = [body[i, 1] for i in [2, 5] if body[i, 1] != 0]+\
[hands2[i, 1] for i in [5,9,13] if hands2[i, 1] != 0]+\
[hands1[i, 1] for i in [5,9,13] if hands1[i, 1] != 0]
if y1:
y1 = np.min(y1)
else:
y1 = 0
x2 = [body[i, 0] for i in [5, 6, 7] if body[i, 0] != 0]+\
[hands1[i, 0] for i in [5,9,13] if hands1[i, 0] != 0]+\
[hands2[i, 0] for i in [5,9,13] if hands2[i, 0] != 0]
if x2:
x2 = np.max(x2)
else:
x2 = parse_array.shape[1]
y2 = [body[i, 1] for i in [8, 11] if body[i, 1] != 0]+\
[hands2[i, 1] for i in [5,9,13] if hands2[i, 1] != 0]+\
[hands1[i, 1] for i in [5,9,13] if hands1[i, 1] != 0]
if y2:
y2 = np.max(y2)
else:
y2 = parse_array.shape[0]
x1 = [body[i, 0] for i in [2, 3, 4] if body[i, 0] > 0]+\
[hands2[i, 0] for i in [5,9,13] if hands2[i, 0] > 0]+\
[hands1[i, 0] for i in [5,9,13] if hands1[i, 0] > 0]
x1 = np.min(x1) if x1 else None
y1 = [body[i, 1] for i in [2, 5] if body[i, 1] > 0]+\
[hands2[i, 1] for i in [5,9,13] if hands2[i, 1] > 0]+\
[hands1[i, 1] for i in [5,9,13] if hands1[i, 1] > 0]
y1 = np.min(y1) if y1 else None
x2 = [body[i, 0] for i in [5, 6, 7] if body[i, 0] > 0]+\
[hands1[i, 0] for i in [5,9,13] if hands1[i, 0] > 0]+\
[hands2[i, 0] for i in [5,9,13] if hands2[i, 0] > 0]
x2 = np.max(x2) if x2 else None
y2 = [body[i, 1] for i in [8, 11] if body[i, 1] > 0]+\
[hands2[i, 1] for i in [5,9,13] if hands2[i, 1] > 0]+\
[hands1[i, 1] for i in [5,9,13] if hands1[i, 1] > 0]
y2 = np.max(y2) if y2 else None
pad_y1 = 20
pad_y2 = 10
pad_x1 = pad_x2 = 25
if cloth_exist:
x1 = min(x1, _x1)
x2 = max(x2, _x2)
y1 = min(y1, _y1)
if y2>_y2:
x1 = _merge_bound(x1, _x1, min)
x2 = _merge_bound(x2, _x2, max)
y1 = _merge_bound(y1, _y1, min)
if y2 is not None and y2>_y2:
pad_y2 = 0
y2 = max(y2, _y2)
y2 = _merge_bound(y2, _y2, max)
x1, y1, x2, y2 = _fallback_box(x1, y1, x2, y2, parse_array.shape)
y_face = [faces[i, 1] for i in [5, 11] if faces[i, 1] != 0]
y_face = [faces[i, 1] for i in [5, 11] if faces[i, 1] > 0]
if y_face:
y_face = np.mean(y_face)
@@ -120,8 +126,8 @@ def get_img_agnostic_upper_rectangle(im_parse, pose_data, offset_top, offset_bot
agnostic_draw.rectangle((max(0, x1-pad_x1+offset_left), max(0, y1-pad_y1+offset_top), min(x2+pad_x2+offset_right, parse_array.shape[1]), min(y2+pad_y2+offset_bottom, parse_array.shape[0])), 'gray', 'gray')
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_head * 255), 'L'))
# agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_fixed * 255), 'L'))
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_head * 255)))
# agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_fixed * 255)))
mask_gray = agnostic.copy()
mask = agnostic.point(lambda p: p == 128 and 255)
@@ -180,33 +186,27 @@ def get_img_agnostic_lower_rectangle(im_parse, pose_data, offset_top, offset_bot
except Exception:
all_cloth_exist = False
x1 = [body[i, 0] for i in [8, 9, 10] if body[i, 0] != 0]+[foot2[i, 0] for i in [0, 1, 2] if foot2[i, 0] != 0]+\
[body[i, 0] for i in [11, 12, 13] if body[i, 0] != 0]+[foot1[i, 0] for i in [0, 1, 2] if foot1[i, 0] != 0]
if x1:
x1 = np.min(x1)
else:
x1=0
y1 = min(body[8, 1], body[11, 1])
x2 = [body[i, 0] for i in [11, 12, 13] if body[i, 0] != 0]+[foot1[i, 0] for i in [0, 1, 2] if foot1[i, 0] != 0]+\
[body[i, 0] for i in [8, 9, 10] if body[i, 0] != 0]+[foot2[i, 0] for i in [0, 1, 2] if foot2[i, 0] != 0]
if x2:
x2 = np.max(x2)
else:
x2 = parse_array.shape[1]
y2 = [body[i, 1] for i in [10, 13] if body[i, 1] != 0]+[foot1[i, 1] for i in [2] if foot2[i, 1] != 0]+\
[foot2[i, 1] for i in [2] if foot2[i, 1] != 0]
if y2:
y2 = np.max(y2)
else:
y2 = parse_array.shape[0]
x1 = [body[i, 0] for i in [8, 9, 10] if body[i, 0] > 0]+[foot2[i, 0] for i in [0, 1, 2] if foot2[i, 0] > 0]+\
[body[i, 0] for i in [11, 12, 13] if body[i, 0] > 0]+[foot1[i, 0] for i in [0, 1, 2] if foot1[i, 0] > 0]
x1 = np.min(x1) if x1 else None
# Keypoints that were not detected are negative (DWPose marks them -1): use only the detected hips.
y1 = [body[i, 1] for i in [8, 11] if body[i, 1] > 0]
y1 = np.min(y1) if y1 else None
x2 = [body[i, 0] for i in [11, 12, 13] if body[i, 0] > 0]+[foot1[i, 0] for i in [0, 1, 2] if foot1[i, 0] > 0]+\
[body[i, 0] for i in [8, 9, 10] if body[i, 0] > 0]+[foot2[i, 0] for i in [0, 1, 2] if foot2[i, 0] > 0]
x2 = np.max(x2) if x2 else None
y2 = [body[i, 1] for i in [10, 13] if body[i, 1] > 0]+[foot1[i, 1] for i in [2] if foot1[i, 1] > 0]+\
[foot2[i, 1] for i in [2] if foot2[i, 1] > 0]
y2 = np.max(y2) if y2 else None
if cloth_exist:
x1 = min(x1, _x1)
x2 = max(x2, _x2)
y1 = min(y1, _y1)
y2 = max(y2, _y2)
x1 = _merge_bound(x1, _x1, min)
x2 = _merge_bound(x2, _x2, max)
y1 = _merge_bound(y1, _y1, min)
y2 = _merge_bound(y2, _y2, max)
elif all_cloth_exist:
x1 = min(x1, _x1)
x2 = max(x2, _x2)
x1 = _merge_bound(x1, _x1, min)
x2 = _merge_bound(x2, _x2, max)
x1, y1, x2, y2 = _fallback_box(x1, y1, x2, y2, parse_array.shape)
pad_y1 = 5 if cloth_exist else 50
pad_y2 = 10
@@ -214,8 +214,8 @@ def get_img_agnostic_lower_rectangle(im_parse, pose_data, offset_top, offset_bot
agnostic_draw.rectangle((max(0, x1-pad_x1+offset_left), max(0, y1-pad_y1+offset_top), min(x2+pad_x2+offset_right, parse_array.shape[1]), min(y2+pad_y2+offset_bottom, parse_array.shape[0])), 'gray', 'gray')
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_head * 255), 'L'))
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_fixed * 255), 'L'))
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_head * 255)))
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_fixed * 255)))
mask_gray = agnostic.copy()
mask = agnostic.point(lambda p: p == 128 and 255)
@@ -266,48 +266,37 @@ def get_img_agnostic_dresses_rectangle(im_parse, pose_data, offset_top, offset_b
except Exception:
cloth_exist = False
x1 = [body[i, 0] for i in [2, 3, 4] if body[i, 0] != 0]+\
[body[i, 0] for i in [8, 9, 10] if body[i, 0] != 0]+\
[foot2[i, 0] for i in [0, 1, 2] if foot2[i, 0] != 0]+\
[hands2[i, 0] for i in [5, 9, 13, 17] if hands2[i, 0] != 0]
if x1:
x1 = np.min(x1)
else:
x1 = 0
y1 = [body[2, 1], body[5, 1]]+\
[hands2[i, 1] for i in [5, 9, 13, 17] if hands2[i, 1] != 0]+\
[hands1[i, 1] for i in [5, 9, 13, 17] if hands1[i, 1] != 0]
if y1:
y1 = np.min(y1)
else:
y1 = 0
x2 = [body[i, 0] for i in [5, 6, 7] if body[i, 0] != 0]+\
[body[i, 0] for i in [11, 12, 13] if body[i, 0] != 0]+\
[foot1[i, 0] for i in [0, 1, 2] if foot1[i, 0] != 0]+\
[hands1[i, 0] for i in [5, 9, 13, 17] if hands1[i, 0] != 0]
if x2:
x2 = np.max(x2)
else:
x2 = parse_array.shape[1]
y2 = [body[i, 1] for i in [10, 13] if body[i, 1] != 0]+[foot1[i, 1] for i in [0, 1, 2] if foot1[i, 1] != 0]+\
[foot2[i, 1] for i in [0, 1, 2] if foot2[i, 1] != 0]
if y2:
y2 = np.max(y2)
else:
y2 = parse_array.shape[0]
x1 = [body[i, 0] for i in [2, 3, 4] if body[i, 0] > 0]+\
[body[i, 0] for i in [8, 9, 10] if body[i, 0] > 0]+\
[foot2[i, 0] for i in [0, 1, 2] if foot2[i, 0] > 0]+\
[hands2[i, 0] for i in [5, 9, 13, 17] if hands2[i, 0] > 0]
x1 = np.min(x1) if x1 else None
y1 = [body[i, 1] for i in [2, 5] if body[i, 1] > 0]+\
[hands2[i, 1] for i in [5, 9, 13, 17] if hands2[i, 1] > 0]+\
[hands1[i, 1] for i in [5, 9, 13, 17] if hands1[i, 1] > 0]
y1 = np.min(y1) if y1 else None
x2 = [body[i, 0] for i in [5, 6, 7] if body[i, 0] > 0]+\
[body[i, 0] for i in [11, 12, 13] if body[i, 0] > 0]+\
[foot1[i, 0] for i in [0, 1, 2] if foot1[i, 0] > 0]+\
[hands1[i, 0] for i in [5, 9, 13, 17] if hands1[i, 0] > 0]
x2 = np.max(x2) if x2 else None
y2 = [body[i, 1] for i in [10, 13] if body[i, 1] > 0]+[foot1[i, 1] for i in [0, 1, 2] if foot1[i, 1] > 0]+\
[foot2[i, 1] for i in [0, 1, 2] if foot2[i, 1] > 0]
y2 = np.max(y2) if y2 else None
if cloth_exist:
x1 = min(x1, _x1)
x2 = max(x2, _x2)
y1 = min(y1, _y1)
y2 = max(y2, _y2)
x1 = _merge_bound(x1, _x1, min)
x2 = _merge_bound(x2, _x2, max)
y1 = _merge_bound(y1, _y1, min)
y2 = _merge_bound(y2, _y2, max)
x1, y1, x2, y2 = _fallback_box(x1, y1, x2, y2, parse_array.shape)
pad_y1 = 20
pad_y2 = 10
pad_x1 = pad_x2 = 50
y_face = [faces[i, 1] for i in [5, 11] if faces[i, 1] != 0]
y_face = [faces[i, 1] for i in [5, 11] if faces[i, 1] > 0]
if y_face:
y_face = np.mean(y_face)
@@ -317,9 +306,9 @@ def get_img_agnostic_dresses_rectangle(im_parse, pose_data, offset_top, offset_b
agnostic_draw.rectangle((max(0, x1-pad_x1+offset_left), max(0, y1-pad_y1+offset_top), min(x2+pad_x2+offset_right, parse_array.shape[1]), min(y2+pad_y2+offset_bottom, parse_array.shape[0])), 'gray', 'gray')
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_head * 255), 'L'))
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_shoes * 255), 'L'))
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_fixed * 255), 'L'))
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_head * 255)))
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_shoes * 255)))
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_fixed * 255)))
mask_gray = agnostic.copy()
mask = agnostic.point(lambda p: p == 128 and 255)
+87
View File
@@ -0,0 +1,87 @@
# Changelog
All notable changes to NH-Nodes. Versions follow the Comfy Registry releases.
"Behaviour change" marks fixes that can change the output of an existing workflow.
## 1.6.2 — Load images matching picks the right group
- `Load images matching` (behaviour change): when the source and the matching library share parent folders (for example
`D:/Photos/Source/Nu/…` and `D:/Photos/Library/{Nam,Nu}/…`), the shared folders no longer outweigh the source's
group, so a source in `Nu` picks `Nu/<match>` instead of the first folder in alphabetical order. Same-drive and
cross-drive setups now rank identically, and folders elsewhere in the library no longer change which match wins.
## 1.6.1 — Cleanup and hardening
- Internal restructuring with no change to node ids, inputs or outputs: the large image-tools module is split into one
module per node family, shared helpers live in `core/` (`image_folders`, `image_ops`, `mask_ops`, `model_files`), and
the frontend extensions share one widget helper module.
- Widgets are hidden with the frontend's own `hidden` property when available (older frontends keep the previous
method); values are still saved with the workflow.
- `Load Images Folder`: the image count used for auto increment now honours `recursive`; a wired `folder_path` no
longer fingerprints ComfyUI's input folder by mistake.
- HTTP routes: the folder-count and large-preview routes no longer return absolute paths, answer with proper status
codes (malformed JSON → 400) and do their file work off the server's event loop.
- `NH Save Large Image` accepts single-channel images; bfloat16 IMAGE tensors convert correctly; image conversions no
longer use Pillow's deprecated `fromarray(mode=…)`.
## 1.6.0 — Performance, caching and VRAM
### Faster graphs
- **Lazy switches:** `If/Else (NH)`, `Switch N (NH)` and `Random Choice (NH)` now ask ComfyUI only for the branch they pick, so the other branches (for example a second sampler) are never executed. The choice itself is unchanged. A connected input whose upstream returns nothing still counts as connected.
- `Layer Stack Composite (NH)` is cached like any pure node instead of re-running on every queue.
- `Load images matching`: results are cached until the matched files change; the search stays inside the matching root (no more scanning parent folders, drive roots or the models folder). When nothing matches, `found` is `False` and `images` returns the new optional `fallback_images` input (evaluated only on a miss); without a fallback, `images` is blocked with a message and the rest of the workflow keeps running (behaviour change: it used to raise an error). Use `fallback_images` rather than routing `images` through a switch — a blocked input stops the switch too. A drive or network-share root cannot be searched recursively any more.
- Indexed loaders (`Load Diffusion Model Index`, `Load VAE Index`, `Load Clip Index`) reuse the model ComfyUI still holds, so editing a slot you did not select no longer reloads it from disk; they keep only a weak reference, so ComfyUI's RAM-pressure cache can still free the model (`--cache-classic` keeps a normal reference). VAEs are loaded with their file metadata and validated like the core loader does.
- `Mask Morphology (NH)`: rewritten on torch (CPU or GPU). Soft masks keep their gradient, masks touching the border no longer shrink from the edge, and large kernels are up to 100× faster. Even kernel lengths act as the next odd length (behaviour change).
### VRAM and models
- `VTON Ultimate Processor (NH)`: new optional `keep_model_loaded` (default **off**). Off frees the ONNX Runtime GPU memory after each run that really used the GPU (about 1.5 s slower per run); on keeps today's speed and is much faster for image lists, which ComfyUI runs one item at a time. ComfyUI is asked to make room before the models are loaded on the GPU, except once ONNX Runtime has fallen back to the CPU.
- SegFormer nodes now really make ComfyUI unload models when VRAM is tight (a device-index mismatch prevented it).
- Model downloads are pinned to tested Hugging Face revisions and only fetch the needed files; a half-downloaded VTON folder is repaired instead of trusted.
- `extra_model_paths.yaml` support: the folder names `nh_nodes` (SegFormer models, `.safetensors`) and `vton_mask` (VTON models, `.onnx`) can point to extra locations; existing installs keep working without moving anything.
### Viewer
- `NH Large Image Preview` / `NH Large Image Compare`: browse every image of a batch with ◀ n / N ▶ or the ←/→ keys.
## 1.5.0 — Correctness fixes
### Image
- **Batches everywhere:** `VTON Ultimate Processor`, `NH Simple Face Paste` and `NH Agnostic Image Generator` now process every image of a batch (smaller inputs with batch 1 are reused for every item). Behaviour change: Face Paste returns the full destination batch instead of one image. VTON stops with a clear error naming the batch index when no person is detected (it used to return a meaningless mask or crash).
- **Values are clipped before 8-bit conversion**, so overshoot from resampling no longer wraps bright pixels to black (Face Paste, Mask-Aware Resize, shared helpers).
- `NH Mask-Aware Resize`: an empty mask now respects the mode (pad = fit and pad with `pad_color_hex`, crop = cover and centre) instead of stretching.
- `NH Agnostic Image Generator`: masks of a different size are resized to the image, 2-D masks are accepted, soft masks keep their gradient, and the `noise` fill is deterministic (behaviour change).
- `Layer Layout Composite` / `Layer Stack Composite`: RGBA layers are pasted with their own alpha instead of as opaque rectangles. RGBA backgrounds give RGBA output.
- Padding colours accept `#RRGGBBAA`; without an alpha part the padding is opaque (it used to copy the blue value into alpha).
- `Save Image Path (NH)`: `filename_prefix` and `new_folder_name` can no longer write outside the chosen folder (on Windows a prefix may not contain `:`, which blocks drive prefixes such as `D:name`); a prefix such as `renders/img` creates the sub-folder. An absolute `folder_path` is still allowed.
- `NH Save Large Image`: WEBP (16383 px) and JPEG (65500 px) size limits are checked before anything is written; `--disable-metadata` is respected.
- `Image Label (NH)`: much faster font fitting (cached fonts + binary search), same sizes as before.
- `Image Grid Composite (NH)`: refuses a canvas that would not fit in the available memory with a clear message instead of crashing.
- `NH Large Preview Cache Manager`: actions run on every queue (it used to be cached).
### Resolution
- `NH Remove Resize Padding`: images resized with `cover_crop` are no longer stretched back (that distorted them). With `original_size` the picture is scaled back uniformly into the original frame and the cropped-away borders are filled from the edges; use `NH Restore Image Geometry` to composite onto the original instead.
- Resize nodes keep float precision for Lanczos/box/hamming (no 8-bit quantisation).
- `NH Smart Resolution Picker`: a stale preset or model name is reported as a warning in `info`.
### Logic, text and lists
- `Math Eval (NH)`: uses a shared safe evaluator. Behaviour change: an invalid expression now shows an error that names the expression instead of silently returning 0. `x / 0`, `x // 0` and `x % 0` give 0 with a warning. numpy/torch scalar inputs work.
- `Random Choice (NH)`: the weight list follows the input slots (`weights` item *i* belongs to `input_i`). Behaviour change when some slots are empty.
- Seeds are used literally in `Random Choice`, `Prompt Scheduler`, `Text Split Lines` and `Text Random Line`, so seed 0 is reproducible (behaviour change).
- `Prompt Template (NH)`: an empty variable no longer glues the neighbouring words together.
- `Regex Extract (NH)`: optional groups that did not match give an empty string instead of "no match".
- `Pack Universal (NH)`: values are stored by slot, so an empty `input_0` no longer shifts the other indices (behaviour change).
- `Compare (NH)` operator `in`: plain membership / substring test (no more fuzzy numeric matching).
- `Batch Merge (NH)`: RGB and RGBA batches can be merged; letterbox bars are transparent for both; very thin inputs no longer crash `pad`.
- `NH Mask Aspect Ratio Match`: `pad`/`crop` placement fixed (no crash at the image edge, requested size honoured) and the whole batch is processed.
- VTON garment masks: undetected keypoints and missing keypoint groups no longer stretch the mask to the image edges; small parsing islands are removed without cutting rectangles out of the garment.
- `Universal Slider Builder (NH)` is marked deprecated (it never produced output); existing workflows still load.
## 1.4.3 — Tooling, licensing and dependencies
- CI runs ruff and the test suite on every pull request; publishing to the Comfy Registry only happens when it passes.
- `onnxruntime` is no longer installed automatically (the CPU wheel overwrote `onnxruntime-gpu`). `VTON Ultimate Processor` explains how to install it and falls back to the CPU with a warning.
- License notices: the VTON code and parsing models are CC BY-NC-SA 4.0 and the face-parsing model is non-commercial — see `THIRD_PARTY_NOTICES.md`.
## 1.4.2
- `NH Smart Resolution Picker`: FLUX.2 latents are 128 channels at 1/16 (images came out twice the size).
- `Load Clip Index (NH)`: works with current ComfyUI (`CLIPType`) and lists every CLIP type.
- `Counter (NH)`: counts on every queue.
- `NH Mask-Aware Resize`: `crop` keeps the whole mask.
File diff suppressed because it is too large Load Diff
+241
View File
@@ -0,0 +1,241 @@
"""Render a ComfyUI-style SVG card for every NH-Nodes node from its INPUT_TYPES / RETURN_TYPES.
Run with ComfyUI's Python from the ComfyUI root:
python custom_nodes/NH-Nodes/docs/_tools/build_node_cards.py [output_dir]
Cards are generated from the live node definitions, so they never drift from the code.
"""
import importlib
import json
import os
import re
import sys
from html import escape
COMFY_ROOT = os.getcwd()
sys.path[:0] = [COMFY_ROOT, os.path.join(COMFY_ROOT, "custom_nodes")]
import torch # noqa: E402
if not torch.cuda.is_available():
from comfy.cli_args import args # noqa: E402
args.cpu = True
PACK_DIR = os.path.join(COMFY_ROOT, "custom_nodes", "NH-Nodes")
OUT_DIR = sys.argv[1] if len(sys.argv) > 1 else os.path.join(PACK_DIR, "docs", "assets", "nodes")
# ComfyUI default dark palette (frontend 1.55) for link types; NH custom types get amber.
TYPE_COLORS = {
"IMAGE": "#64B5F6", "MASK": "#81C784", "LATENT": "#FF9CF9", "MODEL": "#B39DDB",
"CLIP": "#FFD500", "VAE": "#FF6E6E", "CONDITIONING": "#FFA931", "CONTROL_NET": "#6EE7B7",
"INT": "#9CC2E5", "FLOAT": "#9CC2E5", "STRING": "#C5E1A5", "BOOLEAN": "#F48FB1", "*": "#CFD8DC",
}
CUSTOM_TYPE_COLOR = "#E0B272"
WIDGET_TYPES = {"INT", "FLOAT", "STRING", "BOOLEAN"}
W = 360
PAD = 12
TITLE_H = 46
ROW_H = 22
WIDGET_H = 24
WIDGET_GAP = 5
FONT = "Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
MONO = "'JetBrains Mono', Consolas, 'Courier New', monospace"
FILE_EXTENSIONS = (".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tif", ".tiff", ".gif", ".safetensors", ".ckpt",
".pt", ".pth", ".bin", ".gguf", ".onnx", ".sft")
def is_file_combo(options, opts):
"""Combos listing files from the user's folders (inputs, models): never publish those names."""
if opts.get("image_upload") or opts.get("video_upload"):
return True
return any(str(option).lower().endswith(FILE_EXTENSIONS) for option in options)
def type_name(spec_type):
if isinstance(spec_type, (list, tuple)):
return "COMBO"
return str(spec_type)
def type_color(name):
return TYPE_COLORS.get(name, CUSTOM_TYPE_COLOR)
def short(value, limit=24):
text = str(value).replace("\n", " ⏎ ")
return text if len(text) <= limit else text[: limit - 1] + "…"
def fmt_number(value, opts):
if isinstance(value, float):
step = opts.get("step", 0.01) or 0.01
decimals = max(0, min(4, len(str(step).split(".")[1]) if "." in str(step) else 0))
return f"{value:.{decimals}f}"
return str(value)
def collapse_numbered(entries):
"""Fold long numbered families (lora_1..lora_64, enable_1..) into first two + a summary row."""
families = {}
for name, *_ in entries:
match = re.match(r"^(.*?)(\d+)$", name)
if match:
families.setdefault(match.group(1), []).append(int(match.group(2)))
folded = {prefix: max(nums) for prefix, nums in families.items() if len(nums) > 4}
result, emitted = [], set()
for entry in entries:
match = re.match(r"^(.*?)(\d+)$", entry[0])
if match and match.group(1) in folded:
prefix, num = match.group(1), int(match.group(2))
if num <= 2:
result.append(entry)
elif prefix not in emitted:
emitted.add(prefix)
result.append((f"{prefix}3 … {prefix}{folded[prefix]}", "FOLD", {}, entry[3]))
continue
result.append(entry)
return result
def node_entries(cls):
spec = cls.INPUT_TYPES()
entries = []
for section in ("required", "optional"):
for name, value in (spec.get(section) or {}).items():
spec_type = value[0] if isinstance(value, (list, tuple)) and value else value
opts = value[1] if isinstance(value, (list, tuple)) and len(value) > 1 and isinstance(value[1], dict) else {}
entries.append((name, spec_type, opts, section == "optional"))
return collapse_numbered(entries)
def split_entries(entries):
sockets, widgets = [], []
for name, spec_type, opts, optional in entries:
tname = type_name(spec_type)
is_widget = tname == "COMBO" or tname == "FOLD" or (tname in WIDGET_TYPES and not opts.get("forceInput"))
(widgets if is_widget else sockets).append((name, spec_type, opts, optional))
return sockets, widgets
def widget_value(spec_type, opts):
tname = type_name(spec_type)
if tname == "COMBO":
options = list(spec_type)
if is_file_combo(options, opts):
return "(choose a file)", ""
default = opts.get("default", options[0] if options else "")
return (short(default) if default != "" else "—"), f"{len(options)} options" if len(options) > 1 else ""
default = opts.get("default", "")
if tname == "BOOLEAN":
return ("true" if default else "false"), ""
if tname in ("INT", "FLOAT"):
return fmt_number(default, opts), ""
if tname == "STRING":
return short(default, 30) if default != "" else "", "multiline" if opts.get("multiline") else ""
return short(default), ""
def svg_card(node_id, display, category, cls):
entries = node_entries(cls)
sockets, widgets = split_entries(entries)
return_types = [str(t) for t in getattr(cls, "RETURN_TYPES", ())]
return_names = list(getattr(cls, "RETURN_NAMES", None) or return_types)
is_list = list(getattr(cls, "OUTPUT_IS_LIST", ()) or [])
output_node = getattr(cls, "OUTPUT_NODE", False)
io_rows = max(len(sockets), len(return_types))
multiline_rows = sum(1 for _, t, o, _ in widgets if type_name(t) == "STRING" and o.get("multiline"))
widgets_h = len(widgets) * (WIDGET_H + WIDGET_GAP) + multiline_rows * 22
height = TITLE_H + 10 + io_rows * ROW_H + (8 if io_rows and widgets else 0) + widgets_h + PAD
parts = [
f'<svg xmlns="http://www.w3.org/2000/svg" width="{W}" height="{height}" viewBox="0 0 {W} {height}" '
f'font-family="{FONT}" role="img" aria-label="{escape(display)} node">',
f'<rect x="0.5" y="0.5" width="{W - 1}" height="{height - 1}" rx="10" fill="#26282c" stroke="#45484f"/>',
f'<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h{W - 21} a10 10 0 0 1 10 10 v{TITLE_H - 10} h-{W - 1} z" fill="#34373d"/>',
f'<circle cx="{PAD + 5}" cy="17" r="4.5" fill="{"#F6AD55" if output_node else "#8FD18F"}"/>',
f'<text x="{PAD + 16}" y="22" font-size="14" font-weight="600" fill="#EEF0F3">{escape(short(display, 40))}</text>',
f'<text x="{PAD + 16}" y="38" font-size="10.5" fill="#9AA0A8" font-family="{MONO}">'
f'{escape(node_id)} · {escape(category.replace("NH-Nodes/", ""))}</text>',
]
y = TITLE_H + 10
for index in range(io_rows):
cy = y + index * ROW_H + ROW_H / 2
if index < len(sockets):
name, spec_type, opts, optional = sockets[index]
tname = type_name(spec_type)
color = type_color(tname)
parts.append(f'<circle cx="{PAD}" cy="{cy}" r="5" fill="{color}" stroke="#1b1c1f" stroke-width="1.5"/>')
label = escape(name) + (' <tspan fill="#7d838c" font-style="italic">(optional)</tspan>' if optional else "")
parts.append(f'<text x="{PAD + 12}" y="{cy + 4}" font-size="12" fill="#D7DADF">{label}</text>')
if index < len(return_types):
tname = return_types[index]
color = type_color(tname)
name = return_names[index] if index < len(return_names) else tname
list_mark = " ≡" if index < len(is_list) and is_list[index] else ""
parts.append(f'<text x="{W - PAD - 12}" y="{cy + 4}" font-size="12" fill="#D7DADF" text-anchor="end">'
f'{escape(name)}{list_mark}</text>')
parts.append(f'<circle cx="{W - PAD}" cy="{cy}" r="5" fill="{color}" stroke="#1b1c1f" stroke-width="1.5"/>')
y += io_rows * ROW_H + (8 if io_rows and widgets else 0)
for name, spec_type, opts, optional in widgets:
tname = type_name(spec_type)
multiline = tname == "STRING" and opts.get("multiline")
h = WIDGET_H + (22 if multiline else 0)
parts.append(f'<rect x="{PAD}" y="{y}" width="{W - 2 * PAD}" height="{h}" rx="7" fill="#1d1f22" stroke="#3a3d43"/>')
if tname == "FOLD":
parts.append(f'<text x="{W / 2}" y="{y + 16}" font-size="11" fill="#9AA0A8" text-anchor="middle" '
f'font-style="italic">{escape(name)}</text>')
y += h + WIDGET_GAP
continue
value, hint = widget_value(spec_type, opts)
label = escape(name) + (' <tspan fill="#6f747c" font-style="italic">opt</tspan>' if optional else "")
if multiline:
parts.append(f'<text x="{PAD + 10}" y="{y + 16}" font-size="11.5" fill="#9AA0A8">{label}</text>')
parts.append(f'<text x="{PAD + 10}" y="{y + 36}" font-size="11.5" fill="#E6E8EB" font-family="{MONO}">'
f'{escape(value) or "…"}</text>')
elif tname == "BOOLEAN":
on = value == "true"
parts.append(f'<text x="{PAD + 10}" y="{y + 16}" font-size="11.5" fill="#9AA0A8">{label}</text>')
tx = W - PAD - 40
parts.append(f'<rect x="{tx}" y="{y + 6}" width="30" height="13" rx="6.5" fill="{"#4C8BF5" if on else "#4a4d53"}"/>')
parts.append(f'<circle cx="{tx + (23 if on else 7)}" cy="{y + 12.5}" r="5" fill="#F5F6F7"/>')
else:
arrows = tname in ("INT", "FLOAT", "COMBO")
if arrows:
parts.append(f'<text x="{PAD + 8}" y="{y + 16}" font-size="10" fill="#6f747c">◀</text>')
parts.append(f'<text x="{W - PAD - 16}" y="{y + 16}" font-size="10" fill="#6f747c">▶</text>')
lx = PAD + (22 if arrows else 10)
rx = W - PAD - (22 if arrows else 10)
parts.append(f'<text x="{lx}" y="{y + 16}" font-size="11.5" fill="#9AA0A8">{label}</text>')
parts.append(f'<text x="{rx}" y="{y + 16}" font-size="11.5" fill="#E6E8EB" text-anchor="end">{escape(value)}</text>')
y += h + WIDGET_GAP
parts.append("</svg>")
return "\n".join(parts) + "\n"
def main():
pack = importlib.import_module("NH-Nodes")
os.makedirs(OUT_DIR, exist_ok=True)
index = []
for node_id, cls in sorted(pack.NODE_CLASS_MAPPINGS.items()):
display = pack.NODE_DISPLAY_NAME_MAPPINGS.get(node_id, node_id)
category = getattr(cls, "CATEGORY", "")
svg = svg_card(node_id, display, category, cls)
with open(os.path.join(OUT_DIR, f"{node_id}.svg"), "w", encoding="utf-8", newline="\n") as handle:
handle.write(svg)
index.append({"id": node_id, "name": display, "category": category,
"deprecated": bool(getattr(cls, "DEPRECATED", False))})
with open(os.path.join(OUT_DIR, "index.json"), "w", encoding="utf-8", newline="\n") as handle:
json.dump(index, handle, ensure_ascii=False, indent=1)
print(f"wrote {len(index)} cards to {OUT_DIR}")
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
+82
View File
@@ -0,0 +1,82 @@
"""Dump every NH node's live definition (inputs with defaults/ranges/options/tooltips, outputs) to JSON.
Run with ComfyUI's Python from the ComfyUI root: python dump_node_spec.py <out.json>
"""
import importlib
import json
import os
import sys
sys.path[:0] = [os.getcwd(), os.path.join(os.getcwd(), "custom_nodes")]
import torch # noqa: E402
if not torch.cuda.is_available():
from comfy.cli_args import args # noqa: E402
args.cpu = True
pack = importlib.import_module("NH-Nodes")
FILE_EXTENSIONS = (".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tif", ".tiff", ".gif", ".safetensors", ".ckpt",
".pt", ".pth", ".bin", ".gguf", ".onnx", ".sft")
def is_file_combo(options, opts):
"""Combos listing files from the user's folders (inputs, models): never publish those names."""
if opts.get("image_upload") or opts.get("video_upload"):
return True
return any(str(option).lower().endswith(FILE_EXTENSIONS) for option in options)
def jsonable(value):
try:
json.dumps(value)
return value
except TypeError:
return str(value)
spec = []
for node_id, cls in sorted(pack.NODE_CLASS_MAPPINGS.items()):
inputs = []
for section, entries in (cls.INPUT_TYPES() or {}).items():
if section == "hidden":
continue
for name, value in (entries or {}).items():
spec_type = value[0] if isinstance(value, (list, tuple)) and value else value
opts = value[1] if isinstance(value, (list, tuple)) and len(value) > 1 and isinstance(value[1], dict) else {}
entry = {"name": name, "section": section}
if isinstance(spec_type, (list, tuple)):
entry["type"] = "COMBO"
options = [str(o) for o in spec_type]
if is_file_combo(options, opts):
entry["options"] = ["(files from the user's folder)"]
opts = {k: v for k, v in opts.items() if k != "default"}
else:
entry["options"] = options if len(options) <= 40 else options[:40] + [f"... ({len(options)} total)"]
else:
entry["type"] = str(spec_type)
for key in ("default", "min", "max", "step", "multiline", "tooltip", "forceInput", "lazy"):
if key in opts:
entry[key] = jsonable(opts[key])
inputs.append(entry)
names = list(getattr(cls, "RETURN_NAMES", None) or getattr(cls, "RETURN_TYPES", ()))
types = [str(t) for t in getattr(cls, "RETURN_TYPES", ())]
is_list = list(getattr(cls, "OUTPUT_IS_LIST", ()) or [])
outputs = [{"name": n, "type": t, "is_list": bool(is_list[i]) if i < len(is_list) else False}
for i, (n, t) in enumerate(zip(names, types))]
spec.append({
"id": node_id,
"name": pack.NODE_DISPLAY_NAME_MAPPINGS.get(node_id, node_id),
"category": getattr(cls, "CATEGORY", ""),
"module": cls.__module__.replace("NH-Nodes.", ""),
"description": getattr(cls, "DESCRIPTION", "") or (cls.__doc__ or "").strip(),
"output_node": bool(getattr(cls, "OUTPUT_NODE", False)),
"deprecated": bool(getattr(cls, "DEPRECATED", False)),
"inputs": inputs,
"outputs": outputs,
})
with open(sys.argv[1], "w", encoding="utf-8") as handle:
json.dump(spec, handle, ensure_ascii=False, indent=1)
print(f"{len(spec)} nodes -> {sys.argv[1]}")
Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

@@ -0,0 +1,42 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="287" viewBox="0 0 360 287" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Agnostic Image Generator node">
<rect x="0.5" y="0.5" width="359" height="286" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Agnostic Image Generator</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_AgnosticImageGenerator · Image</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">agnostic_img</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">mask</text>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">masked_img</text>
<circle cx="348" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">composite</text>
<circle cx="348" cy="111.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">fill_mode</text>
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">gray</text>
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="175" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="175" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="175" font-size="11.5" fill="#9AA0A8">blur_radius</text>
<text x="326" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">65</text>
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">noise_strength</text>
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">1.00</text>
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="233" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="233" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="233" font-size="11.5" fill="#9AA0A8">gray_value</text>
<text x="326" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.50</text>
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="262" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="262" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="262" font-size="11.5" fill="#9AA0A8">feathering</text>
<text x="326" y="262" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

+36
View File
@@ -0,0 +1,36 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="347" viewBox="0 0 360 347" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Any List Split (NH) node">
<rect x="0.5" y="0.5" width="359" height="346" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Any List Split (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_AnyListSplit · Batch</text>
<circle cx="12" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">items</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">All ≡</text>
<circle cx="348" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">index 1</text>
<circle cx="348" cy="89.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">index 2</text>
<circle cx="348" cy="111.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">index 3</text>
<circle cx="348" cy="133.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">index 4</text>
<circle cx="348" cy="155.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="181.0" font-size="12" fill="#D7DADF" text-anchor="end">index 5</text>
<circle cx="348" cy="177.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="203.0" font-size="12" fill="#D7DADF" text-anchor="end">index 6</text>
<circle cx="348" cy="199.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="225.0" font-size="12" fill="#D7DADF" text-anchor="end">index 7</text>
<circle cx="348" cy="221.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="247.0" font-size="12" fill="#D7DADF" text-anchor="end">index 8</text>
<circle cx="348" cy="243.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="269.0" font-size="12" fill="#D7DADF" text-anchor="end">index 9</text>
<circle cx="348" cy="265.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="291.0" font-size="12" fill="#D7DADF" text-anchor="end">index 10</text>
<circle cx="348" cy="287.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="306" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="322" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="322" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="322" font-size="11.5" fill="#9AA0A8">index_count</text>
<text x="326" y="322" font-size="11.5" fill="#E6E8EB" text-anchor="end">4</text>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

+26
View File
@@ -0,0 +1,26 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="237" viewBox="0 0 360 237" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Any Branch Switch (NH) node">
<rect x="0.5" y="0.5" width="359" height="236" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Any Branch Switch (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_AnySwitch · Logic</text>
<circle cx="12" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">input</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">output 0</text>
<circle cx="348" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">output 1</text>
<circle cx="348" cy="89.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">output 2</text>
<circle cx="348" cy="111.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">output 3</text>
<circle cx="348" cy="133.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">output 4</text>
<circle cx="348" cy="155.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="181.0" font-size="12" fill="#D7DADF" text-anchor="end">index</text>
<circle cx="348" cy="177.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="196" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="212" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="212" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="212" font-size="11.5" fill="#9AA0A8">index</text>
<text x="326" y="212" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+19
View File
@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="149" viewBox="0 0 360 149" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Any Switch (NH) node">
<rect x="0.5" y="0.5" width="359" height="148" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Any Switch (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_AnySwitchBoolean · Logic</text>
<circle cx="12" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">input_true</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
<circle cx="348" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">input_false</text>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">condition</text>
<circle cx="348" cy="89.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="108" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="124" font-size="11.5" fill="#9AA0A8">condition</text>
<rect x="308" y="114" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="120.5" r="5" fill="#F5F6F7"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

+28
View File
@@ -0,0 +1,28 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="207" viewBox="0 0 360 207" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Batch Index (NH) node">
<rect x="0.5" y="0.5" width="359" height="206" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Batch Index (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_BatchIndex · Batch</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">batch</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">original_count</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="108" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="124" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="124" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="124" font-size="11.5" fill="#9AA0A8">index</text>
<text x="326" y="124" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="137" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="153" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="153" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="153" font-size="11.5" fill="#9AA0A8">end_index <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="153" font-size="11.5" fill="#E6E8EB" text-anchor="end">-1</text>
<rect x="12" y="166" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="182" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="182" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="182" font-size="11.5" fill="#9AA0A8">step <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="182" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

+22
View File
@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="171" viewBox="0 0 360 171" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Batch Merge (NH) node">
<rect x="0.5" y="0.5" width="359" height="170" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Batch Merge (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_BatchMerge · Batch</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">batch_a</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">batch_b <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">count</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="111.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="115.0" font-size="12" fill="#D7DADF">batch_c <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">resize_mode <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">stretch</text>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+13
View File
@@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="127" viewBox="0 0 360 127" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Boolean Switch (NH) node">
<rect x="0.5" y="0.5" width="359" height="126" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Boolean Switch (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_BooleanSwitch · Utils</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">boolean</text>
<circle cx="348" cy="67.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="86" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="102" font-size="11.5" fill="#9AA0A8">boolean_switch</text>
<rect x="308" y="92" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="98.5" r="5" fill="#F5F6F7"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+55
View File
@@ -0,0 +1,55 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="432" viewBox="0 0 360 432" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Load Clip Index (NH) node">
<rect x="0.5" y="0.5" width="359" height="431" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Load Clip Index (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ClipIndex · Loaders</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">clip</text>
<circle cx="348" cy="67.0" r="5" fill="#FFD500" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">filename</text>
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">index</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">select_mode</text>
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">index</text>
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="175" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="175" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="175" font-size="11.5" fill="#9AA0A8">type</text>
<text x="326" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">stable_diffusion</text>
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">index</text>
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="233" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="233" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="233" font-size="11.5" fill="#9AA0A8">clip_count</text>
<text x="326" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">5</text>
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="262" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="262" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="262" font-size="11.5" fill="#9AA0A8">clip_1 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="262" font-size="11.5" fill="#E6E8EB" text-anchor="end">(choose a file)</text>
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="291" font-size="11.5" fill="#9AA0A8">enable_1 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<rect x="308" y="281" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="287.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="304" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="320" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="320" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="320" font-size="11.5" fill="#9AA0A8">clip_2 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="320" font-size="11.5" fill="#E6E8EB" text-anchor="end">(choose a file)</text>
<rect x="12" y="333" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="349" font-size="11.5" fill="#9AA0A8">enable_2 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<rect x="308" y="339" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="345.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="362" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="180.0" y="378" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">clip_3 … clip_64</text>
<rect x="12" y="391" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="180.0" y="407" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">enable_3 … enable_64</text>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

+27
View File
@@ -0,0 +1,27 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="200" viewBox="0 0 360 200" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Compare (NH) node">
<rect x="0.5" y="0.5" width="359" height="199" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Compare (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_Compare · Logic</text>
<circle cx="12" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">a</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
<circle cx="348" cy="67.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">b</text>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">a_passthrough</text>
<circle cx="348" cy="89.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">b_passthrough</text>
<circle cx="348" cy="111.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">op</text>
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">is</text>
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="175" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="175" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="175" font-size="11.5" fill="#9AA0A8">type_cast <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">auto</text>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+34
View File
@@ -0,0 +1,34 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="280" viewBox="0 0 360 280" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Counter (NH) node">
<rect x="0.5" y="0.5" width="359" height="279" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Counter (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_Counter · Batch</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">current</text>
<circle cx="348" cy="67.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">is_done</text>
<circle cx="348" cy="89.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">progress</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">remaining</text>
<circle cx="348" cy="133.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="168" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="168" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="168" font-size="11.5" fill="#9AA0A8">start</text>
<text x="326" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="197" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="197" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="197" font-size="11.5" fill="#9AA0A8">step</text>
<text x="326" y="197" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
<rect x="12" y="210" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="226" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="226" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="226" font-size="11.5" fill="#9AA0A8">max_value</text>
<text x="326" y="226" font-size="11.5" fill="#E6E8EB" text-anchor="end">9</text>
<rect x="12" y="239" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="255" font-size="11.5" fill="#9AA0A8">reset</text>
<rect x="308" y="245" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="251.5" r="5" fill="#F5F6F7"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -0,0 +1,55 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="432" viewBox="0 0 360 432" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Load Diffusion Model Index (NH) node">
<rect x="0.5" y="0.5" width="359" height="431" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Load Diffusion Model Index (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_DiffusionModelIndex · Loaders</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">model</text>
<circle cx="348" cy="67.0" r="5" fill="#B39DDB" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">filename</text>
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">index</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">select_mode</text>
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">index</text>
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="175" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="175" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="175" font-size="11.5" fill="#9AA0A8">index</text>
<text x="326" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">model_count</text>
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">5</text>
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="233" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="233" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="233" font-size="11.5" fill="#9AA0A8">weight_dtype</text>
<text x="326" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">default</text>
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="262" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="262" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="262" font-size="11.5" fill="#9AA0A8">diffusion_model_1 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="262" font-size="11.5" fill="#E6E8EB" text-anchor="end">—</text>
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="291" font-size="11.5" fill="#9AA0A8">enable_1 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<rect x="308" y="281" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="287.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="304" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="320" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="320" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="320" font-size="11.5" fill="#9AA0A8">diffusion_model_2 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="320" font-size="11.5" fill="#E6E8EB" text-anchor="end">—</text>
<rect x="12" y="333" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="349" font-size="11.5" fill="#9AA0A8">enable_2 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<rect x="308" y="339" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="345.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="362" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="180.0" y="378" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">diffusion_model_3 … diffusion_model_64</text>
<rect x="12" y="391" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="180.0" y="407" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">enable_3 … enable_64</text>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

+43
View File
@@ -0,0 +1,43 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="316" viewBox="0 0 360 316" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Garment Segment (NH) node">
<rect x="0.5" y="0.5" width="359" height="315" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Garment Segment (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_GarmentSegment · Vision</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">mask</text>
<circle cx="348" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">cutout_rgba</text>
<circle cx="348" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">mask_image</text>
<circle cx="348" cy="111.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">part</text>
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">upper_garment</text>
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="175" font-size="11.5" fill="#9AA0A8">attach_parts</text>
<rect x="308" y="165" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="171.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">mask_expand</text>
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="233" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="233" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="233" font-size="11.5" fill="#9AA0A8">mask_blur</text>
<text x="326" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="262" font-size="11.5" fill="#9AA0A8">fill_holes</text>
<rect x="308" y="252" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="258.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="291" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="291" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="291" font-size="11.5" fill="#9AA0A8">device</text>
<text x="326" y="291" font-size="11.5" fill="#E6E8EB" text-anchor="end">cuda</text>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

+15
View File
@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="127" viewBox="0 0 360 127" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Gate Switch (NH) node">
<rect x="0.5" y="0.5" width="359" height="126" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Gate Switch (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_GateSwitch · Logic</text>
<circle cx="12" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">input</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">output</text>
<circle cx="348" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="86" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="102" font-size="11.5" fill="#9AA0A8">enabled</text>
<rect x="308" y="92" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="98.5" r="5" fill="#F5F6F7"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+19
View File
@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="149" viewBox="0 0 360 149" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="If/Else (NH) node">
<rect x="0.5" y="0.5" width="359" height="148" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">If/Else (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_IfElse · Logic</text>
<circle cx="12" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">if_true</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
<circle cx="348" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">if_false</text>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">condition_out</text>
<circle cx="348" cy="89.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="108" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="124" font-size="11.5" fill="#9AA0A8">condition</text>
<rect x="308" y="114" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="120.5" r="5" fill="#F5F6F7"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

+33
View File
@@ -0,0 +1,33 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="236" viewBox="0 0 360 236" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Image Compare (NH) node">
<rect x="0.5" y="0.5" width="359" height="235" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Image Compare (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ImageCompare · Image</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image_a</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">image_b</text>
<rect x="12" y="108" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="124" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="124" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="124" font-size="11.5" fill="#9AA0A8">compare_mode</text>
<text x="326" y="124" font-size="11.5" fill="#E6E8EB" text-anchor="end">side_by_side</text>
<rect x="12" y="137" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="153" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="153" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="153" font-size="11.5" fill="#9AA0A8">split_position</text>
<text x="326" y="153" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.50</text>
<rect x="12" y="166" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="182" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="182" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="182" font-size="11.5" fill="#9AA0A8">overlay_opacity</text>
<text x="326" y="182" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.50</text>
<rect x="12" y="195" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="211" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="211" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="211" font-size="11.5" fill="#9AA0A8">resize_method</text>
<text x="326" y="211" font-size="11.5" fill="#E6E8EB" text-anchor="end">lanczos</text>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@@ -0,0 +1,60 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="425" viewBox="0 0 360 425" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Image Grid Composite (NH) node">
<rect x="0.5" y="0.5" width="359" height="424" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Image Grid Composite (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ImageGridComposite · Image</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">images</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">slot_count</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">used_count</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">grid_info</text>
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="168" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="168" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="168" font-size="11.5" fill="#9AA0A8">columns</text>
<text x="326" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">2</text>
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="197" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="197" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="197" font-size="11.5" fill="#9AA0A8">rows</text>
<text x="326" y="197" font-size="11.5" fill="#E6E8EB" text-anchor="end">2</text>
<rect x="12" y="210" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="226" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="226" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="226" font-size="11.5" fill="#9AA0A8">cell_size_mode</text>
<text x="326" y="226" font-size="11.5" fill="#E6E8EB" text-anchor="end">manual</text>
<rect x="12" y="239" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="255" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="255" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="255" font-size="11.5" fill="#9AA0A8">cell_width</text>
<text x="326" y="255" font-size="11.5" fill="#E6E8EB" text-anchor="end">512</text>
<rect x="12" y="268" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="284" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="284" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="284" font-size="11.5" fill="#9AA0A8">cell_height</text>
<text x="326" y="284" font-size="11.5" fill="#E6E8EB" text-anchor="end">512</text>
<rect x="12" y="297" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="313" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="313" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="313" font-size="11.5" fill="#9AA0A8">resize_mode</text>
<text x="326" y="313" font-size="11.5" fill="#E6E8EB" text-anchor="end">stretch</text>
<rect x="12" y="326" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="342" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="342" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="342" font-size="11.5" fill="#9AA0A8">gap_x</text>
<text x="326" y="342" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="355" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="371" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="371" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="371" font-size="11.5" fill="#9AA0A8">gap_y</text>
<text x="326" y="371" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="384" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="400" font-size="11.5" fill="#9AA0A8">background_color_hex</text>
<text x="338" y="400" font-size="11.5" fill="#E6E8EB" text-anchor="end">#000000</text>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

+70
View File
@@ -0,0 +1,70 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="526" viewBox="0 0 360 526" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Image Label (NH) node">
<rect x="0.5" y="0.5" width="359" height="525" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Image Label (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ImageLabel · Image</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="86" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="102" font-size="11.5" fill="#9AA0A8">label_text</text>
<text x="22" y="122" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH Label</text>
<rect x="12" y="137" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="153" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="153" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="153" font-size="11.5" fill="#9AA0A8">position</text>
<text x="326" y="153" font-size="11.5" fill="#E6E8EB" text-anchor="end">header</text>
<rect x="12" y="166" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="182" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="182" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="182" font-size="11.5" fill="#9AA0A8">frame_height</text>
<text x="326" y="182" font-size="11.5" fill="#E6E8EB" text-anchor="end">80</text>
<rect x="12" y="195" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="211" font-size="11.5" fill="#9AA0A8">fill_enabled</text>
<rect x="308" y="201" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="207.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="224" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="240" font-size="11.5" fill="#9AA0A8">frame_color_hex</text>
<text x="338" y="240" font-size="11.5" fill="#E6E8EB" text-anchor="end">#111111</text>
<rect x="12" y="253" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="269" font-size="11.5" fill="#9AA0A8">text_color_hex</text>
<text x="338" y="269" font-size="11.5" fill="#E6E8EB" text-anchor="end">#FFFFFF</text>
<rect x="12" y="282" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="298" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="298" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="298" font-size="11.5" fill="#9AA0A8">font_name</text>
<text x="326" y="298" font-size="11.5" fill="#E6E8EB" text-anchor="end">default</text>
<rect x="12" y="311" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="327" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="327" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="327" font-size="11.5" fill="#9AA0A8">font_size</text>
<text x="326" y="327" font-size="11.5" fill="#E6E8EB" text-anchor="end">32</text>
<rect x="12" y="340" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="356" font-size="11.5" fill="#9AA0A8">fit_text</text>
<rect x="308" y="346" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="352.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="369" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="385" font-size="11.5" fill="#9AA0A8">overlay_on_image</text>
<rect x="308" y="375" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="381.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="398" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="414" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="414" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="414" font-size="11.5" fill="#9AA0A8">align</text>
<text x="326" y="414" font-size="11.5" fill="#E6E8EB" text-anchor="end">left</text>
<rect x="12" y="427" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="443" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="443" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="443" font-size="11.5" fill="#9AA0A8">padding_x</text>
<text x="326" y="443" font-size="11.5" fill="#E6E8EB" text-anchor="end">20</text>
<rect x="12" y="456" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="472" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="472" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="472" font-size="11.5" fill="#9AA0A8">outline_width</text>
<text x="326" y="472" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="485" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="501" font-size="11.5" fill="#9AA0A8">outline_color_hex</text>
<text x="338" y="501" font-size="11.5" fill="#E6E8EB" text-anchor="end">#000000</text>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

@@ -0,0 +1,48 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="345" viewBox="0 0 360 345" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Image Resize Unit (NH) node">
<rect x="0.5" y="0.5" width="359" height="344" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Image Resize Unit (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ImageResizeByUnit · Image</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">width_px</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">height_px</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">unit</text>
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">pixel</text>
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="175" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="175" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="175" font-size="11.5" fill="#9AA0A8">width_value</text>
<text x="326" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">1024.0</text>
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">height_value</text>
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">1024.0</text>
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="233" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="233" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="233" font-size="11.5" fill="#9AA0A8">dpi</text>
<text x="326" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">300</text>
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="262" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="262" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="262" font-size="11.5" fill="#9AA0A8">resize_mode</text>
<text x="326" y="262" font-size="11.5" fill="#E6E8EB" text-anchor="end">stretch</text>
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="291" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="291" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="291" font-size="11.5" fill="#9AA0A8">resize_method</text>
<text x="326" y="291" font-size="11.5" fill="#E6E8EB" text-anchor="end">nearest</text>
<rect x="12" y="304" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="320" font-size="11.5" fill="#9AA0A8">pad_color_hex</text>
<text x="338" y="320" font-size="11.5" fill="#E6E8EB" text-anchor="end">#000000</text>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

+41
View File
@@ -0,0 +1,41 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="324" viewBox="0 0 360 324" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Image Tile (NH) node">
<rect x="0.5" y="0.5" width="359" height="323" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Image Tile (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ImageTile · Image</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">tiles</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">tile_data</text>
<circle cx="348" cy="89.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">tile_count</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">overlap_x</text>
<circle cx="348" cy="133.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">overlap_y</text>
<circle cx="348" cy="155.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="181.0" font-size="12" fill="#D7DADF" text-anchor="end">tile_info</text>
<circle cx="348" cy="177.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="196" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="212" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="212" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="212" font-size="11.5" fill="#9AA0A8">tile_mode</text>
<text x="326" y="212" font-size="11.5" fill="#E6E8EB" text-anchor="end">original_ratio</text>
<rect x="12" y="225" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="241" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="241" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="241" font-size="11.5" fill="#9AA0A8">tile_size</text>
<text x="326" y="241" font-size="11.5" fill="#E6E8EB" text-anchor="end">1024</text>
<rect x="12" y="254" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="270" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="270" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="270" font-size="11.5" fill="#9AA0A8">tile_width</text>
<text x="326" y="270" font-size="11.5" fill="#E6E8EB" text-anchor="end">1024</text>
<rect x="12" y="283" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="299" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="299" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="299" font-size="11.5" fill="#9AA0A8">tile_height</text>
<text x="326" y="299" font-size="11.5" fill="#E6E8EB" text-anchor="end">1024</text>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

+15
View File
@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="112" viewBox="0 0 360 112" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Image Untile (NH) node">
<rect x="0.5" y="0.5" width="359" height="111" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Image Untile (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ImageUntile · Image</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">tiles</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">tile_data</text>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">untile_info</text>
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,64 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="432" viewBox="0 0 360 432" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Large Image Compare node">
<rect x="0.5" y="0.5" width="359" height="431" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#F6AD55"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Large Image Compare</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LargeImageCompare · Image</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image_a</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image_a</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">image_b</text>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">image_b</text>
<circle cx="348" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">manifest</text>
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">compare_mode</text>
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">slider</text>
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="175" font-size="11.5" fill="#9AA0A8">sync_pan_zoom</text>
<rect x="308" y="165" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="171.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">preview_megapixels</text>
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">1.00</text>
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="233" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="233" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="233" font-size="11.5" fill="#9AA0A8">preview_format</text>
<text x="326" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">jpg</text>
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="262" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="262" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="262" font-size="11.5" fill="#9AA0A8">preview_quality</text>
<text x="326" y="262" font-size="11.5" fill="#E6E8EB" text-anchor="end">90</text>
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="291" font-size="11.5" fill="#9AA0A8">title</text>
<text x="338" y="291" font-size="11.5" fill="#E6E8EB" text-anchor="end">NH Large Image Compare</text>
<rect x="12" y="304" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="320" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="320" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="320" font-size="11.5" fill="#9AA0A8">tile_size <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="320" font-size="11.5" fill="#E6E8EB" text-anchor="end">512</text>
<rect x="12" y="333" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="349" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="349" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="349" font-size="11.5" fill="#9AA0A8">tile_format <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="349" font-size="11.5" fill="#E6E8EB" text-anchor="end">jpg</text>
<rect x="12" y="362" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="378" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="378" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="378" font-size="11.5" fill="#9AA0A8">tile_quality <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="378" font-size="11.5" fill="#E6E8EB" text-anchor="end">90</text>
<rect x="12" y="391" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="407" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="407" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="407" font-size="11.5" fill="#9AA0A8">cache_policy <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="407" font-size="11.5" fill="#E6E8EB" text-anchor="end">temp</text>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

@@ -0,0 +1,56 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="381" viewBox="0 0 360 381" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Large Image Preview node">
<rect x="0.5" y="0.5" width="359" height="380" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#F6AD55"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Large Image Preview</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LargeImagePreview · Image</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">manifest</text>
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="108" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="124" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="124" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="124" font-size="11.5" fill="#9AA0A8">preview_megapixels</text>
<text x="326" y="124" font-size="11.5" fill="#E6E8EB" text-anchor="end">1.00</text>
<rect x="12" y="137" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="153" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="153" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="153" font-size="11.5" fill="#9AA0A8">preview_format</text>
<text x="326" y="153" font-size="11.5" fill="#E6E8EB" text-anchor="end">jpg</text>
<rect x="12" y="166" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="182" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="182" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="182" font-size="11.5" fill="#9AA0A8">preview_quality</text>
<text x="326" y="182" font-size="11.5" fill="#E6E8EB" text-anchor="end">90</text>
<rect x="12" y="195" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="211" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="211" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="211" font-size="11.5" fill="#9AA0A8">viewer_mode</text>
<text x="326" y="211" font-size="11.5" fill="#E6E8EB" text-anchor="end">modal</text>
<rect x="12" y="224" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="240" font-size="11.5" fill="#9AA0A8">title</text>
<text x="338" y="240" font-size="11.5" fill="#E6E8EB" text-anchor="end">NH Large Image Preview</text>
<rect x="12" y="253" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="269" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="269" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="269" font-size="11.5" fill="#9AA0A8">tile_size <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="269" font-size="11.5" fill="#E6E8EB" text-anchor="end">512</text>
<rect x="12" y="282" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="298" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="298" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="298" font-size="11.5" fill="#9AA0A8">tile_format <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="298" font-size="11.5" fill="#E6E8EB" text-anchor="end">jpg</text>
<rect x="12" y="311" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="327" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="327" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="327" font-size="11.5" fill="#9AA0A8">tile_quality <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="327" font-size="11.5" fill="#E6E8EB" text-anchor="end">90</text>
<rect x="12" y="340" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="356" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="356" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="356" font-size="11.5" fill="#9AA0A8">cache_policy <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="356" font-size="11.5" fill="#E6E8EB" text-anchor="end">temp</text>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

@@ -0,0 +1,33 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="280" viewBox="0 0 360 280" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Large Preview Cache Manager node">
<rect x="0.5" y="0.5" width="359" height="279" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#F6AD55"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Large Preview Cache Manager</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LargePreviewCacheManager · Image</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">summary</text>
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">removed</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">freed_mb</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">cache_root</text>
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="168" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="168" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="168" font-size="11.5" fill="#9AA0A8">action</text>
<text x="326" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">info</text>
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="197" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="197" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="197" font-size="11.5" fill="#9AA0A8">max_age_hours</text>
<text x="326" y="197" font-size="11.5" fill="#E6E8EB" text-anchor="end">24.0</text>
<rect x="12" y="210" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="226" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="226" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="226" font-size="11.5" fill="#9AA0A8">max_total_mb</text>
<text x="326" y="226" font-size="11.5" fill="#E6E8EB" text-anchor="end">2048.0</text>
<rect x="12" y="239" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="255" font-size="11.5" fill="#9AA0A8">cache_id</text>
<text x="338" y="255" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -0,0 +1,90 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="621" viewBox="0 0 360 621" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Layer Layout Composite (NH) node">
<rect x="0.5" y="0.5" width="359" height="620" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Layer Layout Composite (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LayerLayoutComposite · Image</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">images</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">background_image <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">slot_count</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">used_count</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">layout_info</text>
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="168" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="168" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="168" font-size="11.5" fill="#9AA0A8">canvas_width</text>
<text x="326" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">2480</text>
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="197" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="197" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="197" font-size="11.5" fill="#9AA0A8">canvas_height</text>
<text x="326" y="197" font-size="11.5" fill="#E6E8EB" text-anchor="end">3508</text>
<rect x="12" y="210" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="226" font-size="11.5" fill="#9AA0A8">background_color</text>
<text x="338" y="226" font-size="11.5" fill="#E6E8EB" text-anchor="end">#FFFFFF</text>
<rect x="12" y="239" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="255" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="255" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="255" font-size="11.5" fill="#9AA0A8">layout_mode</text>
<text x="326" y="255" font-size="11.5" fill="#E6E8EB" text-anchor="end">preset</text>
<rect x="12" y="268" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="284" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="284" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="284" font-size="11.5" fill="#9AA0A8">layout_preset</text>
<text x="326" y="284" font-size="11.5" fill="#E6E8EB" text-anchor="end">2x3</text>
<rect x="12" y="297" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="313" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="313" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="313" font-size="11.5" fill="#9AA0A8">resize_mode</text>
<text x="326" y="313" font-size="11.5" fill="#E6E8EB" text-anchor="end">cover</text>
<rect x="12" y="326" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="342" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="342" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="342" font-size="11.5" fill="#9AA0A8">columns</text>
<text x="326" y="342" font-size="11.5" fill="#E6E8EB" text-anchor="end">2</text>
<rect x="12" y="355" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="371" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="371" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="371" font-size="11.5" fill="#9AA0A8">rows</text>
<text x="326" y="371" font-size="11.5" fill="#E6E8EB" text-anchor="end">3</text>
<rect x="12" y="384" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="400" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="400" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="400" font-size="11.5" fill="#9AA0A8">margin_x</text>
<text x="326" y="400" font-size="11.5" fill="#E6E8EB" text-anchor="end">150</text>
<rect x="12" y="413" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="429" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="429" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="429" font-size="11.5" fill="#9AA0A8">margin_y</text>
<text x="326" y="429" font-size="11.5" fill="#E6E8EB" text-anchor="end">150</text>
<rect x="12" y="442" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="458" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="458" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="458" font-size="11.5" fill="#9AA0A8">gap_x</text>
<text x="326" y="458" font-size="11.5" fill="#E6E8EB" text-anchor="end">150</text>
<rect x="12" y="471" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="487" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="487" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="487" font-size="11.5" fill="#9AA0A8">gap_y</text>
<text x="326" y="487" font-size="11.5" fill="#E6E8EB" text-anchor="end">150</text>
<rect x="12" y="500" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="516" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="516" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="516" font-size="11.5" fill="#9AA0A8">custom_units</text>
<text x="326" y="516" font-size="11.5" fill="#E6E8EB" text-anchor="end">auto</text>
<rect x="12" y="529" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="545" font-size="11.5" fill="#9AA0A8">custom_layout</text>
<text x="22" y="565" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">0.06,0.04,0.39,0.20 ⏎ 0.55,0.…</text>
<rect x="12" y="580" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="596" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="596" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="596" font-size="11.5" fill="#9AA0A8">background_fit</text>
<text x="326" y="596" font-size="11.5" fill="#E6E8EB" text-anchor="end">stretch</text>
</svg>

After

Width:  |  Height:  |  Size: 6.9 KiB

@@ -0,0 +1,29 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="251" viewBox="0 0 360 251" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Layer Stack Composite (NH) node">
<rect x="0.5" y="0.5" width="359" height="250" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Layer Stack Composite (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LayerStackComposite · Image</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">background_image</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">layer_1 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">used_count</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="111.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="115.0" font-size="12" fill="#D7DADF">layer_2 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">layer_info</text>
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">layer_count</text>
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
<rect x="12" y="159" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="175" font-size="11.5" fill="#9AA0A8">positions</text>
<text x="22" y="195" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">0,0</text>
<rect x="12" y="210" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="180.0" y="226" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">layer_3 … layer_64</text>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

+21
View File
@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="244" viewBox="0 0 360 244" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="List Create (NH) node">
<rect x="0.5" y="0.5" width="359" height="243" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">List Create (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ListCreate · Batch</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">items</text>
<circle cx="348" cy="67.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">count</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">first</text>
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">last</text>
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="152" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="168" font-size="11.5" fill="#9AA0A8">text</text>
<text x="22" y="188" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">item 1 ⏎ item 2 ⏎ item 3</text>
<rect x="12" y="203" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="219" font-size="11.5" fill="#9AA0A8">delimiter <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="338" y="219" font-size="11.5" fill="#E6E8EB" text-anchor="end">\n</text>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

+25
View File
@@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="222" viewBox="0 0 360 222" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="List Filter (NH) node">
<rect x="0.5" y="0.5" width="359" height="221" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">List Filter (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ListFilter · Batch</text>
<circle cx="12" cy="67.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">items</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">passed</text>
<circle cx="348" cy="67.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">rejected</text>
<circle cx="348" cy="89.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">passed_count</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">rejected_count</text>
<circle cx="348" cy="133.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="168" font-size="11.5" fill="#9AA0A8">condition</text>
<text x="338" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">contains:dress</text>
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="197" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="197" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="197" font-size="11.5" fill="#9AA0A8">mode</text>
<text x="326" y="197" font-size="11.5" fill="#E6E8EB" text-anchor="end">include</text>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

+26
View File
@@ -0,0 +1,26 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="222" viewBox="0 0 360 222" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="List Index (NH) node">
<rect x="0.5" y="0.5" width="359" height="221" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">List Index (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ListIndex · Batch</text>
<circle cx="12" cy="67.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">items</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">item</text>
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">is_first</text>
<circle cx="348" cy="89.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">is_last</text>
<circle cx="348" cy="111.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">count</text>
<circle cx="348" cy="133.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="168" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="168" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="168" font-size="11.5" fill="#9AA0A8">index</text>
<text x="326" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="197" font-size="11.5" fill="#9AA0A8">wrap <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<rect x="308" y="187" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="193.5" r="5" fill="#F5F6F7"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+28
View File
@@ -0,0 +1,28 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="251" viewBox="0 0 360 251" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Load Image Info (NH) node">
<rect x="0.5" y="0.5" width="359" height="250" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Load Image Info (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LoadImageInfo · Image</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">mask</text>
<circle cx="348" cy="89.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">file_name</text>
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">path</text>
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="168" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="168" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="168" font-size="11.5" fill="#9AA0A8">image</text>
<text x="326" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">(choose a file)</text>
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="197" font-size="11.5" fill="#9AA0A8">filename_extension</text>
<rect x="308" y="187" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="193.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="210" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="226" font-size="11.5" fill="#9AA0A8">path_include_filename</text>
<rect x="308" y="216" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="222.5" r="5" fill="#F5F6F7"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,44 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="345" viewBox="0 0 360 345" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Load Images Folder (NH) node">
<rect x="0.5" y="0.5" width="359" height="344" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Load Images Folder (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LoadImagesFromFolder · Image</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">images</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">filename</text>
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">path</text>
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="146" font-size="11.5" fill="#9AA0A8">folder_path</text>
<text x="338" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="175" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="175" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="175" font-size="11.5" fill="#9AA0A8">index</text>
<text x="326" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">step</text>
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="233" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="233" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="233" font-size="11.5" fill="#9AA0A8">seed_mode</text>
<text x="326" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">fixed</text>
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="262" font-size="11.5" fill="#9AA0A8">recursive</text>
<rect x="308" y="252" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="258.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="291" font-size="11.5" fill="#9AA0A8">filename_extension</text>
<rect x="308" y="281" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="287.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="304" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="320" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="320" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="320" font-size="11.5" fill="#9AA0A8">path_output</text>
<text x="326" y="320" font-size="11.5" fill="#E6E8EB" text-anchor="end">file_path</text>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

@@ -0,0 +1,58 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="520" viewBox="0 0 360 520" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Load images matching node">
<rect x="0.5" y="0.5" width="359" height="519" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Load images matching</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LoadImagesMatching · Image</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">fallback_images <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">images</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">filename</text>
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">path</text>
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">matched_text</text>
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">found</text>
<circle cx="348" cy="155.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="174" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="190" font-size="11.5" fill="#9AA0A8">source_folder_path</text>
<text x="338" y="190" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
<rect x="12" y="203" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="219" font-size="11.5" fill="#9AA0A8">matching_root_path</text>
<text x="338" y="219" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
<rect x="12" y="232" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="248" font-size="11.5" fill="#9AA0A8">match_text</text>
<text x="338" y="248" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
<rect x="12" y="261" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="277" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="277" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="277" font-size="11.5" fill="#9AA0A8">match_mode</text>
<text x="326" y="277" font-size="11.5" fill="#E6E8EB" text-anchor="end">contains</text>
<rect x="12" y="290" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="306" font-size="11.5" fill="#9AA0A8">recursive</text>
<rect x="308" y="296" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="302.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="319" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="335" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="335" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="335" font-size="11.5" fill="#9AA0A8">index</text>
<text x="326" y="335" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="348" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="364" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="364" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="364" font-size="11.5" fill="#9AA0A8">step</text>
<text x="326" y="364" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
<rect x="12" y="377" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="393" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="393" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="393" font-size="11.5" fill="#9AA0A8">repeat_count</text>
<text x="326" y="393" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
<rect x="12" y="406" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="422" font-size="11.5" fill="#9AA0A8">context_folder_names</text>
<text x="22" y="442" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">Nam ⏎ Nu</text>
<rect x="12" y="457" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="473" font-size="11.5" fill="#9AA0A8">source_file_names <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="22" y="493" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">…</text>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

+22
View File
@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="185" viewBox="0 0 360 185" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Logic Gate (NH) node">
<rect x="0.5" y="0.5" width="359" height="184" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Logic Gate (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LogicGate · Logic</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
<circle cx="348" cy="67.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="86" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="102" font-size="11.5" fill="#9AA0A8">a</text>
<rect x="308" y="92" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="98.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="115" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="131" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="131" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="131" font-size="11.5" fill="#9AA0A8">op</text>
<text x="326" y="131" font-size="11.5" fill="#E6E8EB" text-anchor="end">AND</text>
<rect x="12" y="144" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="160" font-size="11.5" fill="#9AA0A8">b <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<rect x="308" y="150" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="156.5" r="5" fill="#F5F6F7"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+66
View File
@@ -0,0 +1,66 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="483" viewBox="0 0 360 483" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Load LoRA Clip Index (NH) node">
<rect x="0.5" y="0.5" width="359" height="482" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Load LoRA Clip Index (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LoraClipIndex · Loaders</text>
<circle cx="12" cy="67.0" r="5" fill="#B39DDB" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">model</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">model</text>
<circle cx="348" cy="67.0" r="5" fill="#B39DDB" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#FFD500" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">clip</text>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">clip</text>
<circle cx="348" cy="89.0" r="5" fill="#FFD500" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">filename</text>
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">index</text>
<circle cx="348" cy="133.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="168" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="168" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="168" font-size="11.5" fill="#9AA0A8">select_mode</text>
<text x="326" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">index</text>
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="197" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="197" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="197" font-size="11.5" fill="#9AA0A8">index</text>
<text x="326" y="197" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
<rect x="12" y="210" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="226" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="226" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="226" font-size="11.5" fill="#9AA0A8">lora_count</text>
<text x="326" y="226" font-size="11.5" fill="#E6E8EB" text-anchor="end">5</text>
<rect x="12" y="239" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="255" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="255" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="255" font-size="11.5" fill="#9AA0A8">strength_model</text>
<text x="326" y="255" font-size="11.5" fill="#E6E8EB" text-anchor="end">1.00</text>
<rect x="12" y="268" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="284" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="284" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="284" font-size="11.5" fill="#9AA0A8">strength_clip</text>
<text x="326" y="284" font-size="11.5" fill="#E6E8EB" text-anchor="end">1.00</text>
<rect x="12" y="297" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="313" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="313" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="313" font-size="11.5" fill="#9AA0A8">lora_1 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="313" font-size="11.5" fill="#E6E8EB" text-anchor="end">(choose a file)</text>
<rect x="12" y="326" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="342" font-size="11.5" fill="#9AA0A8">enable_1 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<rect x="308" y="332" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="338.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="355" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="371" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="371" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="371" font-size="11.5" fill="#9AA0A8">lora_2 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="371" font-size="11.5" fill="#E6E8EB" text-anchor="end">(choose a file)</text>
<rect x="12" y="384" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="400" font-size="11.5" fill="#9AA0A8">enable_2 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<rect x="308" y="390" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="396.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="413" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="180.0" y="429" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">lora_3 … lora_64</text>
<rect x="12" y="442" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="180.0" y="458" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">enable_3 … enable_64</text>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

+57
View File
@@ -0,0 +1,57 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="432" viewBox="0 0 360 432" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Load LoRA Model Index (NH) node">
<rect x="0.5" y="0.5" width="359" height="431" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Load LoRA Model Index (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LoraModelIndex · Loaders</text>
<circle cx="12" cy="67.0" r="5" fill="#B39DDB" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">model</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">model</text>
<circle cx="348" cy="67.0" r="5" fill="#B39DDB" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">filename</text>
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">index</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">select_mode</text>
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">index</text>
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="175" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="175" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="175" font-size="11.5" fill="#9AA0A8">index</text>
<text x="326" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">lora_count</text>
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">5</text>
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="233" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="233" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="233" font-size="11.5" fill="#9AA0A8">strength_model</text>
<text x="326" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">1.00</text>
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="262" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="262" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="262" font-size="11.5" fill="#9AA0A8">lora_1 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="262" font-size="11.5" fill="#E6E8EB" text-anchor="end">(choose a file)</text>
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="291" font-size="11.5" fill="#9AA0A8">enable_1 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<rect x="308" y="281" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="287.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="304" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="320" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="320" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="320" font-size="11.5" fill="#9AA0A8">lora_2 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="320" font-size="11.5" fill="#E6E8EB" text-anchor="end">(choose a file)</text>
<rect x="12" y="333" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="349" font-size="11.5" fill="#9AA0A8">enable_2 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<rect x="308" y="339" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="345.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="362" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="180.0" y="378" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">lora_3 … lora_64</text>
<rect x="12" y="391" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="180.0" y="407" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">enable_3 … enable_64</text>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

@@ -0,0 +1,30 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="214" viewBox="0 0 360 214" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Mask Morphology (NH) node">
<rect x="0.5" y="0.5" width="359" height="213" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Mask Morphology (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_MaskAdvancedMorphology · Mask</text>
<circle cx="12" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">mask</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">MASK</text>
<circle cx="348" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="86" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="102" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="102" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="102" font-size="11.5" fill="#9AA0A8">horizontal_expand</text>
<text x="326" y="102" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="115" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="131" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="131" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="131" font-size="11.5" fill="#9AA0A8">vertical_expand</text>
<text x="326" y="131" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="144" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="160" font-size="11.5" fill="#9AA0A8">fill_holes</text>
<rect x="308" y="150" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="156.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="173" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="189" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="189" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="189" font-size="11.5" fill="#9AA0A8">blur_radius</text>
<text x="326" y="189" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.0</text>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="149" viewBox="0 0 360 149" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Mask Aspect Ratio Match node">
<rect x="0.5" y="0.5" width="359" height="148" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Mask Aspect Ratio Match</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_MaskAspectRatioMatch · Mask</text>
<circle cx="12" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">target_ratio_mask</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">MASK</text>
<circle cx="348" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">mask_to_adjust</text>
<rect x="12" y="108" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="124" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="124" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="124" font-size="11.5" fill="#9AA0A8">mode</text>
<text x="326" y="124" font-size="11.5" fill="#E6E8EB" text-anchor="end">stretch</text>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+23
View File
@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="222" viewBox="0 0 360 222" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Mask Properties (NH) node">
<rect x="0.5" y="0.5" width="359" height="221" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Mask Properties (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_MaskProperties · Mask</text>
<circle cx="12" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">mask</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">width</text>
<circle cx="348" cy="67.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">height</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">x1</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">y1</text>
<circle cx="348" cy="133.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">x2</text>
<circle cx="348" cy="155.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="181.0" font-size="12" fill="#D7DADF" text-anchor="end">y2</text>
<circle cx="348" cy="177.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="203.0" font-size="12" fill="#D7DADF" text-anchor="end">bbox</text>
<circle cx="348" cy="199.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+33
View File
@@ -0,0 +1,33 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="236" viewBox="0 0 360 236" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Mask-Aware Resize node">
<rect x="0.5" y="0.5" width="359" height="235" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Mask-Aware Resize</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_MaskResizeImage · Image</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">mask</text>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">mask</text>
<circle cx="348" cy="89.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="108" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="124" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="124" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="124" font-size="11.5" fill="#9AA0A8">width</text>
<text x="326" y="124" font-size="11.5" fill="#E6E8EB" text-anchor="end">512</text>
<rect x="12" y="137" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="153" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="153" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="153" font-size="11.5" fill="#9AA0A8">height</text>
<text x="326" y="153" font-size="11.5" fill="#E6E8EB" text-anchor="end">512</text>
<rect x="12" y="166" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="182" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="182" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="182" font-size="11.5" fill="#9AA0A8">mode</text>
<text x="326" y="182" font-size="11.5" fill="#E6E8EB" text-anchor="end">crop</text>
<rect x="12" y="195" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="211" font-size="11.5" fill="#9AA0A8">pad_color_hex</text>
<text x="338" y="211" font-size="11.5" fill="#E6E8EB" text-anchor="end">#000000</text>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

+11
View File
@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="90" viewBox="0 0 360 90" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Create Box Mask node">
<rect x="0.5" y="0.5" width="359" height="89" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Create Box Mask</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_MaskToBBox · Mask</text>
<circle cx="12" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">mask</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">box_mask</text>
<circle cx="348" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 1005 B

+41
View File
@@ -0,0 +1,41 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="316" viewBox="0 0 360 316" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Math Eval (NH) node">
<rect x="0.5" y="0.5" width="359" height="315" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Math Eval (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_MathEval · Logic</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result_float</text>
<circle cx="348" cy="67.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">result_int</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">result_string</text>
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="146" font-size="11.5" fill="#9AA0A8">expression</text>
<text x="338" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">a + b</text>
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="175" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="175" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="175" font-size="11.5" fill="#9AA0A8">a <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.00</text>
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">b <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.00</text>
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="233" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="233" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="233" font-size="11.5" fill="#9AA0A8">c <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.00</text>
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="262" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="262" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="262" font-size="11.5" fill="#9AA0A8">d <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="262" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.00</text>
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="291" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="291" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="291" font-size="11.5" fill="#9AA0A8">round_to <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="291" font-size="11.5" fill="#E6E8EB" text-anchor="end">-1</text>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

+29
View File
@@ -0,0 +1,29 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="273" viewBox="0 0 360 273" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Multi-Slider (FLOAT) node">
<rect x="0.5" y="0.5" width="359" height="272" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Multi-Slider (FLOAT)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_MultiSliderFloat · Utils</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">Float 1</text>
<circle cx="348" cy="67.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">Float 2</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">Float 3</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">Float 4</text>
<circle cx="348" cy="133.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">Float 5</text>
<circle cx="348" cy="155.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="174" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="190" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="190" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="190" font-size="11.5" fill="#9AA0A8">float_1</text>
<text x="326" y="190" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.00</text>
<rect x="12" y="203" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="219" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="219" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="219" font-size="11.5" fill="#9AA0A8">float_2</text>
<text x="326" y="219" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.00</text>
<rect x="12" y="232" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="180.0" y="248" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">float_3 … float_5</text>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

+29
View File
@@ -0,0 +1,29 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="273" viewBox="0 0 360 273" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Multi-Slider (INT) node">
<rect x="0.5" y="0.5" width="359" height="272" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Multi-Slider (INT)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_MultiSliderInt · Utils</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">Int 1</text>
<circle cx="348" cy="67.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">Int 2</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">Int 3</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">Int 4</text>
<circle cx="348" cy="133.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">Int 5</text>
<circle cx="348" cy="155.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="174" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="190" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="190" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="190" font-size="11.5" fill="#9AA0A8">int_1</text>
<text x="326" y="190" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="203" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="219" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="219" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="219" font-size="11.5" fill="#9AA0A8">int_2</text>
<text x="326" y="219" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="232" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="180.0" y="248" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">int_3 … int_5</text>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

+17
View File
@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="171" viewBox="0 0 360 171" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Pack Universal (NH) node">
<rect x="0.5" y="0.5" width="359" height="170" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Pack Universal (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_PackUniversal · Utils/Pipe</text>
<circle cx="12" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">input_0 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">package</text>
<circle cx="348" cy="67.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">input_1 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
<circle cx="12" cy="111.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="115.0" font-size="12" fill="#D7DADF">input_2 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="180.0" y="146" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">input_3 … input_9</text>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

+70
View File
@@ -0,0 +1,70 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="519" viewBox="0 0 360 519" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Portrait Segment (NH) node">
<rect x="0.5" y="0.5" width="359" height="518" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Portrait Segment (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_PortraitSegment · Vision</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">mask</text>
<circle cx="348" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">cutout_rgba</text>
<circle cx="348" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">mask_image</text>
<circle cx="348" cy="111.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="146" font-size="11.5" fill="#9AA0A8">face</text>
<rect x="308" y="136" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="142.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="175" font-size="11.5" fill="#9AA0A8">neck</text>
<rect x="308" y="165" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="171.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="204" font-size="11.5" fill="#9AA0A8">hair</text>
<rect x="308" y="194" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="200.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="233" font-size="11.5" fill="#9AA0A8">eyes</text>
<rect x="308" y="223" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="229.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="262" font-size="11.5" fill="#9AA0A8">lips</text>
<rect x="308" y="252" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="258.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="291" font-size="11.5" fill="#9AA0A8">nose</text>
<rect x="308" y="281" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="287.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="304" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="320" font-size="11.5" fill="#9AA0A8">eyebrows</text>
<rect x="308" y="310" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="316.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="333" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="349" font-size="11.5" fill="#9AA0A8">ears</text>
<rect x="308" y="339" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="345.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="362" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="378" font-size="11.5" fill="#9AA0A8">mouth_interior</text>
<rect x="308" y="368" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="374.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="391" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="407" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="407" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="407" font-size="11.5" fill="#9AA0A8">mask_expand</text>
<text x="326" y="407" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="420" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="436" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="436" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="436" font-size="11.5" fill="#9AA0A8">mask_blur</text>
<text x="326" y="436" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="449" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="465" font-size="11.5" fill="#9AA0A8">fill_holes</text>
<rect x="308" y="455" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="461.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="478" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="494" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="494" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="494" font-size="11.5" fill="#9AA0A8">device</text>
<text x="326" y="494" font-size="11.5" fill="#E6E8EB" text-anchor="end">cuda</text>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

+24
View File
@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="207" viewBox="0 0 360 207" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Prompt Join (NH) node">
<rect x="0.5" y="0.5" width="359" height="206" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Prompt Join (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_PromptJoin · Text</text>
<circle cx="12" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">text_1 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">text_2 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">count</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="108" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="124" font-size="11.5" fill="#9AA0A8">separator</text>
<text x="338" y="124" font-size="11.5" fill="#E6E8EB" text-anchor="end">, </text>
<rect x="12" y="137" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="153" font-size="11.5" fill="#9AA0A8">skip_empty</text>
<rect x="308" y="143" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="149.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="166" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="180.0" y="182" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">text_3 … text_5</text>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

+36
View File
@@ -0,0 +1,36 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="309" viewBox="0 0 360 309" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Prompt Scheduler (NH) node">
<rect x="0.5" y="0.5" width="359" height="308" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Prompt Scheduler (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_PromptScheduler · Text</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">current_prompt</text>
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">progress</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">step_index</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="130" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="146" font-size="11.5" fill="#9AA0A8">prompts</text>
<text x="22" y="166" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">prompt 1 ⏎ prompt 2 ⏎ prompt 3</text>
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="197" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="197" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="197" font-size="11.5" fill="#9AA0A8">current_step</text>
<text x="326" y="197" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="210" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="226" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="226" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="226" font-size="11.5" fill="#9AA0A8">total_steps</text>
<text x="326" y="226" font-size="11.5" fill="#E6E8EB" text-anchor="end">10</text>
<rect x="12" y="239" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="255" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="255" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="255" font-size="11.5" fill="#9AA0A8">mode</text>
<text x="326" y="255" font-size="11.5" fill="#E6E8EB" text-anchor="end">sequential</text>
<rect x="12" y="268" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="284" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="284" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="284" font-size="11.5" fill="#9AA0A8">seed <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="284" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

+23
View File
@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="229" viewBox="0 0 360 229" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Prompt Template (NH) node">
<rect x="0.5" y="0.5" width="359" height="228" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Prompt Template (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_PromptTemplate · Text</text>
<circle cx="12" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">var1 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">var2 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">missing_vars</text>
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="108" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="124" font-size="11.5" fill="#9AA0A8">template</text>
<text x="22" y="144" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">a {color} {garment}, {style},…</text>
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="175" font-size="11.5" fill="#9AA0A8">var_names</text>
<text x="338" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">color,garment,style</text>
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="180.0" y="204" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">var3 … var6</text>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+29
View File
@@ -0,0 +1,29 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="229" viewBox="0 0 360 229" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Random Choice (NH) node">
<rect x="0.5" y="0.5" width="359" height="228" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Random Choice (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_RandomChoice · Logic</text>
<circle cx="12" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">input_0 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
<circle cx="348" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">input_1 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">picked_index</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="111.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="115.0" font-size="12" fill="#D7DADF">input_2 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">probabilities</text>
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">seed</text>
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="175" font-size="11.5" fill="#9AA0A8">weights</text>
<text x="338" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">1,1,1,1,1,1</text>
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="180.0" y="204" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">input_3 … input_5</text>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@@ -0,0 +1,53 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="426" viewBox="0 0 360 426" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Ratio Preset Image Resize node">
<rect x="0.5" y="0.5" width="359" height="425" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Ratio Preset Image Resize</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_RatioPresetImageResize · Resolution</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">width</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">height</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">info</text>
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">source_aspect_ratio</text>
<circle cx="348" cy="155.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="181.0" font-size="12" fill="#D7DADF" text-anchor="end">target_aspect_ratio</text>
<circle cx="348" cy="177.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="203.0" font-size="12" fill="#D7DADF" text-anchor="end">selected_preset</text>
<circle cx="348" cy="199.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="225.0" font-size="12" fill="#D7DADF" text-anchor="end">resize_data</text>
<circle cx="348" cy="221.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="240" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="256" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="256" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="256" font-size="11.5" fill="#9AA0A8">model_family</text>
<text x="326" y="256" font-size="11.5" fill="#E6E8EB" text-anchor="end">FLUX.2 Klein 9B</text>
<rect x="12" y="269" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="285" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="285" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="285" font-size="11.5" fill="#9AA0A8">resolution_level</text>
<text x="326" y="285" font-size="11.5" fill="#E6E8EB" text-anchor="end">1 MP</text>
<rect x="12" y="298" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="314" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="314" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="314" font-size="11.5" fill="#9AA0A8">target_ratio</text>
<text x="326" y="314" font-size="11.5" fill="#E6E8EB" text-anchor="end">1:1</text>
<rect x="12" y="327" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="343" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="343" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="343" font-size="11.5" fill="#9AA0A8">resize_mode</text>
<text x="326" y="343" font-size="11.5" fill="#E6E8EB" text-anchor="end">crop</text>
<rect x="12" y="356" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="372" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="372" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="372" font-size="11.5" fill="#9AA0A8">resize_method</text>
<text x="326" y="372" font-size="11.5" fill="#E6E8EB" text-anchor="end">lanczos</text>
<rect x="12" y="385" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="401" font-size="11.5" fill="#9AA0A8">pad_color_hex</text>
<text x="338" y="401" font-size="11.5" fill="#E6E8EB" text-anchor="end">#FFFFFF</text>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

+29
View File
@@ -0,0 +1,29 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="302" viewBox="0 0 360 302" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Regex Extract (NH) node">
<rect x="0.5" y="0.5" width="359" height="301" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Regex Extract (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_RegexExtract · Text</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">matched</text>
<circle cx="348" cy="89.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">groups</text>
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">count</text>
<circle cx="348" cy="133.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="152" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="168" font-size="11.5" fill="#9AA0A8">text</text>
<text x="22" y="188" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">…</text>
<rect x="12" y="203" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="219" font-size="11.5" fill="#9AA0A8">pattern</text>
<text x="338" y="219" font-size="11.5" fill="#E6E8EB" text-anchor="end">(\d+)</text>
<rect x="12" y="232" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="248" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="248" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="248" font-size="11.5" fill="#9AA0A8">mode</text>
<text x="326" y="248" font-size="11.5" fill="#E6E8EB" text-anchor="end">match</text>
<rect x="12" y="261" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="277" font-size="11.5" fill="#9AA0A8">replacement <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="338" y="277" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

@@ -0,0 +1,29 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="222" viewBox="0 0 360 222" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Remove Resize Padding node">
<rect x="0.5" y="0.5" width="359" height="221" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Remove Resize Padding</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_RemoveResizePadding · Resolution</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">resize_data</text>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">width</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">height</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">info</text>
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="168" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="168" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="168" font-size="11.5" fill="#9AA0A8">output_size</text>
<text x="326" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">content_region</text>
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="197" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="197" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="197" font-size="11.5" fill="#9AA0A8">resize_method</text>
<text x="326" y="197" font-size="11.5" fill="#E6E8EB" text-anchor="end">lanczos</text>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="171" viewBox="0 0 360 171" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Restore Image Geometry node">
<rect x="0.5" y="0.5" width="359" height="170" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Restore Image Geometry</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_RestoreImageGeometry · Resolution</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">original_image</text>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">mask</text>
<circle cx="348" cy="89.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="111.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="115.0" font-size="12" fill="#D7DADF">resize_data</text>
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">resize_method</text>
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">bicubic</text>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+46
View File
@@ -0,0 +1,46 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="374" viewBox="0 0 360 374" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Save Image Path (NH) node">
<rect x="0.5" y="0.5" width="359" height="373" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#F6AD55"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Save Image Path (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_SaveImagePath · Image</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">images</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">filenames</text>
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">path</text>
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">saved_count</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="146" font-size="11.5" fill="#9AA0A8">folder_path</text>
<text x="338" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="175" font-size="11.5" fill="#9AA0A8">create_new_folder</text>
<rect x="308" y="165" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="171.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="204" font-size="11.5" fill="#9AA0A8">new_folder_name</text>
<text x="338" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="233" font-size="11.5" fill="#9AA0A8">filename_prefix</text>
<text x="338" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">NH</text>
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="262" font-size="11.5" fill="#9AA0A8">number_suffix</text>
<rect x="308" y="252" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="258.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="291" font-size="11.5" fill="#9AA0A8">preview_image</text>
<rect x="308" y="281" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="287.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="304" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="320" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="320" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="320" font-size="11.5" fill="#9AA0A8">file_format</text>
<text x="326" y="320" font-size="11.5" fill="#E6E8EB" text-anchor="end">png</text>
<rect x="12" y="333" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="349" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="349" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="349" font-size="11.5" fill="#9AA0A8">dpi</text>
<text x="326" y="349" font-size="11.5" fill="#E6E8EB" text-anchor="end">300</text>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

+32
View File
@@ -0,0 +1,32 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="258" viewBox="0 0 360 258" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Save Large Image node">
<rect x="0.5" y="0.5" width="359" height="257" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#F6AD55"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Save Large Image</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_SaveLargeImage · Image</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">file_path</text>
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">saved_count</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="146" font-size="11.5" fill="#9AA0A8">filename_prefix</text>
<text x="338" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">NH_Large</text>
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="175" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="175" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="175" font-size="11.5" fill="#9AA0A8">file_format</text>
<text x="326" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">png</text>
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">quality</text>
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">95</text>
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="233" font-size="11.5" fill="#9AA0A8">embed_metadata</text>
<rect x="308" y="223" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="229.5" r="5" fill="#F5F6F7"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

+22
View File
@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="193" viewBox="0 0 360 193" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Simple Face Paste node">
<rect x="0.5" y="0.5" width="359" height="192" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Simple Face Paste</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_SimpleFacePaste · Image</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">dest_image</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">IMAGE</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">dest_mask</text>
<circle cx="12" cy="111.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="115.0" font-size="12" fill="#D7DADF">source_image</text>
<circle cx="12" cy="133.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="137.0" font-size="12" fill="#D7DADF">source_mask</text>
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="168" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="168" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="168" font-size="11.5" fill="#9AA0A8">feathering</text>
<text x="326" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">20</text>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,48 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="397" viewBox="0 0 360 397" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Smart Ratio Image Resize node">
<rect x="0.5" y="0.5" width="359" height="396" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Smart Ratio Image Resize</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_SmartRatioImageResize · Resolution</text>
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">width</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">height</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">info</text>
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">source_aspect_ratio</text>
<circle cx="348" cy="155.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="181.0" font-size="12" fill="#D7DADF" text-anchor="end">target_aspect_ratio</text>
<circle cx="348" cy="177.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="203.0" font-size="12" fill="#D7DADF" text-anchor="end">selected_preset</text>
<circle cx="348" cy="199.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="225.0" font-size="12" fill="#D7DADF" text-anchor="end">resize_data</text>
<circle cx="348" cy="221.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="240" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="256" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="256" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="256" font-size="11.5" fill="#9AA0A8">model_family</text>
<text x="326" y="256" font-size="11.5" fill="#E6E8EB" text-anchor="end">FLUX.2 Klein 9B</text>
<rect x="12" y="269" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="285" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="285" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="285" font-size="11.5" fill="#9AA0A8">resolution_level</text>
<text x="326" y="285" font-size="11.5" fill="#E6E8EB" text-anchor="end">1 MP</text>
<rect x="12" y="298" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="314" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="314" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="314" font-size="11.5" fill="#9AA0A8">resize_mode</text>
<text x="326" y="314" font-size="11.5" fill="#E6E8EB" text-anchor="end">cover_crop</text>
<rect x="12" y="327" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="343" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="343" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="343" font-size="11.5" fill="#9AA0A8">resize_method</text>
<text x="326" y="343" font-size="11.5" fill="#E6E8EB" text-anchor="end">lanczos</text>
<rect x="12" y="356" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="372" font-size="11.5" fill="#9AA0A8">pad_color_hex</text>
<text x="338" y="372" font-size="11.5" fill="#E6E8EB" text-anchor="end">#FFFFFF</text>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

@@ -0,0 +1,50 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="389" viewBox="0 0 360 389" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Smart Resolution Picker node">
<rect x="0.5" y="0.5" width="359" height="388" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Smart Resolution Picker</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_SmartResolutionPicker · Resolution</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">width</text>
<circle cx="348" cy="67.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">height</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">latent</text>
<circle cx="348" cy="111.0" r="5" fill="#FF9CF9" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">info</text>
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">aspect_ratio</text>
<circle cx="348" cy="155.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="174" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="190" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="190" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="190" font-size="11.5" fill="#9AA0A8">model_family</text>
<text x="326" y="190" font-size="11.5" fill="#E6E8EB" text-anchor="end">FLUX.2 Klein 9B</text>
<rect x="12" y="203" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="219" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="219" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="219" font-size="11.5" fill="#9AA0A8">preset</text>
<text x="326" y="219" font-size="11.5" fill="#E6E8EB" text-anchor="end">2 MP (sweet spot) ⭐ | 1…</text>
<rect x="12" y="232" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="248" font-size="11.5" fill="#9AA0A8">use_custom <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<rect x="308" y="238" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="244.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="261" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="277" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="277" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="277" font-size="11.5" fill="#9AA0A8">custom_width <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="277" font-size="11.5" fill="#E6E8EB" text-anchor="end">1024</text>
<rect x="12" y="290" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="306" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="306" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="306" font-size="11.5" fill="#9AA0A8">custom_height <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="306" font-size="11.5" fill="#E6E8EB" text-anchor="end">1024</text>
<rect x="12" y="319" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="335" font-size="11.5" fill="#9AA0A8">swap_orientation <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<rect x="308" y="325" width="30" height="13" rx="6.5" fill="#4a4d53"/>
<circle cx="315" cy="331.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="348" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="364" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="364" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="364" font-size="11.5" fill="#9AA0A8">batch_size <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="364" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

+27
View File
@@ -0,0 +1,27 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="280" viewBox="0 0 360 280" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="String Operations (NH) node">
<rect x="0.5" y="0.5" width="359" height="279" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">String Operations (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_StringOps · Text</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">text_out</text>
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">bool_out</text>
<circle cx="348" cy="89.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">int_out</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="130" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="146" font-size="11.5" fill="#9AA0A8">text</text>
<text x="22" y="166" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">…</text>
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="197" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="197" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="197" font-size="11.5" fill="#9AA0A8">operation</text>
<text x="326" y="197" font-size="11.5" fill="#E6E8EB" text-anchor="end">upper</text>
<rect x="12" y="210" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="226" font-size="11.5" fill="#9AA0A8">param_a <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="338" y="226" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
<rect x="12" y="239" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="255" font-size="11.5" fill="#9AA0A8">param_b <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="338" y="255" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

+24
View File
@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="200" viewBox="0 0 360 200" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Switch N (NH) node">
<rect x="0.5" y="0.5" width="359" height="199" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Switch N (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_SwitchN · Logic</text>
<circle cx="12" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="71.0" font-size="12" fill="#D7DADF">input_0 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
<circle cx="348" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="89.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="93.0" font-size="12" fill="#D7DADF">input_1 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">count</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<circle cx="12" cy="111.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="24" y="115.0" font-size="12" fill="#D7DADF">input_2 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">index</text>
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="180.0" y="175" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">input_3 … input_9</text>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+29
View File
@@ -0,0 +1,29 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="272" viewBox="0 0 360 272" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Text Concatenate (NH) node">
<rect x="0.5" y="0.5" width="359" height="271" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Text Concatenate (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_TextConcatenate · Text</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">text</text>
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="86" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="102" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="102" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="102" font-size="11.5" fill="#9AA0A8">string_count</text>
<text x="326" y="102" font-size="11.5" fill="#E6E8EB" text-anchor="end">2</text>
<rect x="12" y="115" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="131" font-size="11.5" fill="#9AA0A8">delimiter</text>
<text x="338" y="131" font-size="11.5" fill="#E6E8EB" text-anchor="end">/</text>
<rect x="12" y="144" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="160" font-size="11.5" fill="#9AA0A8">clean_whitespace</text>
<rect x="308" y="150" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="156.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="173" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="189" font-size="11.5" fill="#9AA0A8">string_1 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="338" y="189" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
<rect x="12" y="202" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="218" font-size="11.5" fill="#9AA0A8">string_2 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="338" y="218" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
<rect x="12" y="231" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="180.0" y="247" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">string_3 … string_64</text>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

+27
View File
@@ -0,0 +1,27 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="287" viewBox="0 0 360 287" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Text Index (NH) node">
<rect x="0.5" y="0.5" width="359" height="286" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Text Index (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_TextIndex · Text</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">text</text>
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="86" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="102" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="102" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="102" font-size="11.5" fill="#9AA0A8">index</text>
<text x="326" y="102" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
<rect x="12" y="115" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="131" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="131" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="131" font-size="11.5" fill="#9AA0A8">text_count</text>
<text x="326" y="131" font-size="11.5" fill="#E6E8EB" text-anchor="end">5</text>
<rect x="12" y="144" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="160" font-size="11.5" fill="#9AA0A8">text_1 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="22" y="180" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">…</text>
<rect x="12" y="195" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="211" font-size="11.5" fill="#9AA0A8">text_2 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="22" y="231" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">…</text>
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="180.0" y="262" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">text_3 … text_64</text>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

+21
View File
@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="222" viewBox="0 0 360 222" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Text Random Line (NH) node">
<rect x="0.5" y="0.5" width="359" height="221" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Text Random Line (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_TextRandomLine · Text</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">text</text>
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">picked_index</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">total_lines</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="130" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="146" font-size="11.5" fill="#9AA0A8">text</text>
<text x="22" y="166" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">option A ⏎ option B ⏎ option C</text>
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="197" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="197" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="197" font-size="11.5" fill="#9AA0A8">seed</text>
<text x="326" y="197" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+26
View File
@@ -0,0 +1,26 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="273" viewBox="0 0 360 273" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Text Split (NH) node">
<rect x="0.5" y="0.5" width="359" height="272" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Text Split (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_TextSplit · Text</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">items_joined</text>
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">count</text>
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">first</text>
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">last</text>
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="152" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="168" font-size="11.5" fill="#9AA0A8">text</text>
<text x="22" y="188" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">…</text>
<rect x="12" y="203" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="219" font-size="11.5" fill="#9AA0A8">delimiter</text>
<text x="338" y="219" font-size="11.5" fill="#E6E8EB" text-anchor="end">, </text>
<rect x="12" y="232" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="248" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="248" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="248" font-size="11.5" fill="#9AA0A8">max_splits <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="326" y="248" font-size="11.5" fill="#E6E8EB" text-anchor="end">-1</text>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

+52
View File
@@ -0,0 +1,52 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="462" viewBox="0 0 360 462" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Text Split Lines (NH) node">
<rect x="0.5" y="0.5" width="359" height="461" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Text Split Lines (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_TextSplitLines · Text</text>
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">lines ≡</text>
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">selected</text>
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">count</text>
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">preview</text>
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">is_first</text>
<circle cx="348" cy="155.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
<text x="336" y="181.0" font-size="12" fill="#D7DADF" text-anchor="end">is_last</text>
<circle cx="348" cy="177.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
<rect x="12" y="196" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="212" font-size="11.5" fill="#9AA0A8">text</text>
<text x="22" y="232" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">first prompt ⏎ second prompt …</text>
<rect x="12" y="247" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="263" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="263" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="263" font-size="11.5" fill="#9AA0A8">index</text>
<text x="326" y="263" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="276" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="292" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="292" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="292" font-size="11.5" fill="#9AA0A8">mode</text>
<text x="326" y="292" font-size="11.5" fill="#E6E8EB" text-anchor="end">sequential</text>
<rect x="12" y="305" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="20" y="321" font-size="10" fill="#6f747c">◀</text>
<text x="332" y="321" font-size="10" fill="#6f747c">▶</text>
<text x="34" y="321" font-size="11.5" fill="#9AA0A8">seed</text>
<text x="326" y="321" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
<rect x="12" y="334" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="350" font-size="11.5" fill="#9AA0A8">delimiter <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<text x="338" y="350" font-size="11.5" fill="#E6E8EB" text-anchor="end">\n</text>
<rect x="12" y="363" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="379" font-size="11.5" fill="#9AA0A8">skip_empty <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<rect x="308" y="369" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="375.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="392" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="408" font-size="11.5" fill="#9AA0A8">strip_whitespace <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<rect x="308" y="398" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="404.5" r="5" fill="#F5F6F7"/>
<rect x="12" y="421" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="437" font-size="11.5" fill="#9AA0A8">wrap_index <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
<rect x="308" y="427" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
<circle cx="331" cy="433.5" r="5" fill="#F5F6F7"/>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="119" viewBox="0 0 360 119" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Universal Slider Builder (NH) node">
<rect x="0.5" y="0.5" width="359" height="118" rx="10" fill="#26282c" stroke="#45484f"/>
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
<circle cx="17" cy="17" r="4.5" fill="#F6AD55"/>
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Universal Slider Builder (NH)</text>
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_UniversalSliderBuilder · Utils</text>
<rect x="12" y="56" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
<text x="22" y="72" font-size="11.5" fill="#9AA0A8">config</text>
<text x="22" y="92" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace"># Syntax: name, type, default…</text>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Some files were not shown because too many files have changed in this diff Show More