From 7ff4b9f331e9030bd64255407a34f146f9813fc8 Mon Sep 17 00:00:00 2001 From: leafiy Date: Tue, 11 Nov 2025 10:04:59 +0800 Subject: [PATCH] update --- README.md | 20 ++++++------- psd_mockup_node.py | 60 ++++++++++++++++--------------------- web/js/psd_mockup_upload.js | 2 +- 3 files changed, 35 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 13830c1..6c9fbf6 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/psd_mockup_node.py b/psd_mockup_node.py index 1fc12be..6fc69f4 100644 --- a/psd_mockup_node.py +++ b/psd_mockup_node.py @@ -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", } diff --git a/web/js/psd_mockup_upload.js b/web/js/psd_mockup_upload.js index f8aa6e3..02ea77b 100644 --- a/web/js/psd_mockup_upload.js +++ b/web/js/psd_mockup_upload.js @@ -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) {