From d71e824b9005c10fa82bc8144fe5e6f4ba25b237 Mon Sep 17 00:00:00 2001 From: vincentfs Date: Tue, 14 Jan 2025 18:12:51 +0800 Subject: [PATCH] init structure --- .github/workflows/publish_action.yml | 20 ++++ ArchiGraphLogo.png | Bin 0 -> 3545 bytes README.md | 24 ++++- __init__.py | 6 ++ example_node.py.example | 155 +++++++++++++++++++++++++++ install.bat | 26 +++++ install.sh | 24 +++++ pyproject.toml | 14 +++ requirements.txt | 0 9 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/publish_action.yml create mode 100644 ArchiGraphLogo.png create mode 100644 __init__.py create mode 100644 example_node.py.example create mode 100644 install.bat create mode 100644 install.sh create mode 100644 pyproject.toml create mode 100644 requirements.txt diff --git a/.github/workflows/publish_action.yml b/.github/workflows/publish_action.yml new file mode 100644 index 0000000..d89ef1a --- /dev/null +++ b/.github/workflows/publish_action.yml @@ -0,0 +1,20 @@ +name: Publish to Comfy registry +on: + workflow_dispatch: + push: + branches: + - main + paths: + - "pyproject.toml" + +jobs: + publish-node: + name: Publish Custom Node to registry + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Publish Custom Node + uses: Comfy-Org/publish-node-action@main + with: + personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} \ No newline at end of file diff --git a/ArchiGraphLogo.png b/ArchiGraphLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..26dca68ecdf1f9fa2c16d17523e3b8c9bf0d3fc7 GIT binary patch literal 3545 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 literal 0 HcmV?d00001 diff --git a/README.md b/README.md index a925cc8..bac9393 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,24 @@ # ComfyUI-ArchiGraph -Implementation of architectural related graph algorithm in ComfyUI. + +
+ +
+ +**ComfyUI-ArchiGraph** is an implementation of architectural related graph algorithm in ComfyUI. + +## 🎈 Introduction + + +## 🚀 Installation +Clone the repo into the **custom_nodes** directory and install the requirements: +```shell +#1. Clone the repo +git clone https://github.com/vincentfs/ComfyUI-ArchiGraph +#2. Install the requirements +Double-click install.bat to install the required dependencies +``` + +## 📜 Credits + +## 💗 SponsorShip + diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..570722e --- /dev/null +++ b/__init__.py @@ -0,0 +1,6 @@ +WEB_DIRECTORY = "./js/v1" + +NODE_CLASS_MAPPINGS = {} +NODE_DISPLAY_NAME_MAPPINGS = {} + +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] \ No newline at end of file diff --git a/example_node.py.example b/example_node.py.example new file mode 100644 index 0000000..29ab2aa --- /dev/null +++ b/example_node.py.example @@ -0,0 +1,155 @@ +class Example: + """ + A example node + + Class methods + ------------- + INPUT_TYPES (dict): + Tell the main program input parameters of nodes. + IS_CHANGED: + optional method to control when the node is re executed. + + Attributes + ---------- + RETURN_TYPES (`tuple`): + The type of each element in the output tuple. + RETURN_NAMES (`tuple`): + Optional: The name of each output in the output tuple. + FUNCTION (`str`): + The name of the entry-point method. For example, if `FUNCTION = "execute"` then it will run Example().execute() + OUTPUT_NODE ([`bool`]): + If this node is an output node that outputs a result/image from the graph. The SaveImage node is an example. + The backend iterates on these output nodes and tries to execute all their parents if their parent graph is properly connected. + Assumed to be False if not present. + CATEGORY (`str`): + The category the node should appear in the UI. + DEPRECATED (`bool`): + Indicates whether the node is deprecated. Deprecated nodes are hidden by default in the UI, but remain + functional in existing workflows that use them. + EXPERIMENTAL (`bool`): + Indicates whether the node is experimental. Experimental nodes are marked as such in the UI and may be subject to + significant changes or removal in future versions. Use with caution in production workflows. + execute(s) -> tuple || None: + The entry point method. The name of this method must be the same as the value of property `FUNCTION`. + For example, if `FUNCTION = "execute"` then this method's name must be `execute`, if `FUNCTION = "foo"` then it must be `foo`. + """ + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + """ + Return a dictionary which contains config for all input fields. + Some types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT". + Input types "INT", "STRING" or "FLOAT" are special values for fields on the node. + The type can be a list for selection. + + Returns: `dict`: + - Key input_fields_group (`string`): Can be either required, hidden or optional. A node class must have property `required` + - Value input_fields (`dict`): Contains input fields config: + * Key field_name (`string`): Name of a entry-point method's argument + * Value field_config (`tuple`): + + First value is a string indicate the type of field or a list for selection. + + Second value is a config for type "INT", "STRING" or "FLOAT". + """ + return { + "required": { + "image": ("IMAGE",), + "int_field": ("INT", { + "default": 0, + "min": 0, #Minimum value + "max": 4096, #Maximum value + "step": 64, #Slider's step + "display": "number", # Cosmetic only: display as "number" or "slider" + "lazy": True # Will only be evaluated if check_lazy_status requires it + }), + "float_field": ("FLOAT", { + "default": 1.0, + "min": 0.0, + "max": 10.0, + "step": 0.01, + "round": 0.001, #The value representing the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. + "display": "number", + "lazy": True + }), + "print_to_screen": (["enable", "disable"],), + "string_field": ("STRING", { + "multiline": False, #True if you want the field to look like the one on the ClipTextEncode node + "default": "Hello World!", + "lazy": True + }), + }, + } + + RETURN_TYPES = ("IMAGE",) + #RETURN_NAMES = ("image_output_name",) + + FUNCTION = "test" + + #OUTPUT_NODE = False + + CATEGORY = "Example" + + def check_lazy_status(self, image, string_field, int_field, float_field, print_to_screen): + """ + Return a list of input names that need to be evaluated. + + This function will be called if there are any lazy inputs which have not yet been + evaluated. As long as you return at least one field which has not yet been evaluated + (and more exist), this function will be called again once the value of the requested + field is available. + + Any evaluated inputs will be passed as arguments to this function. Any unevaluated + inputs will have the value None. + """ + if print_to_screen == "enable": + return ["int_field", "float_field", "string_field"] + else: + return [] + + def test(self, image, string_field, int_field, float_field, print_to_screen): + if print_to_screen == "enable": + print(f"""Your input contains: + string_field aka input text: {string_field} + int_field: {int_field} + float_field: {float_field} + """) + #do some processing on the image, in this example I just invert it + image = 1.0 - image + return (image,) + + """ + The node will always be re executed if any of the inputs change but + this method can be used to force the node to execute again even when the inputs don't change. + You can make this node return a number or a string. This value will be compared to the one returned the last time the node was + executed, if it is different the node will be executed again. + This method is used in the core repo for the LoadImage node where they return the image hash as a string, if the image hash + changes between executions the LoadImage node is executed again. + """ + #@classmethod + #def IS_CHANGED(s, image, string_field, int_field, float_field, print_to_screen): + # return "" + +# Set the web directory, any .js file in that directory will be loaded by the frontend as a frontend extension +# WEB_DIRECTORY = "./somejs" + + +# Add custom API routes, using router +from aiohttp import web +from server import PromptServer + +@PromptServer.instance.routes.get("/hello") +async def get_hello(request): + return web.json_response("hello") + + +# A dictionary that contains all nodes you want to export with their names +# NOTE: names should be globally unique +NODE_CLASS_MAPPINGS = { + "Example": Example +} + +# A dictionary that contains the friendly/humanly readable titles for the nodes +NODE_DISPLAY_NAME_MAPPINGS = { + "Example": "Example Node" +} diff --git a/install.bat b/install.bat new file mode 100644 index 0000000..ab16553 --- /dev/null +++ b/install.bat @@ -0,0 +1,26 @@ +@echo off + +set "requirements_txt=%~dp0\requirements.txt" +set "requirements_repair_txt=%~dp0\repair_dependency_list.txt" +set "python_exec=..\..\..\python_embeded\python.exe" +set "aki_python_exec=..\..\python\python.exe" + +echo Installing ArchiGraph Requirements... + +if exist "%python_exec%" ( + echo Installing with ComfyUI Portable + "%python_exec%" -s -m pip install -r "%requirements_txt%" +)^ +else if exist "%aki_python_exec%" ( + echo Installing with ComfyUI Aki + "%aki_python_exec%" -s -m pip install -r "%requirements_txt%" + for /f "delims=" %%i in (%requirements_repair_txt%) do ( + %aki_python_exec% -s -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple "%%i" + ) +)^ +else ( + echo Installing with system Python + pip install -r "%requirements_txt%" +) + +pause \ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..5c7fb05 --- /dev/null +++ b/install.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +requirements_txt="$(dirname "$0")/requirements.txt" +requirements_repair_txt="$(dirname "$0")/repair_dependency_list.txt" +python_exec="../../../python_embeded/python.exe" +aki_python_exec="../../python/python.exe" + +echo "Installing ArchiGraph Requirements..." + +if [ -f "$python_exec" ]; then + echo "Installing with ComfyUI Portable" + "$python_exec" -s -m pip install -r "$requirements_txt" +elif [ -f "$aki_python_exec" ]; then + echo "Installing with ComfyUI Aki" + "$aki_python_exec" -s -m pip install -r "$requirements_txt" + while IFS= read -r line; do + "$aki_python_exec" -s -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple "$line" + done < "$requirements_repair_txt" +else + echo "Installing with system Python" + pip install -r "$requirements_txt" +fi + +read -p "Press any key to continue..." \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5403bac --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,14 @@ +[project] +name = "archigraph" +description = "Implementation of architectural related graph algorithm in ComfyUI." +version = 0.0.1" +license = {file = "LICENSE"} + +[project.urls] +Repository = "https://github.com/vincentfs/ComfyUI-ArchiGraph" +# Used by Comfy Registry https://comfyregistry.org + +[tool.comfy] +PublisherId = "vincentfs" +DisplayName = "ComfyUI-ArchiGraph" +Icon = "" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e69de29