diff --git a/README.md b/README.md index fe7caf1..67dd6aa 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,10 @@ ## UPDATES +**2024/01/07** @1.0.3: +* relative imports +* node names cleaned + **2024/01/05** @1.0.2: * reorganized imports diff --git a/__init__.py b/__init__.py index d6f96d5..f18c0ec 100644 --- a/__init__.py +++ b/__init__.py @@ -22,7 +22,7 @@ __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"] __author__ = """Alexander G. Morano""" __email__ = "amorano@gmail.com" -__version__ = "1.0.2" +__version__ = "1.0.3" import os import sys @@ -30,6 +30,7 @@ import json import inspect import importlib from pathlib import Path +from types import ModuleType from loguru import logger @@ -50,7 +51,59 @@ JOV_INTERNAL = os.getenv("JOV_INTERNAL", 'false').strip().lower() in ('true', '1 JOV_LOG_LEVEL = os.getenv("JOV_LOG_LEVEL", "INFO") logger.configure(handlers=[{"sink": sys.stdout, "level": JOV_LOG_LEVEL}]) -JOV_PACKAGE = "Jovi_GLSL" +JOV_PACKAGE = "JOV_GL" + +# ============================================================================== +# === CORE NODES === +# ============================================================================== + +class JOVBaseNode: + NOT_IDEMPOTENT = True + CATEGORY = f"{JOV_PACKAGE} 🦚" + RETURN_TYPES = ("IMAGE", "IMAGE", "MASK") + RETURN_NAMES = ('RGBA', 'RGB', 'MASK') + FUNCTION = "run" + + @classmethod + def VALIDATE_INPUTS(cls, *arg, **kw) -> bool: + return True + + @classmethod + def INPUT_TYPES(cls, prompt:bool=False, extra_png:bool=False, dynprompt:bool=False) -> dict: + data = { + "required": {}, + "optional": {}, + "outputs": { + 0: ("IMAGE", {"tooltips":"Full channel [RGBA] image. If there is an alpha, the image will be masked out with it when using this output."}), + 1: ("IMAGE", {"tooltips":"Three channel [RGB] image. There will be no alpha."}), + 2: ("MASK", {"tooltips":"Single channel mask output."}), + }, + "hidden": { + "ident": "UNIQUE_ID" + } + } + if prompt: + data["hidden"]["prompt"] = "PROMPT" + if extra_png: + data["hidden"]["extra_pnginfo"] = "EXTRA_PNGINFO" + + if dynprompt: + data["hidden"]["dynprompt"] = "DYNPROMPT" + return data + +# ============================================================================== +# === TYPE === +# ============================================================================== + +class AnyType(str): + """AnyType input wildcard trick taken from pythongossss's: + + https://github.com/pythongosssss/ComfyUI-Custom-Scripts + """ + def __ne__(self, __value: object) -> bool: + return False + +JOV_TYPE_ANY = AnyType("*") # ============================================================================== # === SUPPORT === @@ -67,6 +120,27 @@ def load_file(fname: str) -> str | None: # === LOADER === # ============================================================================== +def load_module(name: str) -> None|ModuleType: + module = inspect.getmodule(inspect.stack()[0][0]).__name__ + try: + route = str(name).replace("\\", "/") + route = route.split(f"{module}/core/")[1] + route = route.split('.')[0].replace('/', '.') + except Exception as e: + logger.warning(f"module failed {name}") + logger.warning(str(e)) + return + + try: + module = f"{module}.core.{route}" + module = importlib.import_module(module) + except Exception as e: + logger.warning(f"module failed {module}") + logger.warning(str(e)) + return + + return module + def loader(): global NODE_DISPLAY_NAME_MAPPINGS, NODE_CLASS_MAPPINGS NODE_LIST_MAP = {} @@ -75,39 +149,33 @@ def loader(): if fname.stem.startswith('_'): continue - try: - route = str(fname).replace("\\", "/").split(f"{JOV_PACKAGE}/core/")[1] - route = route.split('.')[0].replace('/', '.') - module = f"{JOV_PACKAGE}.core.{route}" - module = importlib.import_module(module) - except Exception as e: - logger.warning(f"module failed {fname}") - logger.warning(str(e)) + if (module := load_module(fname)) is None: continue # check if there is a dynamic register function.... try: for class_name, class_def in module.import_dynamic(): setattr(module, class_name, class_def) - # logger.debug(f"shader: {class_name}") except Exception as e: pass classes = inspect.getmembers(module, inspect.isclass) for class_name, class_object in classes: if not class_name.endswith('BaseNode') and hasattr(class_object, 'NAME') and hasattr(class_object, 'CATEGORY'): - name = class_object.NAME + name = f"{class_object.NAME} ({JOV_PACKAGE})" NODE_DISPLAY_NAME_MAPPINGS[name] = name NODE_CLASS_MAPPINGS[name] = class_object - desc = class_object.DESCRIPTION if hasattr(class_object, 'DESCRIPTION') else name - NODE_LIST_MAP[name] = desc.split('.')[0].strip('\n') + + if class_object.SORT < 5000: + desc = class_object.DESCRIPTION if hasattr(class_object, 'DESCRIPTION') else name + NODE_LIST_MAP[name] = desc.split('.')[0].strip('\n') NODE_CLASS_MAPPINGS = {x[0] : x[1] for x in sorted(NODE_CLASS_MAPPINGS.items(), key=lambda item: getattr(item[1], 'SORT', 0))} keys = NODE_CLASS_MAPPINGS.keys() for name in keys: - logger.debug(f"✅ {name} :: {NODE_DISPLAY_NAME_MAPPINGS[name]}") + logger.debug(f"✅ {name}") logger.info(f"{len(keys)} nodes loaded") # only do the list on local runs... diff --git a/core/__init__.py b/core/__init__.py index ac6c655..5ea08f8 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -15,7 +15,9 @@ import torch import numpy as np from loguru import logger -from Jovi_GLSL import JOV_PACKAGE, ROOT, load_file +from .. import JOV_TYPE_ANY, ROOT, \ + JOVBaseNode, \ + load_file # ============================================================================== # === SHADER LOADER === @@ -90,48 +92,6 @@ class Singleton(type): # === CORE NODES === # ============================================================================== -class JOVBaseNode: - NOT_IDEMPOTENT = True - CATEGORY = f"{JOV_PACKAGE.upper()} 🦚" - RETURN_TYPES = ("IMAGE", "IMAGE", "MASK") - RETURN_NAMES = ('RGBA', 'RGB', 'MASK') - FUNCTION = "run" - - @classmethod - def VALIDATE_INPUTS(cls, *arg, **kw) -> bool: - return True - - @classmethod - def INPUT_TYPES(cls, prompt:bool=False, extra_png:bool=False, dynprompt:bool=False) -> dict: - data = { - "required": {}, - "optional": {}, - "outputs": { - 0: ("IMAGE", {"tooltips":"Full channel [RGBA] image. If there is an alpha, the image will be masked out with it when using this output."}), - 1: ("IMAGE", {"tooltips":"Three channel [RGB] image. There will be no alpha."}), - 2: ("MASK", {"tooltips":"Single channel mask output."}), - }, - "hidden": { - "ident": "UNIQUE_ID" - } - } - if prompt: - data["hidden"]["prompt"] = "PROMPT" - if extra_png: - data["hidden"]["extra_pnginfo"] = "EXTRA_PNGINFO" - - if dynprompt: - data["hidden"]["dynprompt"] = "DYNPROMPT" - return data - -class AnyType(str): - """AnyType input wildcard trick taken from pythongossss's: - - https://github.com/pythongosssss/ComfyUI-Custom-Scripts - """ - def __ne__(self, __value: object) -> bool: - return False - class JOVBaseGLSLNode(JOVBaseNode): NOT_IDEMPOTENT = True RETURN_TYPES = ("IMAGE", "IMAGE", "MASK") @@ -220,7 +180,6 @@ JOV_TYPE_NUMBER = f"{JOV_TYPE_COMFY}|{JOV_TYPE_VECTOR}" JOV_TYPE_IMAGE = "IMAGE|MASK" JOV_TYPE_FULL = f"{JOV_TYPE_NUMBER}|{JOV_TYPE_IMAGE}" -JOV_TYPE_ANY = AnyType("*") JOV_TYPE_COMFY = JOV_TYPE_ANY JOV_TYPE_VECTOR = JOV_TYPE_ANY JOV_TYPE_NUMBER = JOV_TYPE_ANY diff --git a/core/glsl.py b/core/glsl.py index 34eee35..3cbd8cb 100644 --- a/core/glsl.py +++ b/core/glsl.py @@ -13,22 +13,20 @@ from loguru import logger from comfy.utils import ProgressBar -from Jovi_GLSL import load_file +from .. import load_file -from Jovi_GLSL.core import GLSL_PROGRAMS, JOV_TYPE_IMAGE, PTYPE, RE_VARIABLE, \ +from ..core import GLSL_PROGRAMS, JOV_TYPE_IMAGE, PTYPE, RE_VARIABLE, \ ROOT_GLSL, IMAGE_SIZE_MIN, IMAGE_SIZE_DEFAULT, IMAGE_SIZE_MAX, \ CompileException, JOVBaseGLSLNode, EnumConvertType, \ - cv2tensor_full, image_convert, parse_param, parse_value, tensor2cv, zip_longest_fill + cv2tensor_full, image_convert, parse_param, parse_value, \ + tensor2cv, zip_longest_fill -from Jovi_GLSL.core.glsl_shader import GLSLShader +from ..core.glsl_shader import GLSLShader # ============================================================================== # === CONSTANT === # ============================================================================== -GLSL_INTERNAL = '🌈' -GLSL_CUSTOM = '🦄' - RE_INCLUDE = re.compile(r"^\s*#include\s+([A-Za-z_\-\.\\\/]{3,})$", re.MULTILINE) RE_SHADER_META = re.compile(r"^\/\/\s?([A-Za-z_]{3,}):\s?(.+)$", re.MULTILINE) @@ -297,7 +295,7 @@ def load_file_glsl(fname: str) -> str: def import_dynamic() -> Tuple[str,...]: ret = [] - sort = 4000 + sort = 5000 root = str(ROOT_GLSL) for name, fname in GLSL_PROGRAMS['fragment'].items(): if (shader := load_file_glsl(fname)) is None: @@ -312,18 +310,17 @@ def import_dynamic() -> Tuple[str,...]: class_name = name.title().replace(' ', '_') class_name = f'GLSLNode_{class_name}' - emoji = GLSL_CUSTOM sort_order = sort + # put custom user nodes last if fname.startswith(root): - emoji = GLSL_INTERNAL - sort_order -= 2500 + sort_order -= 5000 category = GLSLNodeDynamic.CATEGORY if (sub := meta.get('category', None)) is not None: category += f'/{sub}' class_def = type(class_name, (GLSLNodeDynamic,), { - "NAME": f'GLSL {name} (JOV_GL) {emoji}'.upper(), + "NAME": name, "DESCRIPTION": meta.get('desc', name), "CATEGORY": category.upper(), "FRAGMENT": shader, diff --git a/core/glsl_manager.py b/core/glsl_manager.py index 185bf34..c75b20b 100644 --- a/core/glsl_manager.py +++ b/core/glsl_manager.py @@ -8,7 +8,7 @@ import gc import glfw from loguru import logger -from Jovi_GLSL.core import Singleton +from ..core import Singleton # ============================================================================== # === MANAGER === diff --git a/core/glsl_shader.py b/core/glsl_shader.py index dc3600f..1bbf247 100644 --- a/core/glsl_shader.py +++ b/core/glsl_shader.py @@ -13,12 +13,12 @@ import numpy as np import OpenGL.GL as gl from loguru import logger -from Jovi_GLSL.core import IMAGE_SIZE_MAX, IMAGE_SIZE_MIN, PROG_VERTEX, \ +from ..core import IMAGE_SIZE_MAX, IMAGE_SIZE_MIN, PROG_VERTEX, \ PROG_FRAGMENT, PROG_FOOTER, PROG_HEADER, PTYPE, RE_VARIABLE, \ CompileException, \ image_convert, parse_value -from Jovi_GLSL.core.glsl_manager import GLSLManager +from ..core.glsl_manager import GLSLManager # ============================================================================== # === CONSTANT === diff --git a/node_list.json b/node_list.json index 2cb0657..2bb8f8b 100644 --- a/node_list.json +++ b/node_list.json @@ -1,21 +1,21 @@ { - "GLSL BLEND LINEAR (JOV_GL) \ud83c\udf08": "Simple linear blend between two images", - "GLSL COLOR CONVERSION (JOV_GL) \ud83c\udf08": "Convert an image from one color space (RGB, HSV, LAB, XYZ) to another", - "GLSL COLOR PALETTE (JOV_GL) \ud83c\udf08": "Color palette creation using the formula: color(t) = a + b * cos[tau(c*t+d)]", - "GLSL CONICAL GRADIENT (JOV_GL) \ud83c\udf08": "Generate a conical gradient from black to white", - "GLSL DIRECTIONAL WARP (JOV_GL) \ud83c\udf08": "Domain warp an image with a direction and distortion map", - "GLSL FILTER RANGE (JOV_GL) \ud83c\udf08": "Select pixels from start color through end color", - "GLSL GRAYSCALE (JOV_GL) \ud83c\udf08": "Convert input to grayscale", - "GLSL HSV ADJUST (JOV_GL) \ud83c\udf08": "Hue, Saturation and Value adjustment control", - "GLSL INVERT (JOV_GL) \ud83c\udf08": "Invert the channels of an image along a scalar [0", - "GLSL MIN MAX (JOV_GL) \ud83c\udf08": "Gets the minimum and maximum of an image", - "GLSL NOISE PERLIN (JOV_GL) \ud83c\udf08": "Classic Perlin noise", - "GLSL NOISE SIMPLEX (JOV_GL) \ud83c\udf08": "Simplex noise, simply", - "GLSL NOISE WORLEY (JOV_GL) \ud83c\udf08": "Worley noise with the best", - "GLSL NORMAL (JOV_GL) \ud83c\udf08": "Convert input into a Normal map", - "GLSL NORMAL BLEND (JOV_GL) \ud83c\udf08": "Blend two Normal maps", - "GLSL PIXELATE (JOV_GL) \ud83c\udf08": "Pixelate input image", - "GLSL POSTERIZE (JOV_GL) \ud83c\udf08": "Reduce the pixel color data range", - "GLSL SOBEL (JOV_GL) \ud83c\udf08": "Finds the edges of an image", - "GLSL TRANSFORM (JOV_GL) \ud83c\udf08": "Move, Rotate, Scale and Tile an image" + "BLEND LINEAR (JOV_GL)": "Simple linear blend between two images", + "COLOR CONVERSION (JOV_GL)": "Convert an image from one color space (RGB, HSV, LAB, XYZ) to another", + "COLOR PALETTE (JOV_GL)": "Color palette creation using the formula: color(t) = a + b * cos[tau(c*t+d)]", + "CONICAL GRADIENT (JOV_GL)": "Generate a conical gradient from black to white", + "DIRECTIONAL WARP (JOV_GL)": "Domain warp an image with a direction and distortion map", + "FILTER RANGE (JOV_GL)": "Select pixels from start color through end color", + "GRAYSCALE (JOV_GL)": "Convert input to grayscale", + "HSV ADJUST (JOV_GL)": "Hue, Saturation and Value adjustment control", + "INVERT (JOV_GL)": "Invert the channels of an image along a scalar [0", + "MIN MAX (JOV_GL)": "Gets the minimum and maximum of an image", + "NOISE PERLIN (JOV_GL)": "Classic Perlin noise", + "NOISE SIMPLEX (JOV_GL)": "Simplex noise, simply", + "NOISE WORLEY (JOV_GL)": "Worley noise with the best", + "NORMAL (JOV_GL)": "Convert input into a Normal map", + "NORMAL BLEND (JOV_GL)": "Blend two Normal maps", + "PIXELATE (JOV_GL)": "Pixelate input image", + "POSTERIZE (JOV_GL)": "Reduce the pixel color data range", + "SOBEL (JOV_GL)": "Finds the edges of an image", + "TRANSFORM (JOV_GL)": "Move, Rotate, Scale and Tile an image" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 80ae64d..9043e62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "jovi_glsl" description = "Integrates GLSL shader support." -version = "1.0.2" +version = "1.0.3" license = { file = "LICENSE" } dependencies = [ "aenum>=3.1.15", diff --git a/web/node_glsl_dynamic.js b/web/node_glsl_dynamic.js index 2fedd04..31c5f9f 100644 --- a/web/node_glsl_dynamic.js +++ b/web/node_glsl_dynamic.js @@ -12,7 +12,7 @@ const _id = "GLSL DYNAMIC (JOV_GL)"; app.registerExtension({ name: 'jovi_glsl.node.' + _id, async beforeRegisterNodeDef(nodeType, nodeData) { - if ((!nodeData.name.endsWith("(JOV_GL) 🌈") && !nodeData.name.endsWith("(JOV_GL) 🦄"))) { + if (!nodeData.name.endsWith("(JOV_GL)")) { return; }