Language / 言語 / 语言: English | 日本語 | 中文
A ComfyUI custom node for loading PSD files, rigging layers with interactive control points,
and compositing the result as IMAGE + MASK outputs.
- Interactive layer viewer — toggle visibility, rename layers and custom groups
- Custom groups — bundle layers into named groups; drag to reorder draw order
- Rigging system — place control points on layers directly on the canvas:
- R (blue) — rotation only
- MR (red/orange) — move + rotate
- LSW (green) — layer switch: rotate a handle to step through up to 12 slots; use +L to add an individual layer (1 slot), +P to add a group/folder expanded per-layer (Piece, N slots), or +C to add a group/folder composited as one (Composite, 1 slot)
- PSW (white) — pose switch: rotate a handle to apply registered poses; register R/MR pose states to up to 12 slots in 30° increments
- Setup mode / Pose mode — configure rigs in setup mode, animate in pose mode
- Keyframe animation — record poses at specific frames, interpolate between keyframes (linear lerp for position, shortest-path for angles), preview playback at configurable FPS, and export as WebM video (Chrome/Edge) or transparent animated GIF. Save/load full animation projects to the library.
- Library — save/load named model files (
.psd-model.json), pose files, and keyframe animation projects - Background options — checker pattern / solid color / local image / upstream
IMAGEnode - Capture → Queue Prompt — bake the current canvas state to an output image
- i18n — UI language auto-detected from
navigator.language(Japanese / English / Simplified Chinese)
# 1. Copy or symlink this folder into ComfyUI's custom_nodes directory
# e.g. ComfyUI/custom_nodes/psd-image-loader/
# 2. Install the Python dependency
pip install psd-toolsRestart ComfyUI. The node appears under image/psd → PSD Figure Creator.
A ready-to-use sample is bundled in the user_data/ directory:
| File | Description |
|---|---|
user_data/sample_1.psd |
Sample character PSD |
user_data/models/sample.psd-model.json |
Pre-configured rig (R/MR points, parent hierarchy, SW switch) |
user_data/models/sample2_lswpswset-model.psd-model.json |
Pre-configured rig with LSW and PSW points |
user_data/poses/pose1.pose.json |
Sample pose 1 |
user_data/poses/pose2.pose.json |
Sample pose 2 |
user_data/poses/project-psw.pose.json |
Sample keyframe animation project using PSW |
To use the sample:
- Copy
user_data/sample_1.psdtoComfyUI/input/psd/ - Open the Editor and click 📂 model —
samplewill appear in the library - Load the model; sample poses are available in the pose library
Upgrading from PSD Loader (≤ v2.16):
If your workflow JSON contains"PSDLoader", replace it with"PSDFigureCreator".
Two things must be transferred separately when setting up on a new machine.
PSD files live in ComfyUI's input/psd/ directory.
ComfyUI/input/psd/ → copy to the same path on the new PC
Library data is stored in user_data/ inside the custom node folder.
ComfyUI/custom_nodes/PSD-Figure-Creator/user_data/ → copy to the same path on the new PC
Bringing a workflow JSON: If a workflow has
psd_filenameset but the PSD file is missing on the new PC, you'll see a "Failed to fetch layer info" alert. Just press Setup again — the modal will open with empty layers (v0.5.2+), and you can use the Open PSD button to re-select the file.
| Parameter | Type | Description |
|---|---|---|
psd_filename |
STRING | PSD file path relative to the input/psd/ directory |
layer_config |
STRING | JSON string produced by the UI editor |
output_width |
INT | Output width in pixels, adjustable in 1 px steps (0 = native PSD size) |
output_height |
INT | Output height in pixels, adjustable in 1 px steps (0 = native PSD size) |
image_data |
STRING | Base64 PNG from Capture (bypasses server-side compositing) |
background_image |
IMAGE | Optional upstream image composited as the bottom layer |
| Output | Type | Description |
|---|---|---|
image |
IMAGE | Composited RGB image |
mask |
MASK | Alpha channel |
[✨ New] [📂 PSD file] [⟳]
[Editor] [⏱] [RC]
[📸 Capture]
┌──────────────────────────────────┐ ← keyframe panel (⏱ to toggle)
│ [+KF][🗑KF]|[+CK][-CK]|[↔]|[0][◀][f]/[t][▶] │
│ ◆────◆──────── timeline ────────── │
│ [New] FPS[24] [💾Proj] [🎬WebM][🎞️GIF] [▶▶] [■] │
└──────────────────────────────────┘
┌────────────────────────┐
│ Preview canvas │
└────────────────────────┘
Point Size: ─────────────
BG: [■ color][✕] [🖼 Image][✕] [🔗 Connected?]
- Editor — opens the full-screen setup/pose modal
- ⏱ — toggle the keyframe animation panel
- RC — reset camera (pan + zoom)
- ✨ New — clears all rigging, SW layers, and poses (prompts for confirmation)
| Tab | Contents |
|---|---|
| Layers | Layer tree, custom group management, rig mode buttons (R / MR / LSW / PSW) |
| Parent | Parent–child hierarchy for propagated transforms |
| LSwitch | LSW layer list and group-slot editor |
| PSwitch | PSW point list, slot management, and pose registration |
Blue dot. Drag in pose mode to rotate the layer around the placed pivot.
Red origin + orange handle. Drag the handle to move and rotate simultaneously.
Green origin + cyan handle. Rotating the handle steps through registered slots in 30° increments (maximum 12 slots × 30° = 360°).
Drag the origin in setup mode to reposition; drag the handle to adjust radius and initial angle.
Slot entry types (configured in the LSwitch tab):
| Button | Entry | Badge | Slots |
|---|---|---|---|
+L |
Individual PSD layer | [L] |
1 slot |
+P |
Custom group or PSD folder (Piece) | [P] |
1 slot per member / leaf layer |
+C |
Custom group or PSD folder (Composite) | [C] |
1 slot (all members rendered together) |
A slot entry whose group or folder has been deleted shows a red row background and a ⚠ icon (orphaned). Delete it manually before adding new entries.
White origin + purple handle. Rotating the handle applies the registered pose for the active slot.
Usage (configured in the PSwitch tab):
- In Setup mode, click the PSW button (a PSW layer is created automatically on first use), then click the canvas to place a point
- Use
+Slotto add slots in 30° increments (up to 12 slots);−Slotremoves the last slot - Select the slot 0 (0°) row, then in Pose mode use
+MLPto register all layer poses, or select a layer in the layer tree and use+SLPto register a single layer's pose;−LPclears the slot - For slots other than 0°: click the slot row → click
✏ Editto load the pose → adjust → click✓ Confirmto save - The handle angle determines the active slot: 0°–30° → slot 0, 30°–60° → slot 1, and so on; the movement range is locked to (slot count − 1) × 30°
Multiple PSW points operate independently and their poses are composited together.
The PSW toggle button (left of the Capture button on the node) enables or disables PSW globally:
| State | Color | Effect |
|---|---|---|
| ON (default) | Blue | PSW preset poses are applied based on handle angle |
| OFF | Red | PSW disabled — all layers (including PSW-registered ones) are freely controlled by R/MR |
The PSW ON/OFF state is saved and restored across all operations:
- Keyframes — saved per frame; toggle switches automatically during playback
- Pose save (📷 Pose / right-click Pose+SW) — toggle state is restored on load
- Model save/load — toggle state is included in the model file
- Project save (ComfyUI workflow) — persisted in
layer_config
Toggle the keyframe panel with the ⏱ button on the node.
Row A
| Button / Field | Action |
|---|---|
+KF |
Record the current pose (visibility, position, angles) at the current frame |
🗑KF |
Delete the pose keyframe at the current frame (camera data preserved) |
+CK |
Record a camera keyframe (zoom / x / y / roll) at the current frame |
-CK |
Delete the camera keyframe at the current frame (pose data preserved) |
↔ |
Toggle key-move mode: when ON, drag keyframe diamonds on the timeline to move them; playhead scrubbing is disabled |
0 |
Jump to frame 0 |
◀ / ▶ |
Step one frame back / forward |
| Frame input | Jump to a specific frame |
| Total input | Set total frame count |
Row B
| Button / Field | Action |
|---|---|
New |
Clear all keyframes and reset to frame 0 (confirm required) |
FPS |
Playback and export frame rate (default 24) |
💾 Proj |
Save the animation project to the library (name: project-YYYYMMDDHHMMSS) |
🎬 WebM |
Export as a WebM video file (Chrome/Edge recommended) |
🎞️ GIF |
Export as a transparent animated GIF image |
▶ / ■ |
Start / stop playback preview (▶ is double-width) |
Click or drag the timeline canvas to scrub to any frame. Recorded keyframes appear as ◆ markers.
| Property | Method |
|---|---|
| Position (tx / ty) | Linear lerp |
| Rotation angle | Shortest-path angle lerp (handles 0 ↔ 360° wrap) |
| SW handle angle | Shortest-path angle lerp |
| PSW handle angle | Shortest-path angle lerp |
| Visibility | Step: value of the previous keyframe |
💾 Proj saves keyframe data (keyframes, kf_total_frames, kf_fps) to the library's Poses panel as _type: "kf_project". Loading it from the library restores the full timeline and applies frame 0's pose to the canvas.
Keyframes are also persisted in layer_config.keyframes, so they are saved and restored with the ComfyUI workflow JSON automatically.
Layers with the Photoshop "clip to layer below" flag appear with a ✂ badge in the layer panel and SW +L dropdown. The canvas compositor renders them using source-atop blending: each clipping layer is masked to the opaque area of its base layer (the layer directly below it). R/MR rigs placed on a clipping layer work normally within that masked region. Clipping applies everywhere: at the PSD document root, inside folders, and inside custom groups.
⚠ Parent setup note: If the base layer has a rig that moves it, the clipping layer must share the same parent (configured in the Parent tab) to follow along. Without a matching parent, the clipping layer stays at its original canvas position while the base moves, breaking the mask alignment.
Highest to lowest:
- ComfyUI
background_imageinput — server-side composite (letterbox, aspect-ratio preserved) - Local background image — loaded via the
🖼 Imagebutton, rendered client-side - Background color — solid fill selected with the color picker
- Checker pattern — default transparent background indicator
psd-image-loader/
├── __init__.py # Node registration
├── psd_loader_node.py # PSDFigureCreatorNode
├── psd_utils.py # psd-tools compositing helpers
├── server.py # aiohttp API routes (upload / layers / preview / library)
├── requirements.txt
└── web/
├── js/
│ ├── psd_loader.js # Front-end (canvas, modal, rigging)
│ └── i18n.js # Translation dictionaries + t() helper
└── css/
└── psd_loader.css
- ComfyUI (latest)
- Python 3.10+
- psd-tools ≥ 1.9.0
[INFO] Unknown image resource 1092
[INFO] Unknown tagged block: <Tag.CAI: b'CAI '>, ...
These are informational messages from the psd-tools library (not errors). They appear when a PSD file contains metadata that psd-tools does not yet recognize — for example, resources added by recent versions of Photoshop such as Generative Fill (CAI tag). The file is still read and composited correctly; the unknown data is simply skipped. No action is required.
MIT










{ "visibility": { "<layerId>": true | false }, "renamed": { "<layerId>": "display name" }, "custom_groups": [{ "name": "...", "layer_ids": [...], "visible": true }], "layer_order": [{ "id": "...", "children": [...] }], "rigging": { "<layerId>": { "r": { "x": 0, "y": 0 }, "mr": { "x": 0, "y": 0 }, "mr_radius": 40 } }, "pose": { "<layerId>": { "angle": 0, "tx": 0, "ty": 0 } }, "sw_layers": [{ "id": "...", "name": "sw1", "points": [{ "id": "...", "name": "pt1", "x": 512, "y": 512, "radius": 60, "angle": 0, "groups": [ "<layerId>", // +L — individual layer, 1 slot { "type": "custom_group", "id": "...", "mode": "piece" }, // +P — 1 slot per member layer { "type": "psd_group", "id": "...", "mode": "composite" } // +C — 1 slot (composited) // mode defaults to "piece" when omitted (backward compatible) ] }] }], "psw_layers": [{ "id": "...", "name": "PSW1", "points": [{ "id": "...", "name": "PSW1", "x": 512, "y": 512, "radius": 80, "angle": 0, "slots": [ { "degree": 0, "pose": null }, // empty slot { "degree": 30, "pose": { "<layerId>": { "angle": 0.5, "tx": 10, "ty": -5 } } } ] }] }], "keyframes": [ { "frame": 0, "visibility": { "<layerId>": true }, "pose": { "<layerId>": { "angle": 0, "tx": 0, "ty": 0 } }, "sw_angles": { "<pointId>": 0 }, "psw_angles": { "<pointId>": 0 } } ], "kf_total_frames": 60, "kf_fps": 24 }