This commit is contained in:
leafiy
2025-11-11 10:04:59 +08:00
parent 80fac52a2e
commit 7ff4b9f331
3 changed files with 35 additions and 47 deletions
+9 -11
View File
@@ -11,12 +11,10 @@ matches what you would get when replacing the layer inside Photoshop.
- **PSD Smart Object Inspector** – Lists each smart-object layer (name, id,
bounding box, transform box). Use it to confirm the layer names you want to
target.
- **PSD Mockup Embedder** – Replaces one or more smart objects with the input
image. The node renders the PSD without the target layers, warps the image
using OpenCV, and outputs a ready-to-use RGBA tensor plus an auto-saved PNG.
- **PSD File Upload** – Includes the same “choose PSD to upload” button used in
VideoHelperSuite, powered by the bundled frontend script. Click it (or drag
files over the node) to push PSD/PSB files directly into `ComfyUI/input`.
- **PSD Mockup Embedder** – Uploads PSD/PSB files directly from the node,
replaces the selected smart objects with your image, resizes the result to any
width (default 800 px), and emits the composited tensor plus an auto-saved
PNG.
- Works with PSD/PSB files, multiple smart objects, and either ComfyUI `IMAGE`
tensors or file paths.
@@ -31,15 +29,15 @@ matches what you would get when replacing the layer inside Photoshop.
## Usage
1. Place PSD/PSB mockups under `ComfyUI/input/psd` (or pass an absolute path).
2. Use **PSD File Upload** and click the built-in “choose PSD to upload” button.
Connect its output directly into the `psd_path` input of **PSD Mockup
Embedder** (or Inspector). The frontend script uploads to `ComfyUI/input`
and automatically feeds the resulting path through the link.
2. On **PSD Mockup Embedder**, click “choose PSD to upload” (or drag a PSD onto
the node). The bundled frontend script uploads the file into `ComfyUI/input`
and fills the `psd_file` field automatically.
3. Load your artwork with any stock `Load Image` (or `Upload Image`) node and
connect its `IMAGE` output to the embedder. Leave `smart_object_names`
empty to target the first smart object automatically, or pass a
comma-separated list to specify others.
4. Trigger the workflow. The node outputs:
4. Adjust `output_width` if you need a specific export size (set to `0` to keep
the PSD’s native resolution), then trigger the workflow. The node outputs:
- `mockup_image`: an `IMAGE` tensor that you can preview or post-process.
- `debug_info`: JSON describing the layers that were replaced.
- `png_path`: the auto-saved PNG under `ComfyUI/output`, so you no longer
+25 -35
View File
@@ -251,34 +251,6 @@ def _save_png(image: Image.Image, psd_path: str) -> str:
return out_path
class PSDFileUploader:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"psd_file": (
"STRING",
{
"default": "",
"multiline": False,
"placeholder": "Upload or paste a PSD path",
"tooltip": "Use the upload button to select a PSD/PSB or paste a path manually",
},
)
}
}
CATEGORY = "psd/mockup"
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("psd_path",)
FUNCTION = "upload"
def upload(self, psd_file: str):
"""Return the resolved path inside ComfyUI/input for the chosen PSD."""
path = _resolve_input_path(psd_file, ALLOWED_PSD_EXTENSIONS)
return (path,)
class PSDSmartObjectInspector:
@classmethod
def INPUT_TYPES(cls):
@@ -310,18 +282,29 @@ class PSDMockupEmbedder:
def INPUT_TYPES(cls):
return {
"required": {
"psd_path": (
"psd_file": (
"STRING",
{
"default": "",
"forceInput": True,
"tooltip": "Connect PSD File Upload or provide an absolute path",
"multiline": False,
"placeholder": "Upload or paste PSD/PSB path",
"tooltip": "Use the built-in upload button or paste a path manually",
},
),
"mockup_image": ("IMAGE",),
},
"optional": {
"smart_object_names": ("STRING", {"default": "", "multiline": False}),
"output_width": (
"INT",
{
"default": 800,
"min": 0,
"max": 8192,
"step": 1,
"tooltip": "Resize final image to this width (0 keeps original size)",
},
),
},
}
@@ -332,11 +315,12 @@ class PSDMockupEmbedder:
def apply(
self,
psd_path: str,
psd_file: str,
mockup_image,
smart_object_names: str = "",
output_width: int = 800,
):
resolved_psd = _resolve_input_path(psd_path, ALLOWED_PSD_EXTENSIONS)
resolved_psd = _resolve_input_path(psd_file, ALLOWED_PSD_EXTENSIONS)
psd = PSDImage.open(resolved_psd, strict=False)
source_image = _tensor_to_pil(mockup_image)
if source_image is None:
@@ -374,12 +358,20 @@ class PSDMockupEmbedder:
}
)
if isinstance(output_width, (int, float)) and output_width and output_width > 0:
target_w = int(output_width)
if target_w > 0 and target_w != base_rgba.width:
ratio = target_w / base_rgba.width
target_h = max(1, int(round(base_rgba.height * ratio)))
base_rgba = base_rgba.resize((target_w, target_h), Image.LANCZOS)
result_tensor = _pil_to_tensor(base_rgba)
saved_path = _save_png(base_rgba, resolved_psd)
debug_payload = {
"psd": os.path.basename(resolved_psd),
"layer_count": len(selected),
"layers": metadata,
"output_size": {"width": base_rgba.width, "height": base_rgba.height},
}
if fallback_used:
debug_payload["note"] = "Requested smart object names were not found; falling back to the first smart object layer."
@@ -387,13 +379,11 @@ class PSDMockupEmbedder:
NODE_CLASS_MAPPINGS = {
"PSDFileUploader": PSDFileUploader,
"PSDSmartObjectInspector": PSDSmartObjectInspector,
"PSDMockupEmbedder": PSDMockupEmbedder,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"PSDFileUploader": "PSD File Upload",
"PSDSmartObjectInspector": "PSD → Smart Object Info",
"PSDMockupEmbedder": "PSD Mockup Embedder",
}
+1 -1
View File
@@ -1,7 +1,7 @@
import { app } from "../../../scripts/app.js";
import { api } from "../../../scripts/api.js";
const PSD_NODE_CLASSES = ["PSDFileUploader", "PSDSmartObjectInspector"];
const PSD_NODE_CLASSES = ["PSDMockupEmbedder", "PSDSmartObjectInspector"];
const ACCEPT_EXTENSIONS = [".psd", ".psb"];
function uploadPSDFile(file, node, onProgress) {