diff --git a/README.md b/README.md index 83fc677..2431786 100644 --- a/README.md +++ b/README.md @@ -29,12 +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. Run **PSD Smart Object Inspector** to list available smart objects. Copy - the names you want (comma-separated). + the names you want (comma-separated). If you leave `smart_object_names` + empty, the embedder automatically targets the first smart object it finds. 3. Provide an `IMAGE` input from a standard loader node or fill the `mockup_image_path` with a filename that also lives in `ComfyUI/input`. 4. Feed everything into **PSD Mockup Embedder**. The node outputs: - - `mockup_image`: an `IMAGE` tensor that you can preview or save in ComfyUI. + - `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 + need an extra `Save Image` node to receive a PNG file. ### Notes & Limitations - The result is flattened, so advanced Photoshop blend modes (e.g., highlight diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..513d9b6 --- /dev/null +++ b/__init__.py @@ -0,0 +1,3 @@ +from .psd_mockup_node import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS + +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] diff --git a/psd_mockup_node.py b/psd_mockup_node.py index 8880279..719a894 100644 --- a/psd_mockup_node.py +++ b/psd_mockup_node.py @@ -12,7 +12,9 @@ import json import math import os from dataclasses import dataclass -from typing import Callable, Iterable, List, Optional, Sequence, Tuple +from datetime import datetime +from pathlib import Path +from typing import Iterable, List, Optional, Sequence, Tuple import numpy as np from PIL import Image, ImageDraw @@ -32,6 +34,7 @@ except Exception: # pragma: no cover - allows running outside ComfyUI ALLOWED_PSD_EXTENSIONS = (".psd", ".psb") QUAD_INDICES = ((0, 1), (2, 3), (4, 5), (6, 7)) +PNG_SUFFIX = ".png" @dataclass @@ -150,8 +153,10 @@ def _normalize_name(name: str) -> str: def _select_layers(layers: List[SmartLayerInfo], names: Sequence[str]) -> List[SmartLayerInfo]: - if not names: - return layers + if not names or len(names) == 0: + if not layers: + return [] + return [layers[0]] lookup = {_normalize_name(name) for name in names if name.strip()} if not lookup: return layers @@ -216,6 +221,24 @@ def _polygon_mask(quad: Sequence[float], canvas_size: Tuple[int, int]) -> Image. return mask +def _output_directory() -> str: + if folder_paths and hasattr(folder_paths, "get_output_directory"): + return folder_paths.get_output_directory() + default_dir = os.path.join(os.getcwd(), "output") + os.makedirs(default_dir, exist_ok=True) + return default_dir + + +def _save_png(image: Image.Image, psd_path: str) -> str: + base_name = Path(psd_path).stem or "psd_mockup" + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"{base_name}_{timestamp}{PNG_SUFFIX}" + out_dir = _output_directory() + out_path = os.path.join(out_dir, filename) + image.save(out_path, "PNG") + return out_path + + class PSDSmartObjectInspector: @classmethod def INPUT_TYPES(cls): @@ -257,8 +280,8 @@ class PSDMockupEmbedder: } CATEGORY = "psd/mockup" - RETURN_TYPES = ("IMAGE", "STRING") - RETURN_NAMES = ("mockup_image", "debug_info") + RETURN_TYPES = ("IMAGE", "STRING", "STRING") + RETURN_NAMES = ("mockup_image", "debug_info", "png_path") FUNCTION = "apply" @staticmethod @@ -312,12 +335,13 @@ class PSDMockupEmbedder: ) result_tensor = _pil_to_tensor(base_rgba) + saved_path = _save_png(base_rgba, psd_path) debug_payload = { "psd": os.path.basename(psd_path), "layer_count": len(selected), "layers": metadata, } - return (result_tensor, json.dumps(debug_payload, indent=2)) + return (result_tensor, json.dumps(debug_payload, indent=2), saved_path) NODE_CLASS_MAPPINGS = {