From a32d8b6b0a273a851fc7277702ec663f598d74ea Mon Sep 17 00:00:00 2001 From: noembryo Date: Fri, 9 May 2025 22:54:48 +0300 Subject: [PATCH] 1.1.0 --- README.md | 47 +++++++++++++------- nodes.py | 81 +++++++++++++++++++++++++++++++++-- pyproject.toml | 10 +++-- stuff/auto_save_workflow.png | Bin 0 -> 4770 bytes 4 files changed, 115 insertions(+), 23 deletions(-) create mode 100644 stuff/auto_save_workflow.png diff --git a/README.md b/README.md index de9ad27..f7b85f4 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # noEmbryo Nodes A diverse set of nodes for ComfyUI. -* [PromptTermList (1-6)](#PromptTermList) -* [Resolution Scale](#ResolutionScale) -* [Regex Text Chopper](#RegexTextChopper) +- [PromptTermList (1-6)](#prompttermlist-1-6) +- [Resolution Scale](#resolution-scale) +- [Regex Text Chopper](#regex-text-chopper) +- [Auto Save Workflow](#auto-save-workflow) You can access them through "Add node > noEmbryo" submenu. @@ -10,7 +11,7 @@ You can access them through "Add node > noEmbryo" submenu. [![License: MIT][MIT]](LICENSE) --- -## PromptTermList (1-6) +## PromptTermList (1-6) These are some nodes that help with the creation of Prompts inside [ComfyUI](https://github.com/comfyanonymous/ComfyUI). @@ -27,20 +28,20 @@ The "label" part is what we see at the node's dropdown menu, and the "value" par These `json` files are located inside the `TermLists` directory, in the node's folder. There are two ways to add a new term. -* From within ComfyUI: - * Connect a text box to the node's `text` input. - * Write the "label"/"value" part in the box using the following format: +- From within ComfyUI: + - Connect a text box to the node's `text` input. + - Write the "label"/"value" part in the box using the following format: ``` label=Descriptive text value=masterpiece, artful and cozy ``` - * Enable the `store_input` switch. - * Run a generation job. - * Refresh the page. -* Manually: - * Just open the `json` file and add/remove/change entries. + - Enable the `store_input` switch. + - Run a generation job. + - Refresh the page. +- Manually: + - Just open the `json` file and add/remove/change entries. - Of course we must be very careful with this, to keep the `json` format of labels/values (with the appropriate commas), otherwise the file will not be parsed. + Ofcourse we must be very careful with this, to keep the `json` format of labels/values (with the appropriate commas), otherwise the file will not be parsed. This `text` input is also useful if we want to manually add something *after* our term, or as the *only* term if we select the `None` label of the dropdown. The `strength` value is changing the impact of the term by using the parenthesis format like this: `(a great term:1.3)` @@ -51,16 +52,30 @@ label=The label to be deleted value= ``` --- -## Resolution Scale +## Resolution Scale ![Demo workflow](https://raw.githubusercontent.com/noembryo/ComfyUI-noEmbryo/master/stuff/res_scale1.png) A simple node that outputs the resolution of an image using the dimensions of an input image or some custom user-defined dimensions, using a Scale Factor. If there is an input image connected, setting either `width` or `height` to 0 will use the other dimension to scale the image (but always multiple of 4). --- -## Regex Text Chopper +## Regex Text Chopper ![Demo workflow](https://raw.githubusercontent.com/noembryo/ComfyUI-noEmbryo/master/stuff/regex_text.png) -A node that "chops" a text using a regular expression and outputs the chopped parts of the text. +A node that "chops" a text using a regular expression and outputs the chopped parts of the text. + +--- +## Auto Save Workflow +![Demo workflow](https://raw.githubusercontent.com/noembryo/ComfyUI-noEmbryo/master/stuff/auto_save_workflow.png) +This node can save the current workflow to a `.json` file, every time a generation job is run. +- **UI controls** + - **save_directory** is the directory where the `.json` file will be saved. + It can be a relative path inside the ComfyUI's output directory, or an absolute path of your hard drive. + - **filename** is the name of the `.json` file. + It can contain the `{timestamp}` placeholder, which will be replaced with the current timestamp (for unique filenames). + - **trigger** switch will enable (or not) the saving of the workflow. +- **Outputs** + - **status** outputs a text with the status of the saving process (the path of the saved file, or an error message). + - **✳️trigger** is a dummy output that's used to trigger the saving process, even if nothing is connected to the node. --- ## Installation diff --git a/nodes.py b/nodes.py index 3030ea6..b2269f5 100644 --- a/nodes.py +++ b/nodes.py @@ -1,7 +1,8 @@ -import io -import re +import os, re, io import json from os.path import realpath, join, dirname +from datetime import datetime +import folder_paths MANIFEST = {"name": "noEmbryo Nodes", "version": (1, 0, 3), @@ -11,7 +12,7 @@ MANIFEST = {"name": "noEmbryo Nodes", "license": "MIT", } __author__ = "noEmbryo" -__version__ = "1.0.5" +__version__ = "1.1.0" # LISTS_PATH = realpath("./custom_nodes/ComfyUI-noEmbryo/TermLists/") LISTS_PATH = join(dirname(realpath(__file__)), "TermLists") @@ -262,6 +263,77 @@ class RegExTextChopper: return text1, text2, text3, text4, text_all +class AutoSaveWorkflow: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "save_directory": ("STRING", { + "default": "saved_workflows", + "tooltip": "Relative to ComfyUI output directory or absolute path" + }), + "filename": ("STRING", { + "default": "workflow_{timestamp}", + "tooltip": "Filename (include {timestamp} for unique timestamps)" + }), + "trigger": ("BOOLEAN", { + "default": True, + "label_on": "Enabled", + "label_off": "Disabled", + "tooltip": "Save the workflow if Enabled" + }), + }, + "hidden": { + "prompt": "PROMPT", + "extra_pnginfo": "EXTRA_PNGINFO", + "trigger": "BOOLEAN", # Hidden trigger input + }, + } + + RETURN_TYPES = ("STRING", "BOOLEAN") + RETURN_NAMES = ("status", "✳️trigger") + OUTPUT_TOOLTIPS = ("Get a status report text", + "Dammy output, to trigger execution if nothing is connected") + FUNCTION = "execute" + CATEGORY = "utils" + OUTPUT_NODE = True + + def execute(self, trigger, save_directory, filename, prompt=None, extra_pnginfo=None): + status = "Trigger disabled - workflow not saved" + + if trigger: + try: + workflow_data = extra_pnginfo.get("workflow", {}) if extra_pnginfo else {} + + # Process save directory + if os.path.isabs(save_directory): + output_dir = save_directory + else: + output_dir = os.path.join(folder_paths.get_output_directory(), + save_directory) + os.makedirs(output_dir, exist_ok=True) + + # Process filename with timestamp + timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") + processed_filename = filename.replace("{timestamp}", timestamp) + + # Ensure .json extension + if not processed_filename.lower().endswith('.json'): + processed_filename += '.json' + + save_path = os.path.join(output_dir, processed_filename) + + # Save workflow to JSON + with open(save_path, "w", encoding="utf-8") as f: + json.dump(workflow_data, f, indent=4) + + status = f"Workflow saved to: {save_path}" + except Exception as e: + status = f"Error saving workflow: {str(e)}" + + return (status,) + + NODE_CLASS_MAPPINGS = {"PromptTermList1": PromptTermList1, "PromptTermList2": PromptTermList2, "PromptTermList3": PromptTermList3, @@ -270,6 +342,7 @@ NODE_CLASS_MAPPINGS = {"PromptTermList1": PromptTermList1, "PromptTermList6": PromptTermList6, f"Resolution Scale /{__author__}": ResolutionScale, f"Regex Text Chopper /{__author__}": RegExTextChopper, + f"Auto Save Workflow /{__author__}": AutoSaveWorkflow } NODE_DISPLAY_NAME_MAPPINGS = {"PromptTermList1": f"PromptTermList 1 /{__author__}", @@ -282,4 +355,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {"PromptTermList1": f"PromptTermList 1 /{__author__ f"Resolution Scale /{__author__}", f"Regex Text Chopper /{__author__}": f"Regex Text Chopper /{__author__}", + f"Auto Save Workflow /{__author__}": + f"Auto Save Workflow /{__author__}", } diff --git a/pyproject.toml b/pyproject.toml index 55d2b8b..a3c1fb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,10 +2,12 @@ name = "comfyui-noembryo" description = """ A collection of nodes for ComyUI. -- "PromptTermList (1-6)" nodes to help with the creation of Prompts. -- "Resolution Scale" is a node that resizes an image using pixels or scale factor. -- "Regex Text Chopper" is a node that cuts text based on regex.""" -version = "1.0.5" +* "PromptTermList (1-6)" nodes to help with the creation of Prompts. +* "Resolution Scale" is a node that resizes an image using pixels or scale factor. +* "Regex Text Chopper" is a node that cuts text based on regex. +* "Auto Save Workflow" can save the current workflow to a .json file automatically. +""" +version = "1.1.0" license = {text = "MIT License"} [project.urls] diff --git a/stuff/auto_save_workflow.png b/stuff/auto_save_workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..90db641e6fb17c766d2f1fe272fadb35d48b22b0 GIT binary patch literal 4770 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 literal 0 HcmV?d00001