From fee6fa9da7a688bb7641c3ad1ede3da7f9809979 Mon Sep 17 00:00:00 2001 From: tj_comfyui Date: Sat, 8 Nov 2025 11:35:29 +0900 Subject: [PATCH] Add SVG file saver nodes (SVGFileSaver and SVGBatchSaver) --- __init__.py | 7 +- svg_file_saver.py | 168 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 svg_file_saver.py diff --git a/__init__.py b/__init__.py index 44843de..2f2a4c3 100644 --- a/__init__.py +++ b/__init__.py @@ -5,16 +5,19 @@ Line art to vector conversion nodes from .line_region_detector import NODE_CLASS_MAPPINGS as LINE_NODES, NODE_DISPLAY_NAME_MAPPINGS as LINE_NAMES from .centerline_to_svg import NODE_CLASS_MAPPINGS as SVG_NODES, NODE_DISPLAY_NAME_MAPPINGS as SVG_NAMES +from .svg_file_saver import NODE_CLASS_MAPPINGS as SAVER_NODES, NODE_DISPLAY_NAME_MAPPINGS as SAVER_NAMES # Combine all node mappings NODE_CLASS_MAPPINGS = { **LINE_NODES, - **SVG_NODES + **SVG_NODES, + **SAVER_NODES } NODE_DISPLAY_NAME_MAPPINGS = { **LINE_NAMES, - **SVG_NAMES + **SVG_NAMES, + **SAVER_NAMES } __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] diff --git a/svg_file_saver.py b/svg_file_saver.py new file mode 100644 index 0000000..7163576 --- /dev/null +++ b/svg_file_saver.py @@ -0,0 +1,168 @@ +""" +SVG File Saver Node for ComfyUI +Saves SVG strings to files +""" + +import os +import json +from datetime import datetime + + +class SVGFileSaver: + """ + Saves SVG strings to files + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "svg_string": ("STRING", {"forceInput": True}), + "filename_prefix": ("STRING", {"default": "centerline"}), + "output_path": ("STRING", {"default": "output/svg"}), + }, + "optional": { + "auto_timestamp": ("BOOLEAN", {"default": True}), + "overwrite": ("BOOLEAN", {"default": False}), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("file_path",) + FUNCTION = "save_svg" + CATEGORY = "TJ_Vector" + OUTPUT_NODE = True + + def save_svg(self, svg_string, filename_prefix="centerline", + output_path="output/svg", auto_timestamp=True, overwrite=False): + """ + Save SVG string to file + """ + # Create output directory if it doesn't exist + os.makedirs(output_path, exist_ok=True) + + # Generate filename + if auto_timestamp: + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"{filename_prefix}_{timestamp}.svg" + else: + filename = f"{filename_prefix}.svg" + + filepath = os.path.join(output_path, filename) + + # Check if file exists and handle overwrite + if os.path.exists(filepath) and not overwrite: + # Add counter to filename + counter = 1 + base_name = filename[:-4] # Remove .svg + while os.path.exists(filepath): + filename = f"{base_name}_{counter}.svg" + filepath = os.path.join(output_path, filename) + counter += 1 + + # Save SVG file + try: + with open(filepath, 'w', encoding='utf-8') as f: + f.write(svg_string) + + print(f"SVG saved to: {filepath}") + return (filepath,) + + except Exception as e: + print(f"Error saving SVG: {e}") + return ("",) + + +class SVGBatchSaver: + """ + Saves multiple SVG files with metadata + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "svg_string": ("STRING", {"forceInput": True}), + "filename_prefix": ("STRING", {"default": "centerline"}), + "output_path": ("STRING", {"default": "output/svg"}), + }, + "optional": { + "statistics": ("STRING", {"default": ""}), + "color_info": ("STRING", {"default": ""}), + "save_metadata": ("BOOLEAN", {"default": True}), + } + } + + RETURN_TYPES = ("STRING", "STRING") + RETURN_NAMES = ("svg_path", "metadata_path") + FUNCTION = "save_svg_batch" + CATEGORY = "TJ_Vector" + OUTPUT_NODE = True + + def save_svg_batch(self, svg_string, filename_prefix="centerline", + output_path="output/svg", statistics="", + color_info="", save_metadata=True): + """ + Save SVG with metadata + """ + # Create output directory + os.makedirs(output_path, exist_ok=True) + + # Generate filename with timestamp + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + base_filename = f"{filename_prefix}_{timestamp}" + + # Save SVG file + svg_filepath = os.path.join(output_path, f"{base_filename}.svg") + try: + with open(svg_filepath, 'w', encoding='utf-8') as f: + f.write(svg_string) + print(f"SVG saved to: {svg_filepath}") + except Exception as e: + print(f"Error saving SVG: {e}") + return ("", "") + + # Save metadata if requested + metadata_filepath = "" + if save_metadata: + metadata = { + "timestamp": timestamp, + "svg_file": f"{base_filename}.svg", + "statistics": {}, + "color_info": {} + } + + # Parse statistics JSON + if statistics: + try: + metadata["statistics"] = json.loads(statistics) + except: + metadata["statistics"] = {"raw": statistics} + + # Parse color info JSON + if color_info: + try: + metadata["color_info"] = json.loads(color_info) + except: + metadata["color_info"] = {"raw": color_info} + + metadata_filepath = os.path.join(output_path, f"{base_filename}_metadata.json") + try: + with open(metadata_filepath, 'w', encoding='utf-8') as f: + json.dump(metadata, f, indent=2, ensure_ascii=False) + print(f"Metadata saved to: {metadata_filepath}") + except Exception as e: + print(f"Error saving metadata: {e}") + + return (svg_filepath, metadata_filepath) + + +NODE_CLASS_MAPPINGS = { + "SVGFileSaver": SVGFileSaver, + "SVGBatchSaver": SVGBatchSaver +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "SVGFileSaver": "SVG File Saver", + "SVGBatchSaver": "SVG Batch Saver" +}