Add custom nodes, Civitai loras (LFS), and vast.ai setup script
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled
Includes 30 custom nodes committed directly, 7 Civitai-exclusive loras stored via Git LFS, and a setup script that installs all dependencies and downloads HuggingFace-hosted models on vast.ai. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
6
custom_nodes/ComfyUI-Crystools/core/__init__.py
Normal file
6
custom_nodes/ComfyUI-Crystools/core/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .logger import *
|
||||
from .keys import *
|
||||
from .types import *
|
||||
from .config import *
|
||||
from .common import *
|
||||
from .version import *
|
||||
119
custom_nodes/ComfyUI-Crystools/core/common.py
Normal file
119
custom_nodes/ComfyUI-Crystools/core/common.py
Normal file
@@ -0,0 +1,119 @@
|
||||
import os
|
||||
import json
|
||||
import torch
|
||||
from deepdiff import DeepDiff
|
||||
from ..core import CONFIG, logger
|
||||
|
||||
|
||||
# just a helper function to set the widget values (or clear them)
|
||||
def setWidgetValues(value=None, unique_id=None, extra_pnginfo=None) -> None:
|
||||
if unique_id and extra_pnginfo:
|
||||
workflow = extra_pnginfo["workflow"]
|
||||
node = next((x for x in workflow["nodes"] if str(x["id"]) == unique_id), None)
|
||||
|
||||
if node:
|
||||
node["widgets_values"] = value
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# find difference between two jsons
|
||||
def findJsonStrDiff(json1, json2):
|
||||
msgError = "Could not compare jsons"
|
||||
returnJson = {"error": msgError}
|
||||
try:
|
||||
# TODO review this
|
||||
# dict1 = json.loads(json1)
|
||||
# dict2 = json.loads(json2)
|
||||
|
||||
returnJson = findJsonsDiff(json1, json2)
|
||||
|
||||
returnJson = json.dumps(returnJson, indent=CONFIG["indent"])
|
||||
except Exception as e:
|
||||
logger.warn(f"{msgError}: {e}")
|
||||
|
||||
return returnJson
|
||||
|
||||
|
||||
def findJsonsDiff(json1, json2):
|
||||
msgError = "Could not compare jsons"
|
||||
returnJson = {"error": msgError}
|
||||
|
||||
try:
|
||||
diff = DeepDiff(json1, json2, ignore_order=True, verbose_level=2)
|
||||
|
||||
returnJson = {k: v for k, v in diff.items() if
|
||||
k in ('dictionary_item_added', 'dictionary_item_removed', 'values_changed')}
|
||||
|
||||
# just for print "values_changed" at first
|
||||
returnJson = dict(reversed(returnJson.items()))
|
||||
|
||||
except Exception as e:
|
||||
logger.warn(f"{msgError}: {e}")
|
||||
|
||||
return returnJson
|
||||
|
||||
|
||||
# powered by:
|
||||
# https://github.com/WASasquatch/was-node-suite-comfyui/blob/main/WAS_Node_Suite.py
|
||||
# class: WAS_Samples_Passthrough_Stat_System
|
||||
def get_system_stats():
|
||||
import psutil
|
||||
|
||||
# RAM
|
||||
ram = psutil.virtual_memory()
|
||||
ram_used = ram.used / (1024 ** 3)
|
||||
ram_total = ram.total / (1024 ** 3)
|
||||
ram_stats = f"Used RAM: {ram_used:.2f} GB / Total RAM: {ram_total:.2f} GB"
|
||||
|
||||
# VRAM (with PyTorch)
|
||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
vram_used = torch.cuda.memory_allocated(device) / (1024 ** 3)
|
||||
vram_total = torch.cuda.get_device_properties(device).total_memory / (1024 ** 3)
|
||||
vram_stats = f"Used VRAM: {vram_used:.2f} GB / Total VRAM: {vram_total:.2f} GB"
|
||||
|
||||
# Hard Drive Space
|
||||
hard_drive = psutil.disk_usage("/")
|
||||
used_space = hard_drive.used / (1024 ** 3)
|
||||
total_space = hard_drive.total / (1024 ** 3)
|
||||
hard_drive_stats = f"Used Space: {used_space:.2f} GB / Total Space: {total_space:.2f} GB"
|
||||
|
||||
return [ram_stats, vram_stats, hard_drive_stats]
|
||||
|
||||
|
||||
# return x and y resolution of an image (torch tensor)
|
||||
def getResolutionByTensor(image=None) -> dict:
|
||||
res = {"x": 0, "y": 0}
|
||||
|
||||
if image is not None:
|
||||
img = image.movedim(-1, 1)
|
||||
|
||||
res["x"] = img.shape[3]
|
||||
res["y"] = img.shape[2]
|
||||
|
||||
return res
|
||||
|
||||
|
||||
# by https://stackoverflow.com/questions/6080477/how-to-get-the-size-of-tar-gz-in-mb-file-in-python
|
||||
def get_size(path):
|
||||
size = os.path.getsize(path)
|
||||
if size < 1024:
|
||||
return f"{size} bytes"
|
||||
elif size < pow(1024, 2):
|
||||
return f"{round(size / 1024, 2)} KB"
|
||||
elif size < pow(1024, 3):
|
||||
return f"{round(size / (pow(1024, 2)), 2)} MB"
|
||||
elif size < pow(1024, 4):
|
||||
return f"{round(size / (pow(1024, 3)), 2)} GB"
|
||||
|
||||
|
||||
def get_nested_value(data, dotted_key, default=None):
|
||||
keys = dotted_key.split('.')
|
||||
for key in keys:
|
||||
if isinstance(data, str):
|
||||
data = json.loads(data)
|
||||
if isinstance(data, dict) and key in data:
|
||||
data = data[key]
|
||||
else:
|
||||
return default
|
||||
return data
|
||||
7
custom_nodes/ComfyUI-Crystools/core/config.py
Normal file
7
custom_nodes/ComfyUI-Crystools/core/config.py
Normal file
@@ -0,0 +1,7 @@
|
||||
import os
|
||||
import logging
|
||||
|
||||
CONFIG = {
|
||||
"loglevel": int(os.environ.get("CRYSTOOLS_LOGLEVEL", logging.INFO)),
|
||||
"indent": int(os.environ.get("CRYSTOOLS_INDENT", 2))
|
||||
}
|
||||
29
custom_nodes/ComfyUI-Crystools/core/keys.py
Normal file
29
custom_nodes/ComfyUI-Crystools/core/keys.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class TEXTS(Enum):
|
||||
CUSTOM_NODE_NAME = "Crystools"
|
||||
LOGGER_PREFIX = "Crystools"
|
||||
CONCAT = "concatenated"
|
||||
INACTIVE_MSG = "inactive"
|
||||
INVALID_METADATA_MSG = "Invalid metadata raw"
|
||||
FILE_NOT_FOUND = "File not found!"
|
||||
|
||||
|
||||
class CATEGORY(Enum):
|
||||
TESTING = "_for_testing"
|
||||
MAIN = "crystools 🪛"
|
||||
PRIMITIVE = "/Primitive"
|
||||
DEBUGGER = "/Debugger"
|
||||
LIST = "/List"
|
||||
SWITCH = "/Switch"
|
||||
PIPE = "/Pipe"
|
||||
IMAGE = "/Image"
|
||||
UTILS = "/Utils"
|
||||
METADATA = "/Metadata"
|
||||
|
||||
|
||||
# remember, all keys should be in lowercase!
|
||||
class KEYS(Enum):
|
||||
LIST = "list_string"
|
||||
PREFIX = "prefix"
|
||||
39
custom_nodes/ComfyUI-Crystools/core/logger.py
Normal file
39
custom_nodes/ComfyUI-Crystools/core/logger.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# by https://github.com/Kosinkadink/ComfyUI-Advanced-ControlNet/blob/main/control/logger.py
|
||||
import sys
|
||||
import copy
|
||||
import logging
|
||||
from .keys import TEXTS
|
||||
from .config import CONFIG
|
||||
|
||||
|
||||
class ColoredFormatter(logging.Formatter):
|
||||
COLORS = {
|
||||
"DEBUG": "\033[0;36m", # CYAN
|
||||
"INFO": "\033[0;32m", # GREEN
|
||||
"WARNING": "\033[0;33m", # YELLOW
|
||||
"ERROR": "\033[0;31m", # RED
|
||||
"CRITICAL": "\033[0;37;41m", # WHITE ON RED
|
||||
"RESET": "\033[0m", # RESET COLOR
|
||||
}
|
||||
|
||||
def format(self, record):
|
||||
colored_record = copy.copy(record)
|
||||
levelname = colored_record.levelname
|
||||
seq = self.COLORS.get(levelname, self.COLORS["RESET"])
|
||||
colored_record.levelname = f"{seq}{levelname}{self.COLORS['RESET']}"
|
||||
return super().format(colored_record)
|
||||
|
||||
|
||||
# Create a new logger
|
||||
logger = logging.getLogger(TEXTS.LOGGER_PREFIX.value)
|
||||
logger.propagate = False
|
||||
|
||||
# Add handler if we don't have one.
|
||||
if not logger.handlers:
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
handler.setFormatter(ColoredFormatter("[%(name)s %(levelname)s] %(message)s"))
|
||||
logger.addHandler(handler)
|
||||
|
||||
# Configure logger
|
||||
loglevel = CONFIG["loglevel"]
|
||||
logger.setLevel(loglevel)
|
||||
36
custom_nodes/ComfyUI-Crystools/core/types.py
Normal file
36
custom_nodes/ComfyUI-Crystools/core/types.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import sys
|
||||
|
||||
FLOAT = ("FLOAT", {"default": 1,
|
||||
"min": -sys.float_info.max,
|
||||
"max": sys.float_info.max,
|
||||
"step": 0.01})
|
||||
|
||||
BOOLEAN = ("BOOLEAN", {"default": True})
|
||||
BOOLEAN_FALSE = ("BOOLEAN", {"default": False})
|
||||
|
||||
INT = ("INT", {"default": 1,
|
||||
"min": -sys.maxsize,
|
||||
"max": sys.maxsize,
|
||||
"step": 1})
|
||||
|
||||
STRING = ("STRING", {"default": ""})
|
||||
|
||||
STRING_ML = ("STRING", {"multiline": True, "default": ""})
|
||||
|
||||
STRING_WIDGET = ("STRING", {"forceInput": True})
|
||||
|
||||
JSON_WIDGET = ("JSON", {"forceInput": True})
|
||||
|
||||
METADATA_RAW = ("METADATA_RAW", {"forceInput": True})
|
||||
|
||||
class AnyType(str):
|
||||
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
|
||||
|
||||
def __eq__(self, _) -> bool:
|
||||
return True
|
||||
|
||||
def __ne__(self, __value: object) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
any = AnyType("*")
|
||||
1
custom_nodes/ComfyUI-Crystools/core/version.py
Normal file
1
custom_nodes/ComfyUI-Crystools/core/version.py
Normal file
@@ -0,0 +1 @@
|
||||
version = "1.27.4"
|
||||
Reference in New Issue
Block a user