Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +10 -0
- custom_nodes/rgthree-comfy/.github/workflows/publish_comfy_registry_action.yml +20 -0
- custom_nodes/rgthree-comfy/.gitignore +13 -0
- custom_nodes/rgthree-comfy/.prettierrc.json +5 -0
- custom_nodes/rgthree-comfy/.pylintrc +633 -0
- custom_nodes/rgthree-comfy/.style.yapf +11 -0
- custom_nodes/rgthree-comfy/.tracking +257 -0
- custom_nodes/rgthree-comfy/LICENSE +21 -0
- custom_nodes/rgthree-comfy/README.md +434 -0
- custom_nodes/rgthree-comfy/__build__.py +151 -0
- custom_nodes/rgthree-comfy/__commit__.py +55 -0
- custom_nodes/rgthree-comfy/__init__.py +126 -0
- custom_nodes/rgthree-comfy/__update_comfy__.py +57 -0
- custom_nodes/rgthree-comfy/docs/rgthree_advanced.png +3 -0
- custom_nodes/rgthree-comfy/docs/rgthree_advanced_metadata.png +3 -0
- custom_nodes/rgthree-comfy/docs/rgthree_context.png +3 -0
- custom_nodes/rgthree-comfy/docs/rgthree_context_metadata.png +3 -0
- custom_nodes/rgthree-comfy/docs/rgthree_router.png +0 -0
- custom_nodes/rgthree-comfy/docs/rgthree_seed.png +0 -0
- custom_nodes/rgthree-comfy/package-lock.json +568 -0
- custom_nodes/rgthree-comfy/package.json +12 -0
- custom_nodes/rgthree-comfy/pnpm-lock.yaml +507 -0
- custom_nodes/rgthree-comfy/prestartup_script.py +5 -0
- custom_nodes/rgthree-comfy/py/__init__.py +0 -0
- custom_nodes/rgthree-comfy/py/any_switch.py +38 -0
- custom_nodes/rgthree-comfy/py/config.py +111 -0
- custom_nodes/rgthree-comfy/py/constants.py +11 -0
- custom_nodes/rgthree-comfy/py/context.py +33 -0
- custom_nodes/rgthree-comfy/py/context_big.py +31 -0
- custom_nodes/rgthree-comfy/py/context_merge.py +37 -0
- custom_nodes/rgthree-comfy/py/context_merge_big.py +16 -0
- custom_nodes/rgthree-comfy/py/context_switch.py +36 -0
- custom_nodes/rgthree-comfy/py/context_switch_big.py +16 -0
- custom_nodes/rgthree-comfy/py/context_utils.py +118 -0
- custom_nodes/rgthree-comfy/py/display_any.py +77 -0
- custom_nodes/rgthree-comfy/py/dynamic_context.py +56 -0
- custom_nodes/rgthree-comfy/py/dynamic_context_switch.py +39 -0
- custom_nodes/rgthree-comfy/py/image_comparer.py +42 -0
- custom_nodes/rgthree-comfy/py/image_inset_crop.py +93 -0
- custom_nodes/rgthree-comfy/py/image_or_latent_size.py +31 -0
- custom_nodes/rgthree-comfy/py/image_resize.py +117 -0
- custom_nodes/rgthree-comfy/py/ksampler_config.py +56 -0
- custom_nodes/rgthree-comfy/py/log.py +100 -0
- custom_nodes/rgthree-comfy/py/lora_stack.py +46 -0
- custom_nodes/rgthree-comfy/py/power_lora_loader.py +101 -0
- custom_nodes/rgthree-comfy/py/power_primitive.py +83 -0
- custom_nodes/rgthree-comfy/py/power_prompt.py +95 -0
- custom_nodes/rgthree-comfy/py/power_prompt_simple.py +42 -0
- custom_nodes/rgthree-comfy/py/power_prompt_utils.py +104 -0
- custom_nodes/rgthree-comfy/py/power_puter.py +842 -0
.gitattributes
CHANGED
|
@@ -15,9 +15,19 @@ models/unet/wan2.2_i2v_low_noise_14B_fp8_scaled.safetensors filter=lfs diff=lfs
|
|
| 15 |
models/vae/wan_2.1_vae.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 16 |
output/video/ComfyUI_00001_.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 17 |
user/comfyui.db filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
models/loras/DR34ML4Y_I2V_14B_HIGH_V2.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 19 |
models/loras/DR34ML4Y_I2V_14B_LOW_V2.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 20 |
models/loras/NSFW-22-H-e8.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 21 |
models/loras/NSFW-22-L-e8.safetensors filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
| 22 |
models/loras/wan22-m4crom4sti4-i2v-20epoc-high-k3nk.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 23 |
models/loras/wan22-m4crom4sti4-i2v-20epoc-low-k3nk.safetensors filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 15 |
models/vae/wan_2.1_vae.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 16 |
output/video/ComfyUI_00001_.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 17 |
user/comfyui.db filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
custom_nodes/rgthree-comfy/docs/rgthree_advanced.png filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
custom_nodes/rgthree-comfy/docs/rgthree_advanced_metadata.png filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
custom_nodes/rgthree-comfy/docs/rgthree_context.png filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
custom_nodes/rgthree-comfy/docs/rgthree_context_metadata.png filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
custom_nodes/rgthree-comfy/src_web/lib/tree-sitter-python.wasm filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
custom_nodes/rgthree-comfy/src_web/lib/tree-sitter.wasm filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
custom_nodes/rgthree-comfy/web/lib/tree-sitter-python.wasm filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
custom_nodes/rgthree-comfy/web/lib/tree-sitter.wasm filter=lfs diff=lfs merge=lfs -text
|
| 26 |
models/loras/DR34ML4Y_I2V_14B_HIGH_V2.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 27 |
models/loras/DR34ML4Y_I2V_14B_LOW_V2.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 28 |
models/loras/NSFW-22-H-e8.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 29 |
models/loras/NSFW-22-L-e8.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
models/loras/wan2.2-i2v-high-pov-insertion-v1.0.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
models/loras/wan2.2-i2v-low-pov-insertion-v1.0.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 32 |
models/loras/wan22-m4crom4sti4-i2v-20epoc-high-k3nk.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 33 |
models/loras/wan22-m4crom4sti4-i2v-20epoc-low-k3nk.safetensors filter=lfs diff=lfs merge=lfs -text
|
custom_nodes/rgthree-comfy/.github/workflows/publish_comfy_registry_action.yml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Publish to Comfy registry
|
| 2 |
+
on:
|
| 3 |
+
workflow_dispatch:
|
| 4 |
+
push:
|
| 5 |
+
branches:
|
| 6 |
+
- main
|
| 7 |
+
paths:
|
| 8 |
+
- "pyproject.toml"
|
| 9 |
+
|
| 10 |
+
jobs:
|
| 11 |
+
publish-node:
|
| 12 |
+
name: Publish Custom Node to registry
|
| 13 |
+
runs-on: ubuntu-latest
|
| 14 |
+
steps:
|
| 15 |
+
- name: Check out code
|
| 16 |
+
uses: actions/checkout@v4
|
| 17 |
+
- name: Publish Custom Node
|
| 18 |
+
uses: Comfy-Org/publish-node-action@main
|
| 19 |
+
with:
|
| 20 |
+
personal_access_token: ${{ secrets.COMFY_REGISTRY_ACCESS_TOKEN }}
|
custom_nodes/rgthree-comfy/.gitignore
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__
|
| 2 |
+
*.ini
|
| 3 |
+
wildcards/**
|
| 4 |
+
.vscode/
|
| 5 |
+
.idea/
|
| 6 |
+
node_modules/
|
| 7 |
+
rgthree_config.json
|
| 8 |
+
web/rgthree_config.js
|
| 9 |
+
web/comfyui/rgthree_config.js
|
| 10 |
+
userdata/
|
| 11 |
+
userdata/**
|
| 12 |
+
web/comfyui/testing/
|
| 13 |
+
web/comfyui/tests/
|
custom_nodes/rgthree-comfy/.prettierrc.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"printWidth": 100,
|
| 3 |
+
"bracketSpacing": false,
|
| 4 |
+
"bracketSameLine": true
|
| 5 |
+
}
|
custom_nodes/rgthree-comfy/.pylintrc
ADDED
|
@@ -0,0 +1,633 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[MAIN]
|
| 2 |
+
|
| 3 |
+
# Analyse import fallback blocks. This can be used to support both Python 2 and
|
| 4 |
+
# 3 compatible code, which means that the block might have code that exists
|
| 5 |
+
# only in one or another interpreter, leading to false positives when analysed.
|
| 6 |
+
analyse-fallback-blocks=no
|
| 7 |
+
|
| 8 |
+
# Clear in-memory caches upon conclusion of linting. Useful if running pylint
|
| 9 |
+
# in a server-like mode.
|
| 10 |
+
clear-cache-post-run=no
|
| 11 |
+
|
| 12 |
+
# Load and enable all available extensions. Use --list-extensions to see a list
|
| 13 |
+
# all available extensions.
|
| 14 |
+
#enable-all-extensions=
|
| 15 |
+
|
| 16 |
+
# In error mode, messages with a category besides ERROR or FATAL are
|
| 17 |
+
# suppressed, and no reports are done by default. Error mode is compatible with
|
| 18 |
+
# disabling specific errors.
|
| 19 |
+
#errors-only=
|
| 20 |
+
|
| 21 |
+
# Always return a 0 (non-error) status code, even if lint errors are found.
|
| 22 |
+
# This is primarily useful in continuous integration scripts.
|
| 23 |
+
#exit-zero=
|
| 24 |
+
|
| 25 |
+
# A comma-separated list of package or module names from where C extensions may
|
| 26 |
+
# be loaded. Extensions are loading into the active Python interpreter and may
|
| 27 |
+
# run arbitrary code.
|
| 28 |
+
extension-pkg-allow-list=
|
| 29 |
+
|
| 30 |
+
# A comma-separated list of package or module names from where C extensions may
|
| 31 |
+
# be loaded. Extensions are loading into the active Python interpreter and may
|
| 32 |
+
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
|
| 33 |
+
# for backward compatibility.)
|
| 34 |
+
extension-pkg-whitelist=
|
| 35 |
+
|
| 36 |
+
# Return non-zero exit code if any of these messages/categories are detected,
|
| 37 |
+
# even if score is above --fail-under value. Syntax same as enable. Messages
|
| 38 |
+
# specified are enabled, while categories only check already-enabled messages.
|
| 39 |
+
fail-on=
|
| 40 |
+
|
| 41 |
+
# Specify a score threshold under which the program will exit with error.
|
| 42 |
+
fail-under=10
|
| 43 |
+
|
| 44 |
+
# Interpret the stdin as a python script, whose filename needs to be passed as
|
| 45 |
+
# the module_or_package argument.
|
| 46 |
+
#from-stdin=
|
| 47 |
+
|
| 48 |
+
# Files or directories to be skipped. They should be base names, not paths.
|
| 49 |
+
ignore=CVS
|
| 50 |
+
|
| 51 |
+
# Add files or directories matching the regular expressions patterns to the
|
| 52 |
+
# ignore-list. The regex matches against paths and can be in Posix or Windows
|
| 53 |
+
# format. Because '\\' represents the directory delimiter on Windows systems,
|
| 54 |
+
# it can't be used as an escape character.
|
| 55 |
+
ignore-paths=
|
| 56 |
+
|
| 57 |
+
# Files or directories matching the regular expression patterns are skipped.
|
| 58 |
+
# The regex matches against base names, not paths. The default value ignores
|
| 59 |
+
# Emacs file locks
|
| 60 |
+
ignore-patterns=^\.#
|
| 61 |
+
|
| 62 |
+
# List of module names for which member attributes should not be checked
|
| 63 |
+
# (useful for modules/projects where namespaces are manipulated during runtime
|
| 64 |
+
# and thus existing member attributes cannot be deduced by static analysis). It
|
| 65 |
+
# supports qualified module names, as well as Unix pattern matching.
|
| 66 |
+
ignored-modules=
|
| 67 |
+
|
| 68 |
+
# Python code to execute, usually for sys.path manipulation such as
|
| 69 |
+
# pygtk.require().
|
| 70 |
+
#init-hook=
|
| 71 |
+
|
| 72 |
+
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
|
| 73 |
+
# number of processors available to use, and will cap the count on Windows to
|
| 74 |
+
# avoid hangs.
|
| 75 |
+
jobs=1
|
| 76 |
+
|
| 77 |
+
# Control the amount of potential inferred values when inferring a single
|
| 78 |
+
# object. This can help the performance when dealing with large functions or
|
| 79 |
+
# complex, nested conditions.
|
| 80 |
+
limit-inference-results=100
|
| 81 |
+
|
| 82 |
+
# List of plugins (as comma separated values of python module names) to load,
|
| 83 |
+
# usually to register additional checkers.
|
| 84 |
+
load-plugins=
|
| 85 |
+
|
| 86 |
+
# Pickle collected data for later comparisons.
|
| 87 |
+
persistent=yes
|
| 88 |
+
|
| 89 |
+
# Minimum Python version to use for version dependent checks. Will default to
|
| 90 |
+
# the version used to run pylint.
|
| 91 |
+
py-version=3.10
|
| 92 |
+
|
| 93 |
+
# Discover python modules and packages in the file system subtree.
|
| 94 |
+
recursive=no
|
| 95 |
+
|
| 96 |
+
# Add paths to the list of the source roots. Supports globbing patterns. The
|
| 97 |
+
# source root is an absolute path or a path relative to the current working
|
| 98 |
+
# directory used to determine a package namespace for modules located under the
|
| 99 |
+
# source root.
|
| 100 |
+
source-roots=
|
| 101 |
+
|
| 102 |
+
# When enabled, pylint would attempt to guess common misconfiguration and emit
|
| 103 |
+
# user-friendly hints instead of false-positive error messages.
|
| 104 |
+
suggestion-mode=yes
|
| 105 |
+
|
| 106 |
+
# Allow loading of arbitrary C extensions. Extensions are imported into the
|
| 107 |
+
# active Python interpreter and may run arbitrary code.
|
| 108 |
+
unsafe-load-any-extension=no
|
| 109 |
+
|
| 110 |
+
# In verbose mode, extra non-checker-related info will be displayed.
|
| 111 |
+
#verbose=
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
[BASIC]
|
| 115 |
+
|
| 116 |
+
# Naming style matching correct argument names.
|
| 117 |
+
argument-naming-style=snake_case
|
| 118 |
+
|
| 119 |
+
# Regular expression matching correct argument names. Overrides argument-
|
| 120 |
+
# naming-style. If left empty, argument names will be checked with the set
|
| 121 |
+
# naming style.
|
| 122 |
+
#argument-rgx=
|
| 123 |
+
|
| 124 |
+
# Naming style matching correct attribute names.
|
| 125 |
+
attr-naming-style=snake_case
|
| 126 |
+
|
| 127 |
+
# Regular expression matching correct attribute names. Overrides attr-naming-
|
| 128 |
+
# style. If left empty, attribute names will be checked with the set naming
|
| 129 |
+
# style.
|
| 130 |
+
#attr-rgx=
|
| 131 |
+
|
| 132 |
+
# Bad variable names which should always be refused, separated by a comma.
|
| 133 |
+
bad-names=foo,
|
| 134 |
+
bar,
|
| 135 |
+
baz,
|
| 136 |
+
toto,
|
| 137 |
+
tutu,
|
| 138 |
+
tata
|
| 139 |
+
|
| 140 |
+
# Bad variable names regexes, separated by a comma. If names match any regex,
|
| 141 |
+
# they will always be refused
|
| 142 |
+
bad-names-rgxs=
|
| 143 |
+
|
| 144 |
+
# Naming style matching correct class attribute names.
|
| 145 |
+
class-attribute-naming-style=any
|
| 146 |
+
|
| 147 |
+
# Regular expression matching correct class attribute names. Overrides class-
|
| 148 |
+
# attribute-naming-style. If left empty, class attribute names will be checked
|
| 149 |
+
# with the set naming style.
|
| 150 |
+
#class-attribute-rgx=
|
| 151 |
+
|
| 152 |
+
# Naming style matching correct class constant names.
|
| 153 |
+
class-const-naming-style=UPPER_CASE
|
| 154 |
+
|
| 155 |
+
# Regular expression matching correct class constant names. Overrides class-
|
| 156 |
+
# const-naming-style. If left empty, class constant names will be checked with
|
| 157 |
+
# the set naming style.
|
| 158 |
+
#class-const-rgx=
|
| 159 |
+
|
| 160 |
+
# Naming style matching correct class names.
|
| 161 |
+
class-naming-style=PascalCase
|
| 162 |
+
|
| 163 |
+
# Regular expression matching correct class names. Overrides class-naming-
|
| 164 |
+
# style. If left empty, class names will be checked with the set naming style.
|
| 165 |
+
#class-rgx=
|
| 166 |
+
|
| 167 |
+
# Naming style matching correct constant names.
|
| 168 |
+
const-naming-style=UPPER_CASE
|
| 169 |
+
|
| 170 |
+
# Regular expression matching correct constant names. Overrides const-naming-
|
| 171 |
+
# style. If left empty, constant names will be checked with the set naming
|
| 172 |
+
# style.
|
| 173 |
+
#const-rgx=
|
| 174 |
+
|
| 175 |
+
# Minimum line length for functions/classes that require docstrings, shorter
|
| 176 |
+
# ones are exempt.
|
| 177 |
+
docstring-min-length=-1
|
| 178 |
+
|
| 179 |
+
# Naming style matching correct function names.
|
| 180 |
+
function-naming-style=snake_case
|
| 181 |
+
|
| 182 |
+
# Regular expression matching correct function names. Overrides function-
|
| 183 |
+
# naming-style. If left empty, function names will be checked with the set
|
| 184 |
+
# naming style.
|
| 185 |
+
#function-rgx=
|
| 186 |
+
|
| 187 |
+
# Good variable names which should always be accepted, separated by a comma.
|
| 188 |
+
good-names=i,
|
| 189 |
+
j,
|
| 190 |
+
k,
|
| 191 |
+
ex,
|
| 192 |
+
Run,
|
| 193 |
+
_
|
| 194 |
+
|
| 195 |
+
# Good variable names regexes, separated by a comma. If names match any regex,
|
| 196 |
+
# they will always be accepted
|
| 197 |
+
good-names-rgxs=
|
| 198 |
+
|
| 199 |
+
# Include a hint for the correct naming format with invalid-name.
|
| 200 |
+
include-naming-hint=no
|
| 201 |
+
|
| 202 |
+
# Naming style matching correct inline iteration names.
|
| 203 |
+
inlinevar-naming-style=any
|
| 204 |
+
|
| 205 |
+
# Regular expression matching correct inline iteration names. Overrides
|
| 206 |
+
# inlinevar-naming-style. If left empty, inline iteration names will be checked
|
| 207 |
+
# with the set naming style.
|
| 208 |
+
#inlinevar-rgx=
|
| 209 |
+
|
| 210 |
+
# Naming style matching correct method names.
|
| 211 |
+
method-naming-style=snake_case
|
| 212 |
+
|
| 213 |
+
# Regular expression matching correct method names. Overrides method-naming-
|
| 214 |
+
# style. If left empty, method names will be checked with the set naming style.
|
| 215 |
+
#method-rgx=
|
| 216 |
+
|
| 217 |
+
# Naming style matching correct module names.
|
| 218 |
+
module-naming-style=snake_case
|
| 219 |
+
|
| 220 |
+
# Regular expression matching correct module names. Overrides module-naming-
|
| 221 |
+
# style. If left empty, module names will be checked with the set naming style.
|
| 222 |
+
#module-rgx=
|
| 223 |
+
|
| 224 |
+
# Colon-delimited sets of names that determine each other's naming style when
|
| 225 |
+
# the name regexes allow several styles.
|
| 226 |
+
name-group=
|
| 227 |
+
|
| 228 |
+
# Regular expression which should only match function or class names that do
|
| 229 |
+
# not require a docstring.
|
| 230 |
+
no-docstring-rgx=^_
|
| 231 |
+
|
| 232 |
+
# List of decorators that produce properties, such as abc.abstractproperty. Add
|
| 233 |
+
# to this list to register other decorators that produce valid properties.
|
| 234 |
+
# These decorators are taken in consideration only for invalid-name.
|
| 235 |
+
property-classes=abc.abstractproperty
|
| 236 |
+
|
| 237 |
+
# Regular expression matching correct type alias names. If left empty, type
|
| 238 |
+
# alias names will be checked with the set naming style.
|
| 239 |
+
#typealias-rgx=
|
| 240 |
+
|
| 241 |
+
# Regular expression matching correct type variable names. If left empty, type
|
| 242 |
+
# variable names will be checked with the set naming style.
|
| 243 |
+
#typevar-rgx=
|
| 244 |
+
|
| 245 |
+
# Naming style matching correct variable names.
|
| 246 |
+
variable-naming-style=snake_case
|
| 247 |
+
|
| 248 |
+
# Regular expression matching correct variable names. Overrides variable-
|
| 249 |
+
# naming-style. If left empty, variable names will be checked with the set
|
| 250 |
+
# naming style.
|
| 251 |
+
#variable-rgx=
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
[CLASSES]
|
| 255 |
+
|
| 256 |
+
# Warn about protected attribute access inside special methods
|
| 257 |
+
check-protected-access-in-special-methods=no
|
| 258 |
+
|
| 259 |
+
# List of method names used to declare (i.e. assign) instance attributes.
|
| 260 |
+
defining-attr-methods=__init__,
|
| 261 |
+
__new__,
|
| 262 |
+
setUp,
|
| 263 |
+
asyncSetUp,
|
| 264 |
+
__post_init__
|
| 265 |
+
|
| 266 |
+
# List of member names, which should be excluded from the protected access
|
| 267 |
+
# warning.
|
| 268 |
+
exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit
|
| 269 |
+
|
| 270 |
+
# List of valid names for the first argument in a class method.
|
| 271 |
+
valid-classmethod-first-arg=cls
|
| 272 |
+
|
| 273 |
+
# List of valid names for the first argument in a metaclass class method.
|
| 274 |
+
valid-metaclass-classmethod-first-arg=mcs
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
[DESIGN]
|
| 278 |
+
|
| 279 |
+
# List of regular expressions of class ancestor names to ignore when counting
|
| 280 |
+
# public methods (see R0903)
|
| 281 |
+
exclude-too-few-public-methods=
|
| 282 |
+
|
| 283 |
+
# List of qualified class names to ignore when counting class parents (see
|
| 284 |
+
# R0901)
|
| 285 |
+
ignored-parents=
|
| 286 |
+
|
| 287 |
+
# Maximum number of arguments for function / method.
|
| 288 |
+
max-args=5
|
| 289 |
+
|
| 290 |
+
# Maximum number of attributes for a class (see R0902).
|
| 291 |
+
max-attributes=7
|
| 292 |
+
|
| 293 |
+
# Maximum number of boolean expressions in an if statement (see R0916).
|
| 294 |
+
max-bool-expr=5
|
| 295 |
+
|
| 296 |
+
# Maximum number of branch for function / method body.
|
| 297 |
+
max-branches=12
|
| 298 |
+
|
| 299 |
+
# Maximum number of locals for function / method body.
|
| 300 |
+
max-locals=15
|
| 301 |
+
|
| 302 |
+
# Maximum number of parents for a class (see R0901).
|
| 303 |
+
max-parents=7
|
| 304 |
+
|
| 305 |
+
# Maximum number of public methods for a class (see R0904).
|
| 306 |
+
max-public-methods=20
|
| 307 |
+
|
| 308 |
+
# Maximum number of return / yield for function / method body.
|
| 309 |
+
max-returns=6
|
| 310 |
+
|
| 311 |
+
# Maximum number of statements in function / method body.
|
| 312 |
+
max-statements=50
|
| 313 |
+
|
| 314 |
+
# Minimum number of public methods for a class (see R0903).
|
| 315 |
+
min-public-methods=2
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
[EXCEPTIONS]
|
| 319 |
+
|
| 320 |
+
# Exceptions that will emit a warning when caught.
|
| 321 |
+
overgeneral-exceptions=builtins.BaseException,builtins.Exception
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
[FORMAT]
|
| 325 |
+
|
| 326 |
+
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
|
| 327 |
+
expected-line-ending-format=
|
| 328 |
+
|
| 329 |
+
# Regexp for a line that is allowed to be longer than the limit.
|
| 330 |
+
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
|
| 331 |
+
|
| 332 |
+
# Number of spaces of indent required inside a hanging or continued line.
|
| 333 |
+
indent-after-paren=4
|
| 334 |
+
|
| 335 |
+
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
| 336 |
+
# tab).
|
| 337 |
+
indent-string=' '
|
| 338 |
+
|
| 339 |
+
# Maximum number of characters on a single line.
|
| 340 |
+
max-line-length=100
|
| 341 |
+
|
| 342 |
+
# Maximum number of lines in a module.
|
| 343 |
+
max-module-lines=1000
|
| 344 |
+
|
| 345 |
+
# Allow the body of a class to be on the same line as the declaration if body
|
| 346 |
+
# contains single statement.
|
| 347 |
+
single-line-class-stmt=no
|
| 348 |
+
|
| 349 |
+
# Allow the body of an if to be on the same line as the test if there is no
|
| 350 |
+
# else.
|
| 351 |
+
single-line-if-stmt=no
|
| 352 |
+
|
| 353 |
+
|
| 354 |
+
[IMPORTS]
|
| 355 |
+
|
| 356 |
+
# List of modules that can be imported at any level, not just the top level
|
| 357 |
+
# one.
|
| 358 |
+
allow-any-import-level=
|
| 359 |
+
|
| 360 |
+
# Allow explicit reexports by alias from a package __init__.
|
| 361 |
+
allow-reexport-from-package=no
|
| 362 |
+
|
| 363 |
+
# Allow wildcard imports from modules that define __all__.
|
| 364 |
+
allow-wildcard-with-all=no
|
| 365 |
+
|
| 366 |
+
# Deprecated modules which should not be used, separated by a comma.
|
| 367 |
+
deprecated-modules=
|
| 368 |
+
|
| 369 |
+
# Output a graph (.gv or any supported image format) of external dependencies
|
| 370 |
+
# to the given file (report RP0402 must not be disabled).
|
| 371 |
+
ext-import-graph=
|
| 372 |
+
|
| 373 |
+
# Output a graph (.gv or any supported image format) of all (i.e. internal and
|
| 374 |
+
# external) dependencies to the given file (report RP0402 must not be
|
| 375 |
+
# disabled).
|
| 376 |
+
import-graph=
|
| 377 |
+
|
| 378 |
+
# Output a graph (.gv or any supported image format) of internal dependencies
|
| 379 |
+
# to the given file (report RP0402 must not be disabled).
|
| 380 |
+
int-import-graph=
|
| 381 |
+
|
| 382 |
+
# Force import order to recognize a module as part of the standard
|
| 383 |
+
# compatibility libraries.
|
| 384 |
+
known-standard-library=
|
| 385 |
+
|
| 386 |
+
# Force import order to recognize a module as part of a third party library.
|
| 387 |
+
known-third-party=enchant
|
| 388 |
+
|
| 389 |
+
# Couples of modules and preferred modules, separated by a comma.
|
| 390 |
+
preferred-modules=
|
| 391 |
+
|
| 392 |
+
|
| 393 |
+
[LOGGING]
|
| 394 |
+
|
| 395 |
+
# The type of string formatting that logging methods do. `old` means using %
|
| 396 |
+
# formatting, `new` is for `{}` formatting.
|
| 397 |
+
logging-format-style=old
|
| 398 |
+
|
| 399 |
+
# Logging modules to check that the string format arguments are in logging
|
| 400 |
+
# function parameter format.
|
| 401 |
+
logging-modules=logging
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
[MESSAGES CONTROL]
|
| 405 |
+
|
| 406 |
+
# Only show warnings with the listed confidence levels. Leave empty to show
|
| 407 |
+
# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE,
|
| 408 |
+
# UNDEFINED.
|
| 409 |
+
confidence=HIGH,
|
| 410 |
+
CONTROL_FLOW,
|
| 411 |
+
INFERENCE,
|
| 412 |
+
INFERENCE_FAILURE,
|
| 413 |
+
UNDEFINED
|
| 414 |
+
|
| 415 |
+
# Disable the message, report, category or checker with the given id(s). You
|
| 416 |
+
# can either give multiple identifiers separated by comma (,) or put this
|
| 417 |
+
# option multiple times (only on the command line, not in the configuration
|
| 418 |
+
# file where it should appear only once). You can also use "--disable=all" to
|
| 419 |
+
# disable everything first and then re-enable specific checks. For example, if
|
| 420 |
+
# you want to run only the similarities checker, you can use "--disable=all
|
| 421 |
+
# --enable=similarities". If you want to run only the classes checker, but have
|
| 422 |
+
# no Warning level messages displayed, use "--disable=all --enable=classes
|
| 423 |
+
# --disable=W".
|
| 424 |
+
disable=raw-checker-failed,
|
| 425 |
+
bad-inline-option,
|
| 426 |
+
locally-disabled,
|
| 427 |
+
file-ignored,
|
| 428 |
+
suppressed-message,
|
| 429 |
+
useless-suppression,
|
| 430 |
+
deprecated-pragma,
|
| 431 |
+
use-symbolic-message-instead,
|
| 432 |
+
C0103, # invalid-name :: constant case
|
| 433 |
+
W0603 # global-statements
|
| 434 |
+
|
| 435 |
+
# Enable the message, report, category or checker with the given id(s). You can
|
| 436 |
+
# either give multiple identifier separated by comma (,) or put this option
|
| 437 |
+
# multiple time (only on the command line, not in the configuration file where
|
| 438 |
+
# it should appear only once). See also the "--disable" option for examples.
|
| 439 |
+
enable=c-extension-no-member
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
[METHOD_ARGS]
|
| 443 |
+
|
| 444 |
+
# List of qualified names (i.e., library.method) which require a timeout
|
| 445 |
+
# parameter e.g. 'requests.api.get,requests.api.post'
|
| 446 |
+
timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request
|
| 447 |
+
|
| 448 |
+
|
| 449 |
+
[MISCELLANEOUS]
|
| 450 |
+
|
| 451 |
+
# List of note tags to take in consideration, separated by a comma.
|
| 452 |
+
notes=FIXME,
|
| 453 |
+
XXX,
|
| 454 |
+
TODO
|
| 455 |
+
|
| 456 |
+
# Regular expression of note tags to take in consideration.
|
| 457 |
+
notes-rgx=
|
| 458 |
+
|
| 459 |
+
|
| 460 |
+
[REFACTORING]
|
| 461 |
+
|
| 462 |
+
# Maximum number of nested blocks for function / method body
|
| 463 |
+
max-nested-blocks=5
|
| 464 |
+
|
| 465 |
+
# Complete name of functions that never returns. When checking for
|
| 466 |
+
# inconsistent-return-statements if a never returning function is called then
|
| 467 |
+
# it will be considered as an explicit return statement and no message will be
|
| 468 |
+
# printed.
|
| 469 |
+
never-returning-functions=sys.exit,argparse.parse_error
|
| 470 |
+
|
| 471 |
+
|
| 472 |
+
[REPORTS]
|
| 473 |
+
|
| 474 |
+
# Python expression which should return a score less than or equal to 10. You
|
| 475 |
+
# have access to the variables 'fatal', 'error', 'warning', 'refactor',
|
| 476 |
+
# 'convention', and 'info' which contain the number of messages in each
|
| 477 |
+
# category, as well as 'statement' which is the total number of statements
|
| 478 |
+
# analyzed. This score is used by the global evaluation report (RP0004).
|
| 479 |
+
evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
|
| 480 |
+
|
| 481 |
+
# Template used to display messages. This is a python new-style format string
|
| 482 |
+
# used to format the message information. See doc for all details.
|
| 483 |
+
msg-template=
|
| 484 |
+
|
| 485 |
+
# Set the output format. Available formats are text, parseable, colorized, json
|
| 486 |
+
# and msvs (visual studio). You can also give a reporter class, e.g.
|
| 487 |
+
# mypackage.mymodule.MyReporterClass.
|
| 488 |
+
#output-format=
|
| 489 |
+
|
| 490 |
+
# Tells whether to display a full report or only the messages.
|
| 491 |
+
reports=no
|
| 492 |
+
|
| 493 |
+
# Activate the evaluation score.
|
| 494 |
+
score=yes
|
| 495 |
+
|
| 496 |
+
|
| 497 |
+
[SIMILARITIES]
|
| 498 |
+
|
| 499 |
+
# Comments are removed from the similarity computation
|
| 500 |
+
ignore-comments=yes
|
| 501 |
+
|
| 502 |
+
# Docstrings are removed from the similarity computation
|
| 503 |
+
ignore-docstrings=yes
|
| 504 |
+
|
| 505 |
+
# Imports are removed from the similarity computation
|
| 506 |
+
ignore-imports=yes
|
| 507 |
+
|
| 508 |
+
# Signatures are removed from the similarity computation
|
| 509 |
+
ignore-signatures=yes
|
| 510 |
+
|
| 511 |
+
# Minimum lines number of a similarity.
|
| 512 |
+
min-similarity-lines=4
|
| 513 |
+
|
| 514 |
+
|
| 515 |
+
[SPELLING]
|
| 516 |
+
|
| 517 |
+
# Limits count of emitted suggestions for spelling mistakes.
|
| 518 |
+
max-spelling-suggestions=4
|
| 519 |
+
|
| 520 |
+
# Spelling dictionary name. No available dictionaries : You need to install
|
| 521 |
+
# both the python package and the system dependency for enchant to work..
|
| 522 |
+
spelling-dict=
|
| 523 |
+
|
| 524 |
+
# List of comma separated words that should be considered directives if they
|
| 525 |
+
# appear at the beginning of a comment and should not be checked.
|
| 526 |
+
spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
|
| 527 |
+
|
| 528 |
+
# List of comma separated words that should not be checked.
|
| 529 |
+
spelling-ignore-words=
|
| 530 |
+
|
| 531 |
+
# A path to a file that contains the private dictionary; one word per line.
|
| 532 |
+
spelling-private-dict-file=
|
| 533 |
+
|
| 534 |
+
# Tells whether to store unknown words to the private dictionary (see the
|
| 535 |
+
# --spelling-private-dict-file option) instead of raising a message.
|
| 536 |
+
spelling-store-unknown-words=no
|
| 537 |
+
|
| 538 |
+
|
| 539 |
+
[STRING]
|
| 540 |
+
|
| 541 |
+
# This flag controls whether inconsistent-quotes generates a warning when the
|
| 542 |
+
# character used as a quote delimiter is used inconsistently within a module.
|
| 543 |
+
check-quote-consistency=no
|
| 544 |
+
|
| 545 |
+
# This flag controls whether the implicit-str-concat should generate a warning
|
| 546 |
+
# on implicit string concatenation in sequences defined over several lines.
|
| 547 |
+
check-str-concat-over-line-jumps=no
|
| 548 |
+
|
| 549 |
+
|
| 550 |
+
[TYPECHECK]
|
| 551 |
+
|
| 552 |
+
# List of decorators that produce context managers, such as
|
| 553 |
+
# contextlib.contextmanager. Add to this list to register other decorators that
|
| 554 |
+
# produce valid context managers.
|
| 555 |
+
contextmanager-decorators=contextlib.contextmanager
|
| 556 |
+
|
| 557 |
+
# List of members which are set dynamically and missed by pylint inference
|
| 558 |
+
# system, and so shouldn't trigger E1101 when accessed. Python regular
|
| 559 |
+
# expressions are accepted.
|
| 560 |
+
generated-members=
|
| 561 |
+
|
| 562 |
+
# Tells whether to warn about missing members when the owner of the attribute
|
| 563 |
+
# is inferred to be None.
|
| 564 |
+
ignore-none=yes
|
| 565 |
+
|
| 566 |
+
# This flag controls whether pylint should warn about no-member and similar
|
| 567 |
+
# checks whenever an opaque object is returned when inferring. The inference
|
| 568 |
+
# can return multiple potential results while evaluating a Python object, but
|
| 569 |
+
# some branches might not be evaluated, which results in partial inference. In
|
| 570 |
+
# that case, it might be useful to still emit no-member and other checks for
|
| 571 |
+
# the rest of the inferred objects.
|
| 572 |
+
ignore-on-opaque-inference=yes
|
| 573 |
+
|
| 574 |
+
# List of symbolic message names to ignore for Mixin members.
|
| 575 |
+
ignored-checks-for-mixins=no-member,
|
| 576 |
+
not-async-context-manager,
|
| 577 |
+
not-context-manager,
|
| 578 |
+
attribute-defined-outside-init
|
| 579 |
+
|
| 580 |
+
# List of class names for which member attributes should not be checked (useful
|
| 581 |
+
# for classes with dynamically set attributes). This supports the use of
|
| 582 |
+
# qualified names.
|
| 583 |
+
ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace
|
| 584 |
+
|
| 585 |
+
# Show a hint with possible names when a member name was not found. The aspect
|
| 586 |
+
# of finding the hint is based on edit distance.
|
| 587 |
+
missing-member-hint=yes
|
| 588 |
+
|
| 589 |
+
# The minimum edit distance a name should have in order to be considered a
|
| 590 |
+
# similar match for a missing member name.
|
| 591 |
+
missing-member-hint-distance=1
|
| 592 |
+
|
| 593 |
+
# The total number of similar names that should be taken in consideration when
|
| 594 |
+
# showing a hint for a missing member.
|
| 595 |
+
missing-member-max-choices=1
|
| 596 |
+
|
| 597 |
+
# Regex pattern to define which classes are considered mixins.
|
| 598 |
+
mixin-class-rgx=.*[Mm]ixin
|
| 599 |
+
|
| 600 |
+
# List of decorators that change the signature of a decorated function.
|
| 601 |
+
signature-mutators=
|
| 602 |
+
|
| 603 |
+
|
| 604 |
+
[VARIABLES]
|
| 605 |
+
|
| 606 |
+
# List of additional names supposed to be defined in builtins. Remember that
|
| 607 |
+
# you should avoid defining new builtins when possible.
|
| 608 |
+
additional-builtins=
|
| 609 |
+
|
| 610 |
+
# Tells whether unused global variables should be treated as a violation.
|
| 611 |
+
allow-global-unused-variables=yes
|
| 612 |
+
|
| 613 |
+
# List of names allowed to shadow builtins
|
| 614 |
+
allowed-redefined-builtins=
|
| 615 |
+
|
| 616 |
+
# List of strings which can identify a callback function by name. A callback
|
| 617 |
+
# name must start or end with one of those strings.
|
| 618 |
+
callbacks=cb_,
|
| 619 |
+
_cb
|
| 620 |
+
|
| 621 |
+
# A regular expression matching the name of dummy variables (i.e. expected to
|
| 622 |
+
# not be used).
|
| 623 |
+
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
|
| 624 |
+
|
| 625 |
+
# Argument names that match this expression will be ignored.
|
| 626 |
+
ignored-argument-names=_.*|^ignored_|^unused_
|
| 627 |
+
|
| 628 |
+
# Tells whether we should check for unused import in __init__ files.
|
| 629 |
+
init-import=no
|
| 630 |
+
|
| 631 |
+
# List of qualified module names which can have objects that can redefine
|
| 632 |
+
# builtins.
|
| 633 |
+
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
|
custom_nodes/rgthree-comfy/.style.yapf
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# https://github.com/google/yapf
|
| 2 |
+
[style]
|
| 3 |
+
based_on_style = google
|
| 4 |
+
INDENT_WIDTH = 2
|
| 5 |
+
CONTINUATION_INDENT_WIDTH = 2
|
| 6 |
+
COLUMN_LIMIT = 100
|
| 7 |
+
DISABLE_ENDING_COMMA_HEURISTIC = true
|
| 8 |
+
DEDENT_CLOSING_BRACKETS = true
|
| 9 |
+
FORCE_MULTILINE_DICT = false
|
| 10 |
+
coalesce_brackets=True
|
| 11 |
+
ALLOW_SPLIT_BEFORE_DICT_VALUE = false
|
custom_nodes/rgthree-comfy/.tracking
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.github/workflows/publish_comfy_registry_action.yml
|
| 2 |
+
.gitignore
|
| 3 |
+
.prettierrc.json
|
| 4 |
+
.pylintrc
|
| 5 |
+
.style.yapf
|
| 6 |
+
LICENSE
|
| 7 |
+
README.md
|
| 8 |
+
__build__.py
|
| 9 |
+
__commit__.py
|
| 10 |
+
__init__.py
|
| 11 |
+
__update_comfy__.py
|
| 12 |
+
docs/rgthree_advanced.png
|
| 13 |
+
docs/rgthree_advanced_metadata.png
|
| 14 |
+
docs/rgthree_context.png
|
| 15 |
+
docs/rgthree_context_metadata.png
|
| 16 |
+
docs/rgthree_router.png
|
| 17 |
+
docs/rgthree_seed.png
|
| 18 |
+
package-lock.json
|
| 19 |
+
package.json
|
| 20 |
+
pnpm-lock.yaml
|
| 21 |
+
prestartup_script.py
|
| 22 |
+
py/__init__.py
|
| 23 |
+
py/any_switch.py
|
| 24 |
+
py/config.py
|
| 25 |
+
py/constants.py
|
| 26 |
+
py/context.py
|
| 27 |
+
py/context_big.py
|
| 28 |
+
py/context_merge.py
|
| 29 |
+
py/context_merge_big.py
|
| 30 |
+
py/context_switch.py
|
| 31 |
+
py/context_switch_big.py
|
| 32 |
+
py/context_utils.py
|
| 33 |
+
py/display_any.py
|
| 34 |
+
py/dynamic_context.py
|
| 35 |
+
py/dynamic_context_switch.py
|
| 36 |
+
py/image_comparer.py
|
| 37 |
+
py/image_inset_crop.py
|
| 38 |
+
py/image_or_latent_size.py
|
| 39 |
+
py/image_resize.py
|
| 40 |
+
py/ksampler_config.py
|
| 41 |
+
py/log.py
|
| 42 |
+
py/lora_stack.py
|
| 43 |
+
py/power_lora_loader.py
|
| 44 |
+
py/power_primitive.py
|
| 45 |
+
py/power_prompt.py
|
| 46 |
+
py/power_prompt_simple.py
|
| 47 |
+
py/power_prompt_utils.py
|
| 48 |
+
py/power_puter.py
|
| 49 |
+
py/pyproject.py
|
| 50 |
+
py/sdxl_empty_latent_image.py
|
| 51 |
+
py/sdxl_power_prompt_postive.py
|
| 52 |
+
py/sdxl_power_prompt_simple.py
|
| 53 |
+
py/seed.py
|
| 54 |
+
py/server/rgthree_server.py
|
| 55 |
+
py/server/routes_config.py
|
| 56 |
+
py/server/routes_model_info.py
|
| 57 |
+
py/server/utils_info.py
|
| 58 |
+
py/server/utils_server.py
|
| 59 |
+
py/utils.py
|
| 60 |
+
py/utils_userdata.py
|
| 61 |
+
pyproject.toml
|
| 62 |
+
requirements.txt
|
| 63 |
+
rgthree_config.json.default
|
| 64 |
+
src_web/comfyui/any_switch.ts
|
| 65 |
+
src_web/comfyui/base_any_input_connected_node.ts
|
| 66 |
+
src_web/comfyui/base_node.ts
|
| 67 |
+
src_web/comfyui/base_node_collector.ts
|
| 68 |
+
src_web/comfyui/base_node_mode_changer.ts
|
| 69 |
+
src_web/comfyui/base_power_prompt.ts
|
| 70 |
+
src_web/comfyui/bookmark.ts
|
| 71 |
+
src_web/comfyui/bypasser.ts
|
| 72 |
+
src_web/comfyui/comfy_ui_bar.ts
|
| 73 |
+
src_web/comfyui/config.ts
|
| 74 |
+
src_web/comfyui/constants.ts
|
| 75 |
+
src_web/comfyui/context.ts
|
| 76 |
+
src_web/comfyui/dialog_info.ts
|
| 77 |
+
src_web/comfyui/display_any.ts
|
| 78 |
+
src_web/comfyui/dynamic_context.ts
|
| 79 |
+
src_web/comfyui/dynamic_context_base.ts
|
| 80 |
+
src_web/comfyui/dynamic_context_switch.ts
|
| 81 |
+
src_web/comfyui/fast_actions_button.ts
|
| 82 |
+
src_web/comfyui/fast_groups_bypasser.ts
|
| 83 |
+
src_web/comfyui/fast_groups_muter.ts
|
| 84 |
+
src_web/comfyui/feature_group_fast_toggle.ts
|
| 85 |
+
src_web/comfyui/feature_import_individual_nodes.ts
|
| 86 |
+
src_web/comfyui/image_comparer.ts
|
| 87 |
+
src_web/comfyui/image_inset_crop.ts
|
| 88 |
+
src_web/comfyui/image_or_latent_size.ts
|
| 89 |
+
src_web/comfyui/label.ts
|
| 90 |
+
src_web/comfyui/menu_auto_nest.ts
|
| 91 |
+
src_web/comfyui/menu_copy_image.ts
|
| 92 |
+
src_web/comfyui/menu_queue_node.ts
|
| 93 |
+
src_web/comfyui/muter.ts
|
| 94 |
+
src_web/comfyui/node_collector.ts
|
| 95 |
+
src_web/comfyui/node_mode_relay.ts
|
| 96 |
+
src_web/comfyui/node_mode_repeater.ts
|
| 97 |
+
src_web/comfyui/power_conductor.ts
|
| 98 |
+
src_web/comfyui/power_lora_loader.ts
|
| 99 |
+
src_web/comfyui/power_primitive.ts
|
| 100 |
+
src_web/comfyui/power_prompt.ts
|
| 101 |
+
src_web/comfyui/power_puter.ts
|
| 102 |
+
src_web/comfyui/random_unmuter.ts
|
| 103 |
+
src_web/comfyui/reroute.ts
|
| 104 |
+
src_web/comfyui/rgthree.scss
|
| 105 |
+
src_web/comfyui/rgthree.ts
|
| 106 |
+
src_web/comfyui/seed.ts
|
| 107 |
+
src_web/comfyui/services/bookmarks_services.ts
|
| 108 |
+
src_web/comfyui/services/config_service.ts
|
| 109 |
+
src_web/comfyui/services/context_service.ts
|
| 110 |
+
src_web/comfyui/services/fast_groups_service.ts
|
| 111 |
+
src_web/comfyui/services/key_events_services.ts
|
| 112 |
+
src_web/comfyui/testing/comfyui_env.ts
|
| 113 |
+
src_web/comfyui/testing/runner.ts
|
| 114 |
+
src_web/comfyui/testing/utils_test.ts
|
| 115 |
+
src_web/comfyui/tests/context_dynamic_tests.ts
|
| 116 |
+
src_web/comfyui/tests/image_or_latent_size_tests.ts
|
| 117 |
+
src_web/comfyui/tests/power_puter.ts
|
| 118 |
+
src_web/comfyui/utils.ts
|
| 119 |
+
src_web/comfyui/utils_canvas.ts
|
| 120 |
+
src_web/comfyui/utils_deprecated_comfyui.ts
|
| 121 |
+
src_web/comfyui/utils_inputs_outputs.ts
|
| 122 |
+
src_web/comfyui/utils_menu.ts
|
| 123 |
+
src_web/comfyui/utils_widgets.ts
|
| 124 |
+
src_web/common/comfyui_shim.ts
|
| 125 |
+
src_web/common/comfyui_shim_pnginfo.ts
|
| 126 |
+
src_web/common/components/base_custom_element.ts
|
| 127 |
+
src_web/common/css/buttons.scss
|
| 128 |
+
src_web/common/css/dialog.scss
|
| 129 |
+
src_web/common/css/dialog_lora_chooser.scss
|
| 130 |
+
src_web/common/css/dialog_model_info.scss
|
| 131 |
+
src_web/common/css/menu.scss
|
| 132 |
+
src_web/common/css/pages_base.scss
|
| 133 |
+
src_web/common/dialog.ts
|
| 134 |
+
src_web/common/link_fixer.ts
|
| 135 |
+
src_web/common/media/rgthree.svg
|
| 136 |
+
src_web/common/media/svgs.ts
|
| 137 |
+
src_web/common/menu.ts
|
| 138 |
+
src_web/common/model_info_service.ts
|
| 139 |
+
src_web/common/progress_bar.ts
|
| 140 |
+
src_web/common/prompt_service.ts
|
| 141 |
+
src_web/common/py_parser.ts
|
| 142 |
+
src_web/common/rgthree_api.ts
|
| 143 |
+
src_web/common/shared_utils.ts
|
| 144 |
+
src_web/common/utils_dom.ts
|
| 145 |
+
src_web/common/utils_templates.ts
|
| 146 |
+
src_web/common/utils_workflow.ts
|
| 147 |
+
src_web/lib/tree-sitter-python.wasm
|
| 148 |
+
src_web/lib/tree-sitter.js
|
| 149 |
+
src_web/lib/tree-sitter.wasm
|
| 150 |
+
src_web/link_fixer/icon_file_json.png
|
| 151 |
+
src_web/link_fixer/index.html
|
| 152 |
+
src_web/link_fixer/link_page.ts
|
| 153 |
+
src_web/models/components/model-info-card.html
|
| 154 |
+
src_web/models/components/model-info-card.scss
|
| 155 |
+
src_web/models/components/model-info-card.ts
|
| 156 |
+
src_web/models/index.html
|
| 157 |
+
src_web/models/models.scss
|
| 158 |
+
src_web/models/models_info_page.ts
|
| 159 |
+
src_web/scripts_comfy/README.md
|
| 160 |
+
src_web/scripts_comfy/api.ts
|
| 161 |
+
src_web/scripts_comfy/app.ts
|
| 162 |
+
src_web/scripts_comfy/widgets.ts
|
| 163 |
+
src_web/typings/comfy.d.ts
|
| 164 |
+
src_web/typings/index.d.ts
|
| 165 |
+
src_web/typings/litegraph.d.ts
|
| 166 |
+
src_web/typings/rgthree.d.ts
|
| 167 |
+
src_web/typings/web-tree-sitter.d.ts
|
| 168 |
+
tsconfig.json
|
| 169 |
+
web/comfyui/any_switch.js
|
| 170 |
+
web/comfyui/base_any_input_connected_node.js
|
| 171 |
+
web/comfyui/base_node.js
|
| 172 |
+
web/comfyui/base_node_collector.js
|
| 173 |
+
web/comfyui/base_node_mode_changer.js
|
| 174 |
+
web/comfyui/base_power_prompt.js
|
| 175 |
+
web/comfyui/bookmark.js
|
| 176 |
+
web/comfyui/bypasser.js
|
| 177 |
+
web/comfyui/comfy_ui_bar.js
|
| 178 |
+
web/comfyui/config.js
|
| 179 |
+
web/comfyui/constants.js
|
| 180 |
+
web/comfyui/context.js
|
| 181 |
+
web/comfyui/dialog_info.js
|
| 182 |
+
web/comfyui/display_any.js
|
| 183 |
+
web/comfyui/dynamic_context.js
|
| 184 |
+
web/comfyui/dynamic_context_base.js
|
| 185 |
+
web/comfyui/dynamic_context_switch.js
|
| 186 |
+
web/comfyui/fast_actions_button.js
|
| 187 |
+
web/comfyui/fast_groups_bypasser.js
|
| 188 |
+
web/comfyui/fast_groups_muter.js
|
| 189 |
+
web/comfyui/feature_group_fast_toggle.js
|
| 190 |
+
web/comfyui/feature_import_individual_nodes.js
|
| 191 |
+
web/comfyui/image_comparer.js
|
| 192 |
+
web/comfyui/image_inset_crop.js
|
| 193 |
+
web/comfyui/image_or_latent_size.js
|
| 194 |
+
web/comfyui/label.js
|
| 195 |
+
web/comfyui/menu_auto_nest.js
|
| 196 |
+
web/comfyui/menu_copy_image.js
|
| 197 |
+
web/comfyui/menu_queue_node.js
|
| 198 |
+
web/comfyui/muter.js
|
| 199 |
+
web/comfyui/node_collector.js
|
| 200 |
+
web/comfyui/node_mode_relay.js
|
| 201 |
+
web/comfyui/node_mode_repeater.js
|
| 202 |
+
web/comfyui/power_conductor.js
|
| 203 |
+
web/comfyui/power_lora_loader.js
|
| 204 |
+
web/comfyui/power_primitive.js
|
| 205 |
+
web/comfyui/power_prompt.js
|
| 206 |
+
web/comfyui/power_puter.js
|
| 207 |
+
web/comfyui/random_unmuter.js
|
| 208 |
+
web/comfyui/reroute.js
|
| 209 |
+
web/comfyui/rgthree.css
|
| 210 |
+
web/comfyui/rgthree.js
|
| 211 |
+
web/comfyui/seed.js
|
| 212 |
+
web/comfyui/services/bookmarks_services.js
|
| 213 |
+
web/comfyui/services/config_service.js
|
| 214 |
+
web/comfyui/services/context_service.js
|
| 215 |
+
web/comfyui/services/fast_groups_service.js
|
| 216 |
+
web/comfyui/services/key_events_services.js
|
| 217 |
+
web/comfyui/utils.js
|
| 218 |
+
web/comfyui/utils_canvas.js
|
| 219 |
+
web/comfyui/utils_deprecated_comfyui.js
|
| 220 |
+
web/comfyui/utils_inputs_outputs.js
|
| 221 |
+
web/comfyui/utils_menu.js
|
| 222 |
+
web/comfyui/utils_widgets.js
|
| 223 |
+
web/common/comfyui_shim.js
|
| 224 |
+
web/common/comfyui_shim_pnginfo.js
|
| 225 |
+
web/common/components/base_custom_element.js
|
| 226 |
+
web/common/css/buttons.css
|
| 227 |
+
web/common/css/dialog.css
|
| 228 |
+
web/common/css/dialog_lora_chooser.css
|
| 229 |
+
web/common/css/dialog_model_info.css
|
| 230 |
+
web/common/css/menu.css
|
| 231 |
+
web/common/css/pages_base.css
|
| 232 |
+
web/common/dialog.js
|
| 233 |
+
web/common/link_fixer.js
|
| 234 |
+
web/common/media/rgthree.svg
|
| 235 |
+
web/common/media/svgs.js
|
| 236 |
+
web/common/menu.js
|
| 237 |
+
web/common/model_info_service.js
|
| 238 |
+
web/common/progress_bar.js
|
| 239 |
+
web/common/prompt_service.js
|
| 240 |
+
web/common/py_parser.js
|
| 241 |
+
web/common/rgthree_api.js
|
| 242 |
+
web/common/shared_utils.js
|
| 243 |
+
web/common/utils_dom.js
|
| 244 |
+
web/common/utils_templates.js
|
| 245 |
+
web/common/utils_workflow.js
|
| 246 |
+
web/lib/tree-sitter-python.wasm
|
| 247 |
+
web/lib/tree-sitter.js
|
| 248 |
+
web/lib/tree-sitter.wasm
|
| 249 |
+
web/link_fixer/icon_file_json.png
|
| 250 |
+
web/link_fixer/index.html
|
| 251 |
+
web/link_fixer/link_page.js
|
| 252 |
+
web/models/components/model-info-card.css
|
| 253 |
+
web/models/components/model-info-card.html
|
| 254 |
+
web/models/components/model-info-card.js
|
| 255 |
+
web/models/index.html
|
| 256 |
+
web/models/models.css
|
| 257 |
+
web/models/models_info_page.js
|
custom_nodes/rgthree-comfy/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2023 Regis Gaughan, III (rgthree)
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
custom_nodes/rgthree-comfy/README.md
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<h1 align="center">
|
| 2 |
+
rgthree-comfy
|
| 3 |
+
<br>
|
| 4 |
+
<sub><sup><i>Making ComfyUI more comfortable!</i></sup></sub>
|
| 5 |
+
<br>
|
| 6 |
+
</h1>
|
| 7 |
+
<p align="center">
|
| 8 |
+
<a href="#️-the-nodes">The Nodes</a> | <a href="#-improvements--features">Improvements & Features</a> | <a href="#-link-fixer">Link Fixer</a>
|
| 9 |
+
</p>
|
| 10 |
+
<hr>
|
| 11 |
+
|
| 12 |
+
A collection of nodes and improvements created while messing around with ComfyUI. I made them for myself to make my workflow cleaner, easier, and faster. You're welcome to try them out. But remember, I made them for my own use cases :)
|
| 13 |
+
|
| 14 |
+

|
| 15 |
+
|
| 16 |
+
# Get Started
|
| 17 |
+
|
| 18 |
+
## Install
|
| 19 |
+
|
| 20 |
+
1. Install the great [ComfyUi](https://github.com/comfyanonymous/ComfyUI).
|
| 21 |
+
2. Clone this repo into `custom_modules`:
|
| 22 |
+
```
|
| 23 |
+
cd ComfyUI/custom_nodes
|
| 24 |
+
git clone https://github.com/rgthree/rgthree-comfy.git
|
| 25 |
+
```
|
| 26 |
+
3. Start up ComfyUI.
|
| 27 |
+
|
| 28 |
+
## Settings
|
| 29 |
+
|
| 30 |
+
You can configure certain aspect of rgthree-comfy. For instance, perhaps a future ComfyUI change breaks rgthree-comfy, or you already have another extension that does something similar and you want to turn it off for rgthree-comfy.
|
| 31 |
+
|
| 32 |
+
You can get to rgthree-settings by right-clicking on the empty part of the graph, and selecting `rgthree-comfy > Settings (rgthree-comfy)` or by clicking the `rgthree-comfy settings` in the ComfyUI settings dialog.
|
| 33 |
+
|
| 34 |
+
_(Note, settings are stored in an `rgthree_config.json` in the `rgthree-comfy` directory. There are other advanced settings that can only be configured there; You can copy default settings from `rgthree_config.json.default` before `rgthree_config.json` before modifying)_.
|
| 35 |
+
|
| 36 |
+
<br>
|
| 37 |
+
|
| 38 |
+
# ✴️ The Nodes
|
| 39 |
+
|
| 40 |
+
Note, you can right-click on a bunch of the rgthree-comfy nodes and select `🛟 Node Help` menu item for in-app help when available.
|
| 41 |
+
|
| 42 |
+
## Seed
|
| 43 |
+
> An intuitive seed control node for ComfyUI that works very much like Automatic1111's seed control.
|
| 44 |
+
> <details>
|
| 45 |
+
> <summary>ℹ️ <i>See More Information</i></summary>
|
| 46 |
+
>
|
| 47 |
+
> - Set the seed value to "-1" to use a random seed every time
|
| 48 |
+
> - Set any other number in there to use as a static/fixed seed
|
| 49 |
+
> - Quick actions to randomize, or (re-)use the last queued seed.
|
| 50 |
+
> - Images metadata will store the seed value _(so dragging an image in, will have the seed field already fixed to its seed)_.
|
| 51 |
+
> - _Secret Features_: You can manually set the seed value to "-2" or "-3" to increment or decrement the last seed value. If there was not last seed value, it will randomly use on first.
|
| 52 |
+
>
|
| 53 |
+
> 
|
| 54 |
+
> </details>
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
## Reroute
|
| 58 |
+
> Keep your workflow neat with this much improved Reroute node with, like, actual rerouting with multiple directions and sizes.
|
| 59 |
+
> <details>
|
| 60 |
+
> <summary>ℹ️ <i>More Information</i></summary>
|
| 61 |
+
>
|
| 62 |
+
> - Use the right-click context menu to change the width, height and connection layout
|
| 63 |
+
> - Also toggle resizability (min size is 40x43 if resizing though), and title/type display.
|
| 64 |
+
>
|
| 65 |
+
> 
|
| 66 |
+
> </details>
|
| 67 |
+
|
| 68 |
+
## Bookmark (🔖)
|
| 69 |
+
> Place the bookmark node anywhere on screen to quickly navigate to that with a shortcut key.
|
| 70 |
+
> <details>
|
| 71 |
+
> <summary>ℹ️ <i>See More Information</i></summary>
|
| 72 |
+
>
|
| 73 |
+
> - Define the `shortcut_key` to press to go right to that bookmark node, anchored in the top left.
|
| 74 |
+
> - You can also define the zoom level as well!
|
| 75 |
+
> - Pro tip: `shortcut_key` can be multiple keys. For instance "alt + shift + !" would require
|
| 76 |
+
> pressing the alt key, the shift key, and the "!" (as in the "1" key, but with shift pressed)
|
| 77 |
+
> in order to trigger.
|
| 78 |
+
> </details>
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
## Context / Context Big
|
| 82 |
+
> Pass along in general flow properties, and merge in new data. Similar to some other node suites "pipes" but easier merging, is more easily interoperable with standard nodes by both combining and exploding all in a single node.
|
| 83 |
+
> <details>
|
| 84 |
+
> <summary>ℹ️ <i>More Information</i></summary>
|
| 85 |
+
>
|
| 86 |
+
> - Context and Context Big are backwards compatible with each other. That is, an input connected to a Context Big will be passed through the CONTEXT outputs through normal Context nodes and available as an output on either (or, Context Big if the output is only on that node, like "steps").
|
| 87 |
+
> - Pro Tip: When dragging a Context output over a nother node, hold down "ctrl" and release to automatically connect the other Context outputs to the hovered node.
|
| 88 |
+
> - Pro Tip: You can change between Context and Context Big nodes from the menu.
|
| 89 |
+
>
|
| 90 |
+
> 
|
| 91 |
+
> </details>
|
| 92 |
+
|
| 93 |
+
## Image Comparer
|
| 94 |
+
> The Image Comparer node compares two images on top of each other.
|
| 95 |
+
> <details>
|
| 96 |
+
> <summary>ℹ️ <i>More Information</i></summary>
|
| 97 |
+
>
|
| 98 |
+
> - **Note:** The right-click menu may show image options (Open Image, Save Image, etc.) which will correspond to the first image (image_a) if clicked on the left-half of the node, or the second image if on the right half of the node.
|
| 99 |
+
> - **Inputs:**
|
| 100 |
+
> - `image_a` _Required._ The first image to use to compare. If image_b is not supplied and image_a is a batch, the comparer will use the first two images of image_a.
|
| 101 |
+
> - `image_b` _Optional._ The second image to use to compare. Optional only if image_a is a batch with two images.
|
| 102 |
+
> - **Properties:** You can change the following properties (by right-clicking on the node, and select "Properties" or "Properties Panel" from the menu):
|
| 103 |
+
> - `comparer_mode` - Choose between "Slide" and "Click". Defaults to "Slide".
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
## Image Inset Crop
|
| 107 |
+
> The node that lets you crop an input image by either pixel value, or percentage value.
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
## Display Any
|
| 111 |
+
> Displays most any piece of text data from the backend _after execution_.
|
| 112 |
+
|
| 113 |
+
## Power Lora Loader
|
| 114 |
+
> A super-simply Lora Loader node that can load multiple Loras at once, and quick toggle each, all in an ultra-condensed node.
|
| 115 |
+
> <details>
|
| 116 |
+
> <summary>ℹ️ <i>More Information</i></summary>
|
| 117 |
+
>
|
| 118 |
+
> - Add as many Lora's as you would like by clicking the "+ Add Lora" button. There's no real limit!
|
| 119 |
+
> - Right-click on a Lora widget for special options to move the lora up or down
|
| 120 |
+
> _(no affect on image, just presentation)_, toggle it on/off, or delete the row all together.
|
| 121 |
+
> - from the properties, change the `Show Strengths` to choose between showing a single, simple
|
| 122 |
+
> strength value (which will be used for both model and clip), or a more advanced view with
|
| 123 |
+
> both model and clip strengths being modifiable.
|
| 124 |
+
> </details>
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
## ~~Lora Loader Stack~~
|
| 128 |
+
> _**Deprecated.** Used the `Power Lora Loader` instead._
|
| 129 |
+
>
|
| 130 |
+
> A simplified Lora Loader stack. Much like other suites, but more interoperable with standard inputs/outputs.
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
## Power Prompt
|
| 134 |
+
> Power up your prompt and get drop downs for adding your embeddings, loras, and even have saved prompt snippets.
|
| 135 |
+
> <details>
|
| 136 |
+
> <summary>ℹ️ <i>More Information</i></summary>
|
| 137 |
+
>
|
| 138 |
+
> - At the core, you can use Power Prompt almost as a String Primitive node with additional features of dropdowns for choosing your embeddings, and even loras, with no further processing. This will output just the raw `TEXT` to another node for any lora processing, CLIP Encoding, etc.
|
| 139 |
+
> - Connect a `CLIP` to the input to encode the text, with both the `CLIP` and `CONDITIONING` output right from the node.
|
| 140 |
+
> - Connect a `MODEL` to the input to parse and load any `<lora:...>` tags in the text automatically, without
|
| 141 |
+
> needing a separate Lora Loaders
|
| 142 |
+
> </details>
|
| 143 |
+
|
| 144 |
+
## Power Prompt - Simple
|
| 145 |
+
> Same as Power Prompt above, but without LORA support; made for a slightly cleaner negative prompt _(since negative prompts do not support loras)_.
|
| 146 |
+
|
| 147 |
+
## SDXL Power Prompt - Positive
|
| 148 |
+
> The SDXL sibling to the Power Prompt above. It contains the text_g and text_l as separate text inputs, as well a couple more input slots necessary to ensure proper clipe encoding. Combine with
|
| 149 |
+
|
| 150 |
+
## SDXL Power Prompt - Simple
|
| 151 |
+
> Like the non-SDXL `Power Prompt - Simple` node, this one is essentially the same as the SDXL Power Prompt but without lora support for either non-lora positive prompts or SDXL negative prompts _(since negative prompts do not support loras)_.
|
| 152 |
+
|
| 153 |
+
## SDXL Config
|
| 154 |
+
> Just some configuration fields for SDXL prompting. Honestly, could be used for non SDXL too.
|
| 155 |
+
|
| 156 |
+
## Context Switch / Context Switch Big
|
| 157 |
+
> A powerful node to branch your workflow. Works by choosing the first Context input that is not null/empty.
|
| 158 |
+
> <details>
|
| 159 |
+
> <summary>ℹ️ <i>More Information</i></summary>
|
| 160 |
+
>
|
| 161 |
+
> - Pass in several context nodes and the Context Switch will automatically choose the first non-null context to continue onward with.
|
| 162 |
+
> - Wondering how to toggle contexts to null? Use in conjuction with the **Fast Muter** or **Fast Groups Muter**
|
| 163 |
+
>
|
| 164 |
+
> </details>
|
| 165 |
+
|
| 166 |
+
## Any Switch
|
| 167 |
+
> A powerful node to similar to the Context Switch above, that chooses the first input that is not null/empty.
|
| 168 |
+
> <details>
|
| 169 |
+
> <summary>ℹ️ <i>More Information</i></summary>
|
| 170 |
+
>
|
| 171 |
+
> - Pass in several inmputs of the same type and the Any Switch will automatically choose the first non-null value to continue onward with.
|
| 172 |
+
> - Wondering how to toggle contexts to null? Use in conjuction with the **Fast Muter** or **Fast Groups Muter**
|
| 173 |
+
>
|
| 174 |
+
> </details>
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
## Power Primitive
|
| 178 |
+
> A single node that can output primitives (STRING, INT, FLOAT, BOOLEAN). If connecting an input, it will cast/convert the primitive input to the desired output.
|
| 179 |
+
> <details>
|
| 180 |
+
> <summary>ℹ️ <i>More Information</i></summary>
|
| 181 |
+
>
|
| 182 |
+
> - You can hide the type selector input from the right-click menu or properties.
|
| 183 |
+
> - You can also fast-switch the output type from the right-click menu as well.
|
| 184 |
+
>
|
| 185 |
+
> </details>
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
## Power Puter
|
| 189 |
+
> A powerful and versatile node that opens the door for a wide range of utility by offering mult-line code parsing for output. This node can be used for simple string concatenation, or math operations; to an image dimension or a node's widgets with advanced list comprehension. If you want to output something in your workflow, this is the node to do it.
|
| 190 |
+
>
|
| 191 |
+
> Additional documentation available in the [wiki](https://github.com/rgthree/rgthree-comfy/wiki/Node:-Power-Puter)
|
| 192 |
+
> <details>
|
| 193 |
+
> <summary>ℹ️ <i>More Information</i></summary>
|
| 194 |
+
>
|
| 195 |
+
> - Evaluate almost any kind of input and more, and choose your output from INT, FLOAT, STRING, or BOOLEAN.
|
| 196 |
+
> - Connect some nodes and do simply math operations like `a + b` or `ceil(1 / 2)`.
|
| 197 |
+
> - Or do more advanced things, like input an image, and get the width like `a.shape[2]`.
|
| 198 |
+
> - Even more powerful, you can target nodes in the prompt that's sent to the backend. For instance; if you have a Power Lora Loader node at id #5, and want to get a comma-delimited list of the enabled loras, you could enter:
|
| 199 |
+
>
|
| 200 |
+
> ```
|
| 201 |
+
> loras = [v.lora for v in node(5).inputs.values() if 'lora' in v and v.on]
|
| 202 |
+
> ', '.join(loras)
|
| 203 |
+
> ```
|
| 204 |
+
>
|
| 205 |
+
> </details>
|
| 206 |
+
|
| 207 |
+
## Fast Groups Muter
|
| 208 |
+
> The Fast Groups Muter is an input-less node that automatically collects all groups in your current workflow and allows you to quickly mute and unmute all nodes within the group.
|
| 209 |
+
> <details>
|
| 210 |
+
> <summary>ℹ️ <i>More Information</i></summary>
|
| 211 |
+
>
|
| 212 |
+
> - Groups will automatically be shown, though you can filter, sort and more from the **node Properties** _(by right-clicking on the node, and select "Properties" or "Properties Panel" from the menu)_. Properties include:
|
| 213 |
+
> - `matchColors` - Only add groups that match the provided colors. Can be ComfyUI colors (red, pale_blue) or hex codes (#a4d399). Multiple can be added, comma delimited.
|
| 214 |
+
> - `matchTitle` - Filter the list of toggles by title match (string match, or regular expression).
|
| 215 |
+
> - `showNav` - Add / remove a quick navigation arrow to take you to the group. (default: true)
|
| 216 |
+
> - `showAllGraphs` - Show groups from all [sub]graphs in the workflow. (default: true)
|
| 217 |
+
> - `sort` - Sort the toggles' order by "alphanumeric", graph "position", or "custom alphabet". (default: "position")
|
| 218 |
+
> - `customSortAlphabet` - When the sort property is "custom alphabet" you can define the alphabet to use here, which will match the beginning of each group name and sort against it. If group titles do not match any custom alphabet entry, then they will be put after groups that do, ordered alphanumerically.
|
| 219 |
+
>
|
| 220 |
+
> This can be a list of single characters, like "zyxw..." or comma delimited strings for more control, like "sdxl,pro,sd,n,p".
|
| 221 |
+
>
|
| 222 |
+
> Note, when two group title match the same custom alphabet entry, the normal alphanumeric alphabet breaks the tie. For instance, a custom alphabet of "e,s,d" will order groups names like "SDXL, SEGS, Detailer" eventhough the custom alphabet has an "e" before "d" (where one may expect "SE" to be before "SD").
|
| 223 |
+
>
|
| 224 |
+
> To have "SEGS" appear before "SDXL" you can use longer strings. For instance, the custom alphabet value of "se,s,f" would work here.
|
| 225 |
+
> - `toggleRestriction` - Optionally, attempt to restrict the number of widgets that can be enabled to a maximum of one, or always one.
|
| 226 |
+
>
|
| 227 |
+
> _Note: If using "max one" or "always one" then this is only enforced when clicking a toggle on this node; if nodes within groups are changed outside of the initial toggle click, then these restriction will not be enforced, and could result in a state where more than one toggle is enabled. This could also happen if nodes are overlapped with multiple groups._
|
| 228 |
+
> </details>
|
| 229 |
+
|
| 230 |
+
## Fast Groups Bypasser
|
| 231 |
+
> _Same as **Fast Groups Muter** above, but sets the connected nodes to "Bypass" instead of "Mute"_
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
## Fast Muter
|
| 235 |
+
> A powerful 'control panel' node to quickly toggle connected nodes allowing them to quickly be muted or enabled
|
| 236 |
+
> <details>
|
| 237 |
+
> <summary>ℹ️ <i>More Information</i></summary>
|
| 238 |
+
>
|
| 239 |
+
> - Add a collection of all connected nodes allowing a single-spot as a "dashboard" to quickly enable and disable nodes. Two distinct nodes; one for "Muting" connected nodes, and one for "Bypassing" connected nodes.
|
| 240 |
+
> </details>
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
## Fast Bypasser
|
| 244 |
+
> Same as Fast Muter but sets the connected nodes to "Bypass"
|
| 245 |
+
|
| 246 |
+
## Fast Actions Button
|
| 247 |
+
> Oh boy, this node allows you to semi-automate connected nodes and/or ConfyUI.
|
| 248 |
+
> <details>
|
| 249 |
+
> <summary>ℹ️ <i>More Information</i></summary>
|
| 250 |
+
>
|
| 251 |
+
> - Connect nodes and, at the least, mute, bypass or enable them when the button is pressed.
|
| 252 |
+
> - Certain nodes expose additional actions. For instance, the `Seed` node you can set `Randomize Each Time` or `Use Last Queued Seed` when the button is pressed.
|
| 253 |
+
> - Also, from the node properties, set a shortcut key to toggle the button actions, without needing a click!
|
| 254 |
+
> </details>
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
## Node Collector
|
| 258 |
+
> Used to cleanup noodles, this will accept any number of input nodes and passes it along to another node.
|
| 259 |
+
>
|
| 260 |
+
> ⚠️ *Currently, this should really only be connected to **Fast Muter**, **Fast Bypasser**, or **Mute / Bypass Relay**.*
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
## Mute / Bypass Repeater
|
| 264 |
+
> A powerful node that will dispatch its Mute/Bypass/Active mode to all connected input nodes or, if in a group w/o any connected inputs, will dispatch its Mute/Bypass/Active mode to all nodes in that group.
|
| 265 |
+
> <details>
|
| 266 |
+
> <summary>ℹ️ <i>More Information</i></summary>
|
| 267 |
+
>
|
| 268 |
+
> - 💡 Pro Tip #1: Connect this node's output to a **Fast Muter** or **Fast Bypasser** to have a single toggle there that can mute/bypass/enable many nodes with one click.
|
| 269 |
+
>
|
| 270 |
+
> - 💡 Pro Tip #2: Connect a **Mute / Bypass Relay** node to this node's inputs to have the relay automatically dispatch a mute/bypass/enable change to the repeater.
|
| 271 |
+
> </details>
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
## Mute / Bypass Relay
|
| 275 |
+
> An advanced node that, when working with a **Mute / Bypass Repeater**, will relay its input nodes'
|
| 276 |
+
> modes (Mute, Bypass, or Active) to a connected repeater (which would then repeat that mode change
|
| 277 |
+
> to all of its inputs).
|
| 278 |
+
> <details>
|
| 279 |
+
> <summary>ℹ️ <i>More Information</i></summary>
|
| 280 |
+
>
|
| 281 |
+
> - When all connected input nodes are muted, the relay will set a connected repeater to mute (by
|
| 282 |
+
> default).
|
| 283 |
+
> - When all connected input nodes are bypassed, the relay will set a connected repeater to
|
| 284 |
+
> bypass (by default).
|
| 285 |
+
> - When _any_ connected input nodes are active, the relay will set a connected repeater to
|
| 286 |
+
> active (by default).
|
| 287 |
+
> - **Note:** If no inputs are connected, the relay will set a connected repeater to its mode
|
| 288 |
+
> _when its own mode is changed_. **Note**, if any inputs are connected, then the above bullets
|
| 289 |
+
> will occur and the Relay's mode does not matter.
|
| 290 |
+
> - **Pro Tip:** You can change which signals get sent on the above in the `Properties`.
|
| 291 |
+
> For instance, you could configure an inverse relay which will send a MUTE when any of its
|
| 292 |
+
> inputs are active (instead of sending an ACTIVE signal), and send an ACTIVE signal when all
|
| 293 |
+
> of its inputs are muted (instead of sending a MUTE signal), etc.
|
| 294 |
+
> </details>
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
## Random Unmuter
|
| 298 |
+
> An advanced node used to unmute one of its inputs randomly when the graph is queued (and, immediately mute it back).
|
| 299 |
+
> <details>
|
| 300 |
+
> <summary>ℹ️ <i>More Information</i></summary>
|
| 301 |
+
>
|
| 302 |
+
> - **Note:** All input nodes MUST be muted to start; if not this node will not randomly unmute another. (This is powerful, as the generated image can be dragged in and the chosen input will already by unmuted and work w/o any further action.)
|
| 303 |
+
> - **Tip:** Connect a Repeater's output to this nodes input and place that Repeater on a group without any other inputs, and it will mute/unmute the entire group.
|
| 304 |
+
> </details>
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
## Label
|
| 308 |
+
> A purely visual node, this allows you to add a floating label to your workflow.
|
| 309 |
+
> <details>
|
| 310 |
+
> <summary>ℹ️ <i>More Information</i></summary>
|
| 311 |
+
>
|
| 312 |
+
> - The text shown is the "Title" of the node and you can adjust the the font size, font family,
|
| 313 |
+
> font color, text alignment as well as a background color, padding, background border
|
| 314 |
+
> radius, and angle (in degrees) from the node's properties. You can double-click the node to
|
| 315 |
+
> open the properties panel.
|
| 316 |
+
> - The Title also supports the literal sequence "\\n" to insert a newline when drawing the label.
|
| 317 |
+
> - ~**Pro Tip #1:** You can add multiline text from the properties panel _(because ComfyUI let's
|
| 318 |
+
> you shift + enter there, only)._~
|
| 319 |
+
> - **Pro Tip #2:** You can use ComfyUI's native "pin" option in the right-click menu to make the
|
| 320 |
+
> label stick to the workflow and clicks to "go through". You can right-click at any time to
|
| 321 |
+
> unpin.
|
| 322 |
+
> - **Pro Tip #3:** Color values are hexidecimal strings, like "#FFFFFF" for white, or "#660000"
|
| 323 |
+
> for dark red. You can supply a 7th & 8th value (or 5th if using shorthand) to create a
|
| 324 |
+
> transluscent color. For instance, "#FFFFFF88" is semi-transparent white.
|
| 325 |
+
> </details>
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
# Advanced Techniques
|
| 329 |
+
|
| 330 |
+
## First, a word on muting
|
| 331 |
+
|
| 332 |
+
A lot of the power of these nodes comes from *Muting*. Muting is the basis of correctly implementing multiple paths for a workflow utlizing the Context Switch node.
|
| 333 |
+
|
| 334 |
+
While other extensions may provide switches, they often get it wrong causing your workflow to do more work than is needed. While other switches may have a selector to choose which input to pass along, they don't stop the execution of the other inputs, which will result in wasted work. Instead, Context Switch works by choosing the first non-empty context to pass along and correctly Muting is one way to make a previous node empty, and causes no extra work to be done when set up correctly.
|
| 335 |
+
|
| 336 |
+
### To understand muting, is to understand the graph flow
|
| 337 |
+
|
| 338 |
+
Muting, and therefore using Switches, can often confuse people at first because it _feels_ like muting a node, or using a switch, should be able to stop or direct the _forward_ flow of the graph. However, this is not the case and, in fact, the graph actually starts working backwards.
|
| 339 |
+
|
| 340 |
+
If you have a workflow that has a path like `... > Context > KSampler > VAE Decode > Save Image` it may initially _feel_ like you should be able to mute that first Context node and the graph would stop there when moving forward and skip the rest of that workflow.
|
| 341 |
+
|
| 342 |
+
But you'll quickly find that will cause an error, becase the graph doesn't actually move forward. When a workflow is processed, it _first moves backwards_ starting at each "Output Node" (Preview Image, Save Image, even "Display String" etc.) and then walking backwards to all possible paths to get there.
|
| 343 |
+
|
| 344 |
+
So, with that `... > Context > KSampler > VAE Decode > Save Image` example from above, we actually want to mute the `Save Image` node to stop this path. Once we do, since the output node is gone, none of these nodes will be run.
|
| 345 |
+
|
| 346 |
+
Let's take a look at an example.
|
| 347 |
+
|
| 348 |
+
### A powerful combination: Using Context, Context Switch, & Fast Muter
|
| 349 |
+
|
| 350 |
+

|
| 351 |
+
|
| 352 |
+
1. Using the **Context Switch** (aqua colored in screenshot) feed context inputs in order of preference. In the workflow above, the `Upscale Out` context is first so, if that one is enabled, it will be chosen for the output. If not, the second input slot which comes from the context rerouted from above (before the Upscaler booth) will be chosen.
|
| 353 |
+
|
| 354 |
+
- Notice the `Upscale Preview` is _after_ the `Upscale Out` context node, using the image from it instead of the image from the upscale `VAE Decoder`. This is on purpose so, when we disable the `Upscale Out` context, none of the Upscaler nodes will run, saving precious GPU cycles. If we had the preview hooked up directly to the `VAE Decoder` the upscaler would always run to generate the preview, even if we had the `Upscale Out` context node disabled.
|
| 355 |
+
|
| 356 |
+
2. We can now disable the `Upscale Out` context node by _muting_ it. Highlighting it and pressing `ctrl + m` will work. By doing so, it's output will be None, and it will not pass anthing onto the further nodes. In the diagram you can see the `Upscale Preview` is red, but that's OK; there are no actual errors to stop execution.
|
| 357 |
+
|
| 358 |
+
3. Now, let's hook it up to the `Fast Muter` node. `The Fast Muter` node works as dashboard by adding quick toggles for any connected node (ignoring reroutes). In the diagram, we have both the `Upscaler Out` context node, and the `Save File` context node hooked up. So, we can quickly enable and disable those.
|
| 359 |
+
|
| 360 |
+
- The workflow seen here would be a common one where we can generate a handful of base previews cheaply with a random seed, and then choose one to upscale and save to disk.
|
| 361 |
+
|
| 362 |
+
4. Lastly, and optionally, you can see the `Node Collector`. Use it to clean up noodles if you want and connect it to the muter. You can connect anything to it, but doing so may break your workflow's execution.
|
| 363 |
+
|
| 364 |
+
<br>
|
| 365 |
+
|
| 366 |
+
# ⚡ Improvements & Features
|
| 367 |
+
|
| 368 |
+
rgthree-comfy adds several improvements, features, and optimizations to ComfyUI that are not directly tied to nodes.
|
| 369 |
+
|
| 370 |
+
## Progress Bar
|
| 371 |
+
> A minimal progress bar that run alongs the top of the app window that shows the queue size, the current progress of the a prompt execution (within the same window), and the progress of multi-step nodes as well.
|
| 372 |
+
>
|
| 373 |
+
> <i>You can remove/enable from rgthree-comfy settings, as well as configure the height/size.</i>
|
| 374 |
+
|
| 375 |
+
|
| 376 |
+
## ~~ComfyUI Recursive Optimization~~
|
| 377 |
+
> 🎉 The newest version of ComfyUI no longer suffers from poor execution recursion! This feature
|
| 378 |
+
> has been removed from rgthree-comfy.
|
| 379 |
+
|
| 380 |
+
|
| 381 |
+
## "Queue Selected Output Nodes" in right-click menu
|
| 382 |
+
> Sometimes you want to just queue one or two paths to specific output node(s) without executing the entire workflow. Well, now you can do just that by right-clicking on an output node and selecting `Queue Selected Output Nodes (rgthree)`.
|
| 383 |
+
>
|
| 384 |
+
> <details>
|
| 385 |
+
> <summary>ℹ️ <i>More Information</i></summary>
|
| 386 |
+
>
|
| 387 |
+
> - Select the _output_ nodes you want to execute.
|
| 388 |
+
>
|
| 389 |
+
> - Note: Only output nodes are captured and traversed, not all selected nodes. So if you select an output AND a node from a different path, only the path connected to the output will be executed and not non-output nodes, even if they were selected.
|
| 390 |
+
>
|
| 391 |
+
> - Note: The whole workflow is serialized, and then we trim what we don't want for the backend. So things like all seed random/increment/decrement will run even if that node isn't being sent in the end, etc.
|
| 392 |
+
>
|
| 393 |
+
> </details>
|
| 394 |
+
|
| 395 |
+
|
| 396 |
+
## Auto-Nest Subdirectories in long Combos
|
| 397 |
+
> _(Off by default while experimenting, turn on in rgthree-comfy settings)_.
|
| 398 |
+
>
|
| 399 |
+
> Automatically detect top-level subdirectories in long combo lists (like, Load Checkpoint) and break out into sub directories.
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
## Quick Mute/Bypass Toggles in Group Headers
|
| 403 |
+
> _(Off by default while experimenting, turn on in rgthree-comfy settings)_.
|
| 404 |
+
>
|
| 405 |
+
> Adds a mute and/or bypass toggle icons in the top-right of Group Headers for one-click toggling of groups you may be currently looking at.
|
| 406 |
+
|
| 407 |
+
|
| 408 |
+
## Import Individual Node Widgets (Drag & Drop)
|
| 409 |
+
> _(Off by default while experimenting, turn on in rgthree-comfy settings)_.
|
| 410 |
+
>
|
| 411 |
+
> Allows dragging and dropping an image/JSON workflow from a previous generation and overriding the same node's widgets
|
| 412 |
+
> (that match with the same id & type). This is useful if you have several generations using the same general workflow
|
| 413 |
+
> and would like to import just some data, like a previous generation's seed, or prompt, etc.
|
| 414 |
+
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
## "Copy Image" in right-click menu
|
| 418 |
+
> Right clicking on a node that has an image should have a context-menu item of "Copy Image" will allow you to copy the image right to your clipboard
|
| 419 |
+
>
|
| 420 |
+
> <i>🎓 I believe this has graduated, with ComfyUI recently adding this setting too. You won't get two menu items; my code checks that there isn't already a "Copy Image" item there before adding it.</i>
|
| 421 |
+
|
| 422 |
+
|
| 423 |
+
## Other/Smaller Fixes
|
| 424 |
+
- Fixed the width of ultra-wide node chooser on double click.
|
| 425 |
+
- Fixed z-indexes for textareas that would overlap above other elements, like Properties Panel, or @pythongosssss's image viewer.
|
| 426 |
+
- Check for bad links when loading a workflow and log to console, by default. _(See Link Fixer below)._
|
| 427 |
+
|
| 428 |
+
<br>
|
| 429 |
+
|
| 430 |
+
# 📄 Link Fixer
|
| 431 |
+
|
| 432 |
+
If your workflows sometimes have missing connections, or even errors on load, start up ComfyUI and go to http://127.0.0.1:8188/rgthree/link_fixer which will allow you to drop in an image or workflow json file and check for and fix any bad links.
|
| 433 |
+
|
| 434 |
+
You can also enable a link fixer check in the rgthree-comfy settings to give you an alert if you load a workflow with bad linking data to start.
|
custom_nodes/rgthree-comfy/__build__.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
import subprocess
|
| 4 |
+
import os
|
| 5 |
+
from shutil import rmtree, copytree, ignore_patterns
|
| 6 |
+
from glob import glob
|
| 7 |
+
import time
|
| 8 |
+
import re
|
| 9 |
+
import argparse
|
| 10 |
+
|
| 11 |
+
from py.log import COLORS
|
| 12 |
+
from py.config import RGTHREE_CONFIG
|
| 13 |
+
|
| 14 |
+
step_msg = ''
|
| 15 |
+
step_start = 0
|
| 16 |
+
step_infos = []
|
| 17 |
+
|
| 18 |
+
def log_step(msg=None, status=None):
|
| 19 |
+
""" Logs a step keeping track of timing and initial msg. """
|
| 20 |
+
global step_msg # pylint: disable=W0601
|
| 21 |
+
global step_start # pylint: disable=W0601
|
| 22 |
+
global step_infos # pylint: disable=W0601
|
| 23 |
+
if msg:
|
| 24 |
+
tag = f'{COLORS["YELLOW"]}[ Notice ]' if status == 'Notice' else f'{COLORS["RESET"]}[Starting]'
|
| 25 |
+
step_msg = f'▻ {tag}{COLORS["RESET"]} {msg}...'
|
| 26 |
+
step_start = time.time()
|
| 27 |
+
step_infos = []
|
| 28 |
+
print(step_msg, end="\r")
|
| 29 |
+
elif status:
|
| 30 |
+
if status != 'Error':
|
| 31 |
+
warnings = [w for w in step_infos if w["type"] == 'warn']
|
| 32 |
+
status = "Warn" if warnings else status
|
| 33 |
+
step_time = round(time.time() - step_start, 3)
|
| 34 |
+
if status == 'Error':
|
| 35 |
+
status_msg = f'{COLORS["RED"]}⤫ {status}{COLORS["RESET"]}'
|
| 36 |
+
elif status == 'Warn':
|
| 37 |
+
status_msg = f'{COLORS["YELLOW"]}! {status}{COLORS["RESET"]}'
|
| 38 |
+
else:
|
| 39 |
+
status_msg = f'{COLORS["BRIGHT_GREEN"]}🗸 {status}{COLORS["RESET"]}'
|
| 40 |
+
print(f'{step_msg.ljust(64, ".")} {status_msg} ({step_time}s)')
|
| 41 |
+
for info in step_infos:
|
| 42 |
+
print(info["msg"])
|
| 43 |
+
|
| 44 |
+
def log_step_info(msg:str, status='info'):
|
| 45 |
+
global step_infos # pylint: disable=W0601
|
| 46 |
+
step_infos.append({"msg": f' - {msg}', "type": status})
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def build(without_tests = True, fix = False):
|
| 50 |
+
|
| 51 |
+
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 52 |
+
DIR_SRC_WEB = os.path.abspath(f'{THIS_DIR}/src_web/')
|
| 53 |
+
DIR_WEB = os.path.abspath(f'{THIS_DIR}/web/')
|
| 54 |
+
DIR_WEB_COMFYUI = os.path.abspath(f'{DIR_WEB}/comfyui/')
|
| 55 |
+
|
| 56 |
+
if fix:
|
| 57 |
+
tss = glob(os.path.join(DIR_SRC_WEB, "**", "*.ts"), recursive=True)
|
| 58 |
+
log_step(msg=f'Fixing {len(tss)} ts files')
|
| 59 |
+
for ts in tss:
|
| 60 |
+
with open(ts, 'r', encoding="utf-8") as f:
|
| 61 |
+
content = f.read()
|
| 62 |
+
# (\s*from\s*['"](?!.*[.]js['"]).*?)(['"];) in vscode.
|
| 63 |
+
content, n = re.subn(r'(\s*from [\'"](?!.*[.]js[\'"]).*?)([\'"];)', '\\1.js\\2', content)
|
| 64 |
+
if n > 0:
|
| 65 |
+
filename = os.path.basename(ts)
|
| 66 |
+
log_step_info(
|
| 67 |
+
f'{filename} has {n} import{"s" if n > 1 else ""} that do not end in ".js"', 'warn')
|
| 68 |
+
with open(ts, 'w', encoding="utf-8") as f:
|
| 69 |
+
f.write(content)
|
| 70 |
+
log_step(status="Done")
|
| 71 |
+
|
| 72 |
+
log_step(msg='Copying web directory')
|
| 73 |
+
rmtree(DIR_WEB)
|
| 74 |
+
copytree(DIR_SRC_WEB, DIR_WEB, ignore=ignore_patterns("typings*", "*.ts", "*.scss"))
|
| 75 |
+
log_step(status="Done")
|
| 76 |
+
|
| 77 |
+
ts_version_result = subprocess.run(["node", "./node_modules/typescript/bin/tsc", "-v"],
|
| 78 |
+
capture_output=True,
|
| 79 |
+
text=True,
|
| 80 |
+
check=True)
|
| 81 |
+
ts_version = re.sub(r'^.*Version\s*([\d\.]+).*', 'v\\1', ts_version_result.stdout, flags=re.DOTALL)
|
| 82 |
+
|
| 83 |
+
log_step(msg=f'TypeScript ({ts_version})')
|
| 84 |
+
checked = subprocess.run(["node", "./node_modules/typescript/bin/tsc"], check=True)
|
| 85 |
+
log_step(status="Done")
|
| 86 |
+
|
| 87 |
+
if not without_tests:
|
| 88 |
+
log_step(msg='Removing directories (KEEPING TESTING)', status="Notice")
|
| 89 |
+
else:
|
| 90 |
+
log_step(msg='Removing unneeded directories')
|
| 91 |
+
test_path = os.path.join(DIR_WEB, 'comfyui', 'tests')
|
| 92 |
+
if os.path.exists(test_path):
|
| 93 |
+
rmtree(test_path)
|
| 94 |
+
rmtree(os.path.join(DIR_WEB, 'comfyui', 'testing'))
|
| 95 |
+
# Always remove the dummy scripts_comfy directory
|
| 96 |
+
rmtree(os.path.join(DIR_WEB, 'scripts_comfy'))
|
| 97 |
+
log_step(status="Done")
|
| 98 |
+
|
| 99 |
+
scsss = glob(os.path.join(DIR_SRC_WEB, "**", "*.scss"), recursive=True)
|
| 100 |
+
log_step(msg=f'SASS for {len(scsss)} files')
|
| 101 |
+
scsss = [i.replace(THIS_DIR, '.') for i in scsss]
|
| 102 |
+
cmds = ["node", "./node_modules/sass/sass"]
|
| 103 |
+
for scss in scsss:
|
| 104 |
+
out = scss.replace('src_web', 'web').replace('.scss', '.css')
|
| 105 |
+
cmds.append(f'{scss}:{out}')
|
| 106 |
+
cmds.append('--no-source-map')
|
| 107 |
+
checked = subprocess.run(cmds, check=True)
|
| 108 |
+
log_step(status="Done")
|
| 109 |
+
|
| 110 |
+
# Handle the common directories. Because ComfyUI loads under /extensions/rgthree-comfy we can't
|
| 111 |
+
# easily share sources outside of the `DIR_WEB_COMFYUI` _and_ allow typescript to resolve them in
|
| 112 |
+
# src view, so we set the path in the tsconfig to map an import of "rgthree/common" to the
|
| 113 |
+
# "src_web/common" directory, but then need to rewrite the comfyui JS files to load from
|
| 114 |
+
# "../../rgthree/common" (which we map correctly in rgthree_server.py).
|
| 115 |
+
log_step(msg='Cleaning Imports')
|
| 116 |
+
js_files = glob(os.path.join(DIR_WEB, '**', '*.js'), recursive=True)
|
| 117 |
+
for file in js_files:
|
| 118 |
+
rel_path = file.replace(f'{DIR_WEB}/', "")
|
| 119 |
+
with open(file, 'r', encoding="utf-8") as f:
|
| 120 |
+
filedata = f.read()
|
| 121 |
+
num = rel_path.count(os.sep)
|
| 122 |
+
if rel_path.startswith('comfyui'):
|
| 123 |
+
filedata = re.sub(r'(from\s+["\'])rgthree/', f'\\1{"../" * (num + 1)}rgthree/', filedata)
|
| 124 |
+
filedata = re.sub(r'(from\s+["\'])scripts/', f'\\1{"../" * (num + 1)}scripts/', filedata)
|
| 125 |
+
# Dynamic Imports
|
| 126 |
+
filedata = re.sub(r'(import\(["\'])rgthree/', f'\\1{"../" * (num + 1)}rgthree/', filedata)
|
| 127 |
+
else:
|
| 128 |
+
filedata = re.sub(r'(from\s+["\'])rgthree/', f'\\1{"../" * num}', filedata)
|
| 129 |
+
filedata = re.sub(r'(from\s+["\'])scripts/', f'\\1{"../" * (num + 1)}scripts/', filedata)
|
| 130 |
+
# Dynamic Imports
|
| 131 |
+
filedata = re.sub(r'(import\(["\'])rgthree/', f'\\1{"../" * num}', filedata)
|
| 132 |
+
|
| 133 |
+
filedata, n = re.subn(r'(\s*from [\'"](?!.*[.]js[\'"]).*?)([\'"];)', '\\1.js\\2', filedata)
|
| 134 |
+
if n > 0:
|
| 135 |
+
filename = file.split('rgthree-comfy')[1]
|
| 136 |
+
log_step_info(
|
| 137 |
+
f'{filename} has {n} import{"s" if n > 1 else ""} that do not end in ".js"', 'warn')
|
| 138 |
+
with open(file, 'w', encoding="utf-8") as f:
|
| 139 |
+
f.write(filedata)
|
| 140 |
+
log_step(status="Done")
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
if __name__ == "__main__":
|
| 144 |
+
parser = argparse.ArgumentParser()
|
| 145 |
+
parser.add_argument("-t", "--no-tests", default=False, action="store_true")
|
| 146 |
+
parser.add_argument("-f", "--fix", default=False, action="store_true")
|
| 147 |
+
args = parser.parse_args()
|
| 148 |
+
|
| 149 |
+
start = time.time()
|
| 150 |
+
build(without_tests=args.no_tests, fix=args.fix)
|
| 151 |
+
print(f'Finished all in {round(time.time() - start, 3)}s')
|
custom_nodes/rgthree-comfy/__commit__.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
import subprocess
|
| 4 |
+
import os
|
| 5 |
+
import re
|
| 6 |
+
import datetime
|
| 7 |
+
import time
|
| 8 |
+
import argparse
|
| 9 |
+
|
| 10 |
+
from __build__ import build, log_step, log_step_info
|
| 11 |
+
|
| 12 |
+
_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 13 |
+
_FILE_PY_PROJECT = os.path.join(_THIS_DIR, 'pyproject.toml')
|
| 14 |
+
|
| 15 |
+
parser = argparse.ArgumentParser()
|
| 16 |
+
parser.add_argument(
|
| 17 |
+
"-m", "--message", help="The git commit message", required=True, action="store", type=str
|
| 18 |
+
)
|
| 19 |
+
args = parser.parse_args()
|
| 20 |
+
|
| 21 |
+
start = time.time()
|
| 22 |
+
build()
|
| 23 |
+
|
| 24 |
+
log_step(msg='Updating version in pyproject.toml')
|
| 25 |
+
py_project = ''
|
| 26 |
+
with open(_FILE_PY_PROJECT, "r", encoding='utf-8') as f:
|
| 27 |
+
py_project = f.read()
|
| 28 |
+
|
| 29 |
+
version = re.search(r'^\s*version\s*=\s*"(.*?)"', py_project, flags=re.MULTILINE)
|
| 30 |
+
version_old = version[1]
|
| 31 |
+
|
| 32 |
+
now = datetime.datetime.now()
|
| 33 |
+
version_new = version_old.split('.')
|
| 34 |
+
version_new[-1] = f'{str(now.year)[2:]}{now.month:02}{now.day:02}{now.hour:02}{now.minute:02}'
|
| 35 |
+
version_new = '.'.join(version_new)
|
| 36 |
+
|
| 37 |
+
log_step_info(f'Updating from v{version_old} to v{version_new}')
|
| 38 |
+
py_project = py_project.replace(version_old, version_new)
|
| 39 |
+
with open(_FILE_PY_PROJECT, "w", encoding='utf-8') as f:
|
| 40 |
+
f.write(py_project)
|
| 41 |
+
log_step(status="Done")
|
| 42 |
+
|
| 43 |
+
log_step('Running git add')
|
| 44 |
+
process = subprocess.Popen(['git', 'add', '.'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
| 45 |
+
stdout, stderr = process.communicate()
|
| 46 |
+
log_step(status="Done")
|
| 47 |
+
|
| 48 |
+
log_step('Running git commit')
|
| 49 |
+
process = subprocess.Popen(['git', 'commit', '-a', '-v', '-m', args.message],
|
| 50 |
+
stdout=subprocess.PIPE,
|
| 51 |
+
stderr=subprocess.PIPE)
|
| 52 |
+
stdout, stderr = process.communicate()
|
| 53 |
+
log_step(status="Done")
|
| 54 |
+
|
| 55 |
+
print(f'Finished all in {round(time.time() - start, 3)}s')
|
custom_nodes/rgthree-comfy/__init__.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
@author: rgthree
|
| 3 |
+
@title: Comfy Nodes
|
| 4 |
+
@nickname: rgthree
|
| 5 |
+
@description: A bunch of nodes I created that I also find useful.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from glob import glob
|
| 9 |
+
import json
|
| 10 |
+
import os
|
| 11 |
+
import shutil
|
| 12 |
+
import re
|
| 13 |
+
import random
|
| 14 |
+
|
| 15 |
+
import execution
|
| 16 |
+
|
| 17 |
+
from .py.log import log
|
| 18 |
+
from .py.config import get_config_value
|
| 19 |
+
from .py.server.rgthree_server import *
|
| 20 |
+
|
| 21 |
+
from .py.context import RgthreeContext
|
| 22 |
+
from .py.context_switch import RgthreeContextSwitch
|
| 23 |
+
from .py.context_switch_big import RgthreeContextSwitchBig
|
| 24 |
+
from .py.display_any import RgthreeDisplayAny, RgthreeDisplayInt
|
| 25 |
+
from .py.lora_stack import RgthreeLoraLoaderStack
|
| 26 |
+
from .py.seed import RgthreeSeed
|
| 27 |
+
from .py.sdxl_empty_latent_image import RgthreeSDXLEmptyLatentImage
|
| 28 |
+
from .py.power_prompt import RgthreePowerPrompt
|
| 29 |
+
from .py.power_prompt_simple import RgthreePowerPromptSimple
|
| 30 |
+
from .py.image_inset_crop import RgthreeImageInsetCrop
|
| 31 |
+
from .py.context_big import RgthreeBigContext
|
| 32 |
+
from .py.dynamic_context import RgthreeDynamicContext
|
| 33 |
+
from .py.dynamic_context_switch import RgthreeDynamicContextSwitch
|
| 34 |
+
from .py.ksampler_config import RgthreeKSamplerConfig
|
| 35 |
+
from .py.sdxl_power_prompt_postive import RgthreeSDXLPowerPromptPositive
|
| 36 |
+
from .py.sdxl_power_prompt_simple import RgthreeSDXLPowerPromptSimple
|
| 37 |
+
from .py.any_switch import RgthreeAnySwitch
|
| 38 |
+
from .py.context_merge import RgthreeContextMerge
|
| 39 |
+
from .py.context_merge_big import RgthreeContextMergeBig
|
| 40 |
+
from .py.image_comparer import RgthreeImageComparer
|
| 41 |
+
from .py.power_lora_loader import RgthreePowerLoraLoader
|
| 42 |
+
from .py.power_primitive import RgthreePowerPrimitive
|
| 43 |
+
from .py.image_or_latent_size import RgthreeImageOrLatentSize
|
| 44 |
+
from .py.image_resize import RgthreeImageResize
|
| 45 |
+
from .py.power_puter import RgthreePowerPuter
|
| 46 |
+
|
| 47 |
+
NODE_CLASS_MAPPINGS = {
|
| 48 |
+
RgthreeBigContext.NAME: RgthreeBigContext,
|
| 49 |
+
RgthreeContext.NAME: RgthreeContext,
|
| 50 |
+
RgthreeContextSwitch.NAME: RgthreeContextSwitch,
|
| 51 |
+
RgthreeContextSwitchBig.NAME: RgthreeContextSwitchBig,
|
| 52 |
+
RgthreeContextMerge.NAME: RgthreeContextMerge,
|
| 53 |
+
RgthreeContextMergeBig.NAME: RgthreeContextMergeBig,
|
| 54 |
+
RgthreeDisplayInt.NAME: RgthreeDisplayInt,
|
| 55 |
+
RgthreeDisplayAny.NAME: RgthreeDisplayAny,
|
| 56 |
+
RgthreeLoraLoaderStack.NAME: RgthreeLoraLoaderStack,
|
| 57 |
+
RgthreeSeed.NAME: RgthreeSeed,
|
| 58 |
+
RgthreeImageInsetCrop.NAME: RgthreeImageInsetCrop,
|
| 59 |
+
RgthreePowerPrompt.NAME: RgthreePowerPrompt,
|
| 60 |
+
RgthreePowerPromptSimple.NAME: RgthreePowerPromptSimple,
|
| 61 |
+
RgthreeKSamplerConfig.NAME: RgthreeKSamplerConfig,
|
| 62 |
+
RgthreeSDXLEmptyLatentImage.NAME: RgthreeSDXLEmptyLatentImage,
|
| 63 |
+
RgthreeSDXLPowerPromptPositive.NAME: RgthreeSDXLPowerPromptPositive,
|
| 64 |
+
RgthreeSDXLPowerPromptSimple.NAME: RgthreeSDXLPowerPromptSimple,
|
| 65 |
+
RgthreeAnySwitch.NAME: RgthreeAnySwitch,
|
| 66 |
+
RgthreeImageComparer.NAME: RgthreeImageComparer,
|
| 67 |
+
RgthreePowerLoraLoader.NAME: RgthreePowerLoraLoader,
|
| 68 |
+
RgthreePowerPrimitive.NAME: RgthreePowerPrimitive,
|
| 69 |
+
RgthreeImageOrLatentSize.NAME: RgthreeImageOrLatentSize,
|
| 70 |
+
RgthreeImageResize.NAME: RgthreeImageResize,
|
| 71 |
+
RgthreePowerPuter.NAME: RgthreePowerPuter,
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
if get_config_value('unreleased.dynamic_context.enabled') is True:
|
| 75 |
+
NODE_CLASS_MAPPINGS[RgthreeDynamicContext.NAME] = RgthreeDynamicContext
|
| 76 |
+
NODE_CLASS_MAPPINGS[RgthreeDynamicContextSwitch.NAME] = RgthreeDynamicContextSwitch
|
| 77 |
+
|
| 78 |
+
# WEB_DIRECTORY is the comfyui nodes directory that ComfyUI will link and auto-load.
|
| 79 |
+
WEB_DIRECTORY = "./web/comfyui"
|
| 80 |
+
|
| 81 |
+
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 82 |
+
DIR_WEB = os.path.abspath(f'{THIS_DIR}/{WEB_DIRECTORY}')
|
| 83 |
+
DIR_PY = os.path.abspath(f'{THIS_DIR}/py')
|
| 84 |
+
|
| 85 |
+
# remove old directories
|
| 86 |
+
OLD_DIRS = [
|
| 87 |
+
os.path.abspath(f'{THIS_DIR}/../../web/extensions/rgthree'),
|
| 88 |
+
os.path.abspath(f'{THIS_DIR}/../../web/extensions/rgthree-comfy'),
|
| 89 |
+
]
|
| 90 |
+
for old_dir in OLD_DIRS:
|
| 91 |
+
if os.path.exists(old_dir):
|
| 92 |
+
shutil.rmtree(old_dir)
|
| 93 |
+
|
| 94 |
+
__all__ = ['NODE_CLASS_MAPPINGS', 'WEB_DIRECTORY']
|
| 95 |
+
|
| 96 |
+
NOT_NODES = ['constants', 'log', 'utils', 'rgthree', 'rgthree_server', 'image_clipbaord', 'config']
|
| 97 |
+
|
| 98 |
+
nodes = []
|
| 99 |
+
for file in glob(os.path.join(DIR_PY, '*.py')) + glob(os.path.join(DIR_WEB, '*.js')):
|
| 100 |
+
name = os.path.splitext(os.path.basename(file))[0]
|
| 101 |
+
if name in NOT_NODES or name in nodes:
|
| 102 |
+
continue
|
| 103 |
+
if name.startswith('_') or name.startswith('base') or 'utils' in name:
|
| 104 |
+
continue
|
| 105 |
+
nodes.append(name)
|
| 106 |
+
if name == 'display_any':
|
| 107 |
+
nodes.append('display_int')
|
| 108 |
+
|
| 109 |
+
print()
|
| 110 |
+
adjs = ['exciting', 'extraordinary', 'epic', 'fantastic', 'magnificent']
|
| 111 |
+
log(f'Loaded {len(nodes)} {random.choice(adjs)} nodes. 🎉', color='BRIGHT_GREEN')
|
| 112 |
+
print()
|
| 113 |
+
|
| 114 |
+
if get_config_value('announcements.comfy-nodes-20.incompatible', True):
|
| 115 |
+
message = (
|
| 116 |
+
"ComfyUI's new Node 2.0 rendering may be incompatible with some rgthree-comfy nodes "
|
| 117 |
+
"and features, breaking some rendering as well as losing the ability to "
|
| 118 |
+
"access a node's properties (a vital part of many nodes). It also appears to run MUCH more "
|
| 119 |
+
"slowly spiking CPU usage and causing jankiness and unresponsiveness, especially with large "
|
| 120 |
+
"workflows. Personally I am not planning to use the new Nodes 2.0 and, unfortunately, am not "
|
| 121 |
+
"able to invest the time to investigate and overhaul rgthree-comfy where needed. "
|
| 122 |
+
"If you have issues when Nodes 2.0 is enabled, I'd urge you to switch it off as well and "
|
| 123 |
+
"join me in hoping ComfyUI is not planning to deprecate the existing, stable canvas rendering "
|
| 124 |
+
"all together.\n"
|
| 125 |
+
)
|
| 126 |
+
log(message, color='YELLOW', id='announcements.comfy-nodes-20.incompatible', at_most_secs=60)
|
custom_nodes/rgthree-comfy/__update_comfy__.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
# A nicer output for git pulling custom nodes (and ComfyUI).
|
| 4 |
+
# Quick shell version: ls | xargs -I % sh -c 'echo; echo %; git -C % pull'
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
from subprocess import Popen, PIPE, STDOUT
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def pull_path(path):
|
| 11 |
+
p = Popen(["git", "-C", path, "pull"], stdout=PIPE, stderr=STDOUT)
|
| 12 |
+
output, error = p.communicate()
|
| 13 |
+
return output.decode()
|
| 14 |
+
|
| 15 |
+
THIS_DIR=os.path.dirname(os.path.abspath(__file__))
|
| 16 |
+
|
| 17 |
+
def show_output(output):
|
| 18 |
+
if output.startswith('Already up to date'):
|
| 19 |
+
print(f' \33[32m🗸 {output}\33[0m', end ='')
|
| 20 |
+
elif output.startswith('error:'):
|
| 21 |
+
print(f' \33[31m🞫 Error.\33[0m \n {output}')
|
| 22 |
+
else:
|
| 23 |
+
print(f' \33[33m🡅 Needs update.\33[0m \n {output}', end='')
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
os.chdir(THIS_DIR)
|
| 27 |
+
os.chdir("../")
|
| 28 |
+
|
| 29 |
+
# Get the list or custom nodes, so we can format the output a little more nicely.
|
| 30 |
+
custom_extensions = []
|
| 31 |
+
custom_extensions_name_max = 0
|
| 32 |
+
for directory in os.listdir(os.getcwd()):
|
| 33 |
+
if os.path.isdir(directory) and directory != "__pycache__": #and directory != "rgthree-comfy" :
|
| 34 |
+
custom_extensions.append({
|
| 35 |
+
'directory': directory
|
| 36 |
+
})
|
| 37 |
+
if len(directory) > custom_extensions_name_max:
|
| 38 |
+
custom_extensions_name_max = len(directory)
|
| 39 |
+
|
| 40 |
+
if len(custom_extensions) == 0:
|
| 41 |
+
custom_extensions_name_max = 15
|
| 42 |
+
else:
|
| 43 |
+
custom_extensions_name_max += 6
|
| 44 |
+
|
| 45 |
+
# Update ComfyUI itself.
|
| 46 |
+
label = "{0:.<{max}}".format('Updating ComfyUI ', max=custom_extensions_name_max)
|
| 47 |
+
print(label, end = '')
|
| 48 |
+
show_output(pull_path('../'))
|
| 49 |
+
|
| 50 |
+
# If we have custom nodes, update them as well.
|
| 51 |
+
if len(custom_extensions) > 0:
|
| 52 |
+
print(f'\nUpdating custom_nodes ({len(custom_extensions)}):')
|
| 53 |
+
for custom_extension in custom_extensions:
|
| 54 |
+
directory = custom_extension['directory']
|
| 55 |
+
label = "{0:.<{max}}".format(f'🗀 {directory} ', max=custom_extensions_name_max)
|
| 56 |
+
print(label, end = '')
|
| 57 |
+
show_output(pull_path(directory))
|
custom_nodes/rgthree-comfy/docs/rgthree_advanced.png
ADDED
|
Git LFS Details
|
custom_nodes/rgthree-comfy/docs/rgthree_advanced_metadata.png
ADDED
|
Git LFS Details
|
custom_nodes/rgthree-comfy/docs/rgthree_context.png
ADDED
|
Git LFS Details
|
custom_nodes/rgthree-comfy/docs/rgthree_context_metadata.png
ADDED
|
Git LFS Details
|
custom_nodes/rgthree-comfy/docs/rgthree_router.png
ADDED
|
custom_nodes/rgthree-comfy/docs/rgthree_seed.png
ADDED
|
custom_nodes/rgthree-comfy/package-lock.json
ADDED
|
@@ -0,0 +1,568 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "rgthree-comfy",
|
| 3 |
+
"lockfileVersion": 3,
|
| 4 |
+
"requires": true,
|
| 5 |
+
"packages": {
|
| 6 |
+
"": {
|
| 7 |
+
"devDependencies": {
|
| 8 |
+
"@comfyorg/comfyui-frontend-types": "^1.32.4",
|
| 9 |
+
"prettier": "^3.3.3",
|
| 10 |
+
"sass": "^1.77.8",
|
| 11 |
+
"typescript": "^5.5.4",
|
| 12 |
+
"web-tree-sitter": "0.25.6"
|
| 13 |
+
}
|
| 14 |
+
},
|
| 15 |
+
"node_modules/@babel/helper-string-parser": {
|
| 16 |
+
"version": "7.25.9",
|
| 17 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
|
| 18 |
+
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
|
| 19 |
+
"dev": true,
|
| 20 |
+
"peer": true,
|
| 21 |
+
"engines": {
|
| 22 |
+
"node": ">=6.9.0"
|
| 23 |
+
}
|
| 24 |
+
},
|
| 25 |
+
"node_modules/@babel/helper-validator-identifier": {
|
| 26 |
+
"version": "7.25.9",
|
| 27 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
|
| 28 |
+
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
|
| 29 |
+
"dev": true,
|
| 30 |
+
"peer": true,
|
| 31 |
+
"engines": {
|
| 32 |
+
"node": ">=6.9.0"
|
| 33 |
+
}
|
| 34 |
+
},
|
| 35 |
+
"node_modules/@babel/parser": {
|
| 36 |
+
"version": "7.27.0",
|
| 37 |
+
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
|
| 38 |
+
"integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
|
| 39 |
+
"dev": true,
|
| 40 |
+
"peer": true,
|
| 41 |
+
"dependencies": {
|
| 42 |
+
"@babel/types": "^7.27.0"
|
| 43 |
+
},
|
| 44 |
+
"bin": {
|
| 45 |
+
"parser": "bin/babel-parser.js"
|
| 46 |
+
},
|
| 47 |
+
"engines": {
|
| 48 |
+
"node": ">=6.0.0"
|
| 49 |
+
}
|
| 50 |
+
},
|
| 51 |
+
"node_modules/@babel/types": {
|
| 52 |
+
"version": "7.27.0",
|
| 53 |
+
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
|
| 54 |
+
"integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
|
| 55 |
+
"dev": true,
|
| 56 |
+
"peer": true,
|
| 57 |
+
"dependencies": {
|
| 58 |
+
"@babel/helper-string-parser": "^7.25.9",
|
| 59 |
+
"@babel/helper-validator-identifier": "^7.25.9"
|
| 60 |
+
},
|
| 61 |
+
"engines": {
|
| 62 |
+
"node": ">=6.9.0"
|
| 63 |
+
}
|
| 64 |
+
},
|
| 65 |
+
"node_modules/@comfyorg/comfyui-frontend-types": {
|
| 66 |
+
"version": "1.32.4",
|
| 67 |
+
"resolved": "https://registry.npmjs.org/@comfyorg/comfyui-frontend-types/-/comfyui-frontend-types-1.32.4.tgz",
|
| 68 |
+
"integrity": "sha512-9EBFwQPaSMLI151caWN5nA0qVmY5S9BR9lHP5NboPnTsmkFwnWdrAPadAnafwmZcK40ZWntKJZxO0Uuju0jw/w==",
|
| 69 |
+
"dev": true,
|
| 70 |
+
"peerDependencies": {
|
| 71 |
+
"vue": "^3.5.13",
|
| 72 |
+
"zod": "^3.23.8"
|
| 73 |
+
}
|
| 74 |
+
},
|
| 75 |
+
"node_modules/@jridgewell/sourcemap-codec": {
|
| 76 |
+
"version": "1.5.0",
|
| 77 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
| 78 |
+
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
|
| 79 |
+
"dev": true,
|
| 80 |
+
"peer": true
|
| 81 |
+
},
|
| 82 |
+
"node_modules/@vue/compiler-core": {
|
| 83 |
+
"version": "3.5.13",
|
| 84 |
+
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
|
| 85 |
+
"integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
|
| 86 |
+
"dev": true,
|
| 87 |
+
"peer": true,
|
| 88 |
+
"dependencies": {
|
| 89 |
+
"@babel/parser": "^7.25.3",
|
| 90 |
+
"@vue/shared": "3.5.13",
|
| 91 |
+
"entities": "^4.5.0",
|
| 92 |
+
"estree-walker": "^2.0.2",
|
| 93 |
+
"source-map-js": "^1.2.0"
|
| 94 |
+
}
|
| 95 |
+
},
|
| 96 |
+
"node_modules/@vue/compiler-dom": {
|
| 97 |
+
"version": "3.5.13",
|
| 98 |
+
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
|
| 99 |
+
"integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
|
| 100 |
+
"dev": true,
|
| 101 |
+
"peer": true,
|
| 102 |
+
"dependencies": {
|
| 103 |
+
"@vue/compiler-core": "3.5.13",
|
| 104 |
+
"@vue/shared": "3.5.13"
|
| 105 |
+
}
|
| 106 |
+
},
|
| 107 |
+
"node_modules/@vue/compiler-sfc": {
|
| 108 |
+
"version": "3.5.13",
|
| 109 |
+
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz",
|
| 110 |
+
"integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==",
|
| 111 |
+
"dev": true,
|
| 112 |
+
"peer": true,
|
| 113 |
+
"dependencies": {
|
| 114 |
+
"@babel/parser": "^7.25.3",
|
| 115 |
+
"@vue/compiler-core": "3.5.13",
|
| 116 |
+
"@vue/compiler-dom": "3.5.13",
|
| 117 |
+
"@vue/compiler-ssr": "3.5.13",
|
| 118 |
+
"@vue/shared": "3.5.13",
|
| 119 |
+
"estree-walker": "^2.0.2",
|
| 120 |
+
"magic-string": "^0.30.11",
|
| 121 |
+
"postcss": "^8.4.48",
|
| 122 |
+
"source-map-js": "^1.2.0"
|
| 123 |
+
}
|
| 124 |
+
},
|
| 125 |
+
"node_modules/@vue/compiler-ssr": {
|
| 126 |
+
"version": "3.5.13",
|
| 127 |
+
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz",
|
| 128 |
+
"integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==",
|
| 129 |
+
"dev": true,
|
| 130 |
+
"peer": true,
|
| 131 |
+
"dependencies": {
|
| 132 |
+
"@vue/compiler-dom": "3.5.13",
|
| 133 |
+
"@vue/shared": "3.5.13"
|
| 134 |
+
}
|
| 135 |
+
},
|
| 136 |
+
"node_modules/@vue/reactivity": {
|
| 137 |
+
"version": "3.5.13",
|
| 138 |
+
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz",
|
| 139 |
+
"integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==",
|
| 140 |
+
"dev": true,
|
| 141 |
+
"peer": true,
|
| 142 |
+
"dependencies": {
|
| 143 |
+
"@vue/shared": "3.5.13"
|
| 144 |
+
}
|
| 145 |
+
},
|
| 146 |
+
"node_modules/@vue/runtime-core": {
|
| 147 |
+
"version": "3.5.13",
|
| 148 |
+
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz",
|
| 149 |
+
"integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==",
|
| 150 |
+
"dev": true,
|
| 151 |
+
"peer": true,
|
| 152 |
+
"dependencies": {
|
| 153 |
+
"@vue/reactivity": "3.5.13",
|
| 154 |
+
"@vue/shared": "3.5.13"
|
| 155 |
+
}
|
| 156 |
+
},
|
| 157 |
+
"node_modules/@vue/runtime-dom": {
|
| 158 |
+
"version": "3.5.13",
|
| 159 |
+
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz",
|
| 160 |
+
"integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==",
|
| 161 |
+
"dev": true,
|
| 162 |
+
"peer": true,
|
| 163 |
+
"dependencies": {
|
| 164 |
+
"@vue/reactivity": "3.5.13",
|
| 165 |
+
"@vue/runtime-core": "3.5.13",
|
| 166 |
+
"@vue/shared": "3.5.13",
|
| 167 |
+
"csstype": "^3.1.3"
|
| 168 |
+
}
|
| 169 |
+
},
|
| 170 |
+
"node_modules/@vue/server-renderer": {
|
| 171 |
+
"version": "3.5.13",
|
| 172 |
+
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz",
|
| 173 |
+
"integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==",
|
| 174 |
+
"dev": true,
|
| 175 |
+
"peer": true,
|
| 176 |
+
"dependencies": {
|
| 177 |
+
"@vue/compiler-ssr": "3.5.13",
|
| 178 |
+
"@vue/shared": "3.5.13"
|
| 179 |
+
},
|
| 180 |
+
"peerDependencies": {
|
| 181 |
+
"vue": "3.5.13"
|
| 182 |
+
}
|
| 183 |
+
},
|
| 184 |
+
"node_modules/@vue/shared": {
|
| 185 |
+
"version": "3.5.13",
|
| 186 |
+
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz",
|
| 187 |
+
"integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
|
| 188 |
+
"dev": true,
|
| 189 |
+
"peer": true
|
| 190 |
+
},
|
| 191 |
+
"node_modules/anymatch": {
|
| 192 |
+
"version": "3.1.3",
|
| 193 |
+
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
| 194 |
+
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
| 195 |
+
"dev": true,
|
| 196 |
+
"dependencies": {
|
| 197 |
+
"normalize-path": "^3.0.0",
|
| 198 |
+
"picomatch": "^2.0.4"
|
| 199 |
+
},
|
| 200 |
+
"engines": {
|
| 201 |
+
"node": ">= 8"
|
| 202 |
+
}
|
| 203 |
+
},
|
| 204 |
+
"node_modules/binary-extensions": {
|
| 205 |
+
"version": "2.2.0",
|
| 206 |
+
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
| 207 |
+
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
| 208 |
+
"dev": true,
|
| 209 |
+
"engines": {
|
| 210 |
+
"node": ">=8"
|
| 211 |
+
}
|
| 212 |
+
},
|
| 213 |
+
"node_modules/braces": {
|
| 214 |
+
"version": "3.0.3",
|
| 215 |
+
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
| 216 |
+
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
| 217 |
+
"dev": true,
|
| 218 |
+
"dependencies": {
|
| 219 |
+
"fill-range": "^7.1.1"
|
| 220 |
+
},
|
| 221 |
+
"engines": {
|
| 222 |
+
"node": ">=8"
|
| 223 |
+
}
|
| 224 |
+
},
|
| 225 |
+
"node_modules/chokidar": {
|
| 226 |
+
"version": "3.5.3",
|
| 227 |
+
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
| 228 |
+
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
|
| 229 |
+
"dev": true,
|
| 230 |
+
"funding": [
|
| 231 |
+
{
|
| 232 |
+
"type": "individual",
|
| 233 |
+
"url": "https://paulmillr.com/funding/"
|
| 234 |
+
}
|
| 235 |
+
],
|
| 236 |
+
"dependencies": {
|
| 237 |
+
"anymatch": "~3.1.2",
|
| 238 |
+
"braces": "~3.0.2",
|
| 239 |
+
"glob-parent": "~5.1.2",
|
| 240 |
+
"is-binary-path": "~2.1.0",
|
| 241 |
+
"is-glob": "~4.0.1",
|
| 242 |
+
"normalize-path": "~3.0.0",
|
| 243 |
+
"readdirp": "~3.6.0"
|
| 244 |
+
},
|
| 245 |
+
"engines": {
|
| 246 |
+
"node": ">= 8.10.0"
|
| 247 |
+
},
|
| 248 |
+
"optionalDependencies": {
|
| 249 |
+
"fsevents": "~2.3.2"
|
| 250 |
+
}
|
| 251 |
+
},
|
| 252 |
+
"node_modules/csstype": {
|
| 253 |
+
"version": "3.1.3",
|
| 254 |
+
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
| 255 |
+
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
| 256 |
+
"dev": true,
|
| 257 |
+
"peer": true
|
| 258 |
+
},
|
| 259 |
+
"node_modules/entities": {
|
| 260 |
+
"version": "4.5.0",
|
| 261 |
+
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
| 262 |
+
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
| 263 |
+
"dev": true,
|
| 264 |
+
"peer": true,
|
| 265 |
+
"engines": {
|
| 266 |
+
"node": ">=0.12"
|
| 267 |
+
},
|
| 268 |
+
"funding": {
|
| 269 |
+
"url": "https://github.com/fb55/entities?sponsor=1"
|
| 270 |
+
}
|
| 271 |
+
},
|
| 272 |
+
"node_modules/estree-walker": {
|
| 273 |
+
"version": "2.0.2",
|
| 274 |
+
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
| 275 |
+
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
| 276 |
+
"dev": true,
|
| 277 |
+
"peer": true
|
| 278 |
+
},
|
| 279 |
+
"node_modules/fill-range": {
|
| 280 |
+
"version": "7.1.1",
|
| 281 |
+
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
| 282 |
+
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
| 283 |
+
"dev": true,
|
| 284 |
+
"dependencies": {
|
| 285 |
+
"to-regex-range": "^5.0.1"
|
| 286 |
+
},
|
| 287 |
+
"engines": {
|
| 288 |
+
"node": ">=8"
|
| 289 |
+
}
|
| 290 |
+
},
|
| 291 |
+
"node_modules/fsevents": {
|
| 292 |
+
"version": "2.3.3",
|
| 293 |
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 294 |
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 295 |
+
"dev": true,
|
| 296 |
+
"hasInstallScript": true,
|
| 297 |
+
"optional": true,
|
| 298 |
+
"os": [
|
| 299 |
+
"darwin"
|
| 300 |
+
],
|
| 301 |
+
"engines": {
|
| 302 |
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 303 |
+
}
|
| 304 |
+
},
|
| 305 |
+
"node_modules/glob-parent": {
|
| 306 |
+
"version": "5.1.2",
|
| 307 |
+
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
| 308 |
+
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
| 309 |
+
"dev": true,
|
| 310 |
+
"dependencies": {
|
| 311 |
+
"is-glob": "^4.0.1"
|
| 312 |
+
},
|
| 313 |
+
"engines": {
|
| 314 |
+
"node": ">= 6"
|
| 315 |
+
}
|
| 316 |
+
},
|
| 317 |
+
"node_modules/immutable": {
|
| 318 |
+
"version": "4.3.5",
|
| 319 |
+
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz",
|
| 320 |
+
"integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==",
|
| 321 |
+
"dev": true
|
| 322 |
+
},
|
| 323 |
+
"node_modules/is-binary-path": {
|
| 324 |
+
"version": "2.1.0",
|
| 325 |
+
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
| 326 |
+
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
| 327 |
+
"dev": true,
|
| 328 |
+
"dependencies": {
|
| 329 |
+
"binary-extensions": "^2.0.0"
|
| 330 |
+
},
|
| 331 |
+
"engines": {
|
| 332 |
+
"node": ">=8"
|
| 333 |
+
}
|
| 334 |
+
},
|
| 335 |
+
"node_modules/is-extglob": {
|
| 336 |
+
"version": "2.1.1",
|
| 337 |
+
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
| 338 |
+
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
| 339 |
+
"dev": true,
|
| 340 |
+
"engines": {
|
| 341 |
+
"node": ">=0.10.0"
|
| 342 |
+
}
|
| 343 |
+
},
|
| 344 |
+
"node_modules/is-glob": {
|
| 345 |
+
"version": "4.0.3",
|
| 346 |
+
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
| 347 |
+
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
| 348 |
+
"dev": true,
|
| 349 |
+
"dependencies": {
|
| 350 |
+
"is-extglob": "^2.1.1"
|
| 351 |
+
},
|
| 352 |
+
"engines": {
|
| 353 |
+
"node": ">=0.10.0"
|
| 354 |
+
}
|
| 355 |
+
},
|
| 356 |
+
"node_modules/is-number": {
|
| 357 |
+
"version": "7.0.0",
|
| 358 |
+
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
| 359 |
+
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
| 360 |
+
"dev": true,
|
| 361 |
+
"engines": {
|
| 362 |
+
"node": ">=0.12.0"
|
| 363 |
+
}
|
| 364 |
+
},
|
| 365 |
+
"node_modules/magic-string": {
|
| 366 |
+
"version": "0.30.17",
|
| 367 |
+
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
| 368 |
+
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
|
| 369 |
+
"dev": true,
|
| 370 |
+
"peer": true,
|
| 371 |
+
"dependencies": {
|
| 372 |
+
"@jridgewell/sourcemap-codec": "^1.5.0"
|
| 373 |
+
}
|
| 374 |
+
},
|
| 375 |
+
"node_modules/nanoid": {
|
| 376 |
+
"version": "3.3.11",
|
| 377 |
+
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
| 378 |
+
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
| 379 |
+
"dev": true,
|
| 380 |
+
"funding": [
|
| 381 |
+
{
|
| 382 |
+
"type": "github",
|
| 383 |
+
"url": "https://github.com/sponsors/ai"
|
| 384 |
+
}
|
| 385 |
+
],
|
| 386 |
+
"peer": true,
|
| 387 |
+
"bin": {
|
| 388 |
+
"nanoid": "bin/nanoid.cjs"
|
| 389 |
+
},
|
| 390 |
+
"engines": {
|
| 391 |
+
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
| 392 |
+
}
|
| 393 |
+
},
|
| 394 |
+
"node_modules/normalize-path": {
|
| 395 |
+
"version": "3.0.0",
|
| 396 |
+
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
| 397 |
+
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
| 398 |
+
"dev": true,
|
| 399 |
+
"engines": {
|
| 400 |
+
"node": ">=0.10.0"
|
| 401 |
+
}
|
| 402 |
+
},
|
| 403 |
+
"node_modules/picocolors": {
|
| 404 |
+
"version": "1.1.1",
|
| 405 |
+
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 406 |
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
| 407 |
+
"dev": true,
|
| 408 |
+
"peer": true
|
| 409 |
+
},
|
| 410 |
+
"node_modules/picomatch": {
|
| 411 |
+
"version": "2.3.1",
|
| 412 |
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
| 413 |
+
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
| 414 |
+
"dev": true,
|
| 415 |
+
"engines": {
|
| 416 |
+
"node": ">=8.6"
|
| 417 |
+
},
|
| 418 |
+
"funding": {
|
| 419 |
+
"url": "https://github.com/sponsors/jonschlinkert"
|
| 420 |
+
}
|
| 421 |
+
},
|
| 422 |
+
"node_modules/postcss": {
|
| 423 |
+
"version": "8.5.3",
|
| 424 |
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
| 425 |
+
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
|
| 426 |
+
"dev": true,
|
| 427 |
+
"funding": [
|
| 428 |
+
{
|
| 429 |
+
"type": "opencollective",
|
| 430 |
+
"url": "https://opencollective.com/postcss/"
|
| 431 |
+
},
|
| 432 |
+
{
|
| 433 |
+
"type": "tidelift",
|
| 434 |
+
"url": "https://tidelift.com/funding/github/npm/postcss"
|
| 435 |
+
},
|
| 436 |
+
{
|
| 437 |
+
"type": "github",
|
| 438 |
+
"url": "https://github.com/sponsors/ai"
|
| 439 |
+
}
|
| 440 |
+
],
|
| 441 |
+
"peer": true,
|
| 442 |
+
"dependencies": {
|
| 443 |
+
"nanoid": "^3.3.8",
|
| 444 |
+
"picocolors": "^1.1.1",
|
| 445 |
+
"source-map-js": "^1.2.1"
|
| 446 |
+
},
|
| 447 |
+
"engines": {
|
| 448 |
+
"node": "^10 || ^12 || >=14"
|
| 449 |
+
}
|
| 450 |
+
},
|
| 451 |
+
"node_modules/prettier": {
|
| 452 |
+
"version": "3.3.3",
|
| 453 |
+
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
|
| 454 |
+
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
|
| 455 |
+
"dev": true,
|
| 456 |
+
"bin": {
|
| 457 |
+
"prettier": "bin/prettier.cjs"
|
| 458 |
+
},
|
| 459 |
+
"engines": {
|
| 460 |
+
"node": ">=14"
|
| 461 |
+
},
|
| 462 |
+
"funding": {
|
| 463 |
+
"url": "https://github.com/prettier/prettier?sponsor=1"
|
| 464 |
+
}
|
| 465 |
+
},
|
| 466 |
+
"node_modules/readdirp": {
|
| 467 |
+
"version": "3.6.0",
|
| 468 |
+
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
| 469 |
+
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
| 470 |
+
"dev": true,
|
| 471 |
+
"dependencies": {
|
| 472 |
+
"picomatch": "^2.2.1"
|
| 473 |
+
},
|
| 474 |
+
"engines": {
|
| 475 |
+
"node": ">=8.10.0"
|
| 476 |
+
}
|
| 477 |
+
},
|
| 478 |
+
"node_modules/sass": {
|
| 479 |
+
"version": "1.77.8",
|
| 480 |
+
"resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz",
|
| 481 |
+
"integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==",
|
| 482 |
+
"dev": true,
|
| 483 |
+
"dependencies": {
|
| 484 |
+
"chokidar": ">=3.0.0 <4.0.0",
|
| 485 |
+
"immutable": "^4.0.0",
|
| 486 |
+
"source-map-js": ">=0.6.2 <2.0.0"
|
| 487 |
+
},
|
| 488 |
+
"bin": {
|
| 489 |
+
"sass": "sass.js"
|
| 490 |
+
},
|
| 491 |
+
"engines": {
|
| 492 |
+
"node": ">=14.0.0"
|
| 493 |
+
}
|
| 494 |
+
},
|
| 495 |
+
"node_modules/source-map-js": {
|
| 496 |
+
"version": "1.2.1",
|
| 497 |
+
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
| 498 |
+
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
| 499 |
+
"dev": true,
|
| 500 |
+
"engines": {
|
| 501 |
+
"node": ">=0.10.0"
|
| 502 |
+
}
|
| 503 |
+
},
|
| 504 |
+
"node_modules/to-regex-range": {
|
| 505 |
+
"version": "5.0.1",
|
| 506 |
+
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
| 507 |
+
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
| 508 |
+
"dev": true,
|
| 509 |
+
"dependencies": {
|
| 510 |
+
"is-number": "^7.0.0"
|
| 511 |
+
},
|
| 512 |
+
"engines": {
|
| 513 |
+
"node": ">=8.0"
|
| 514 |
+
}
|
| 515 |
+
},
|
| 516 |
+
"node_modules/typescript": {
|
| 517 |
+
"version": "5.9.2",
|
| 518 |
+
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
| 519 |
+
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
| 520 |
+
"dev": true,
|
| 521 |
+
"bin": {
|
| 522 |
+
"tsc": "bin/tsc",
|
| 523 |
+
"tsserver": "bin/tsserver"
|
| 524 |
+
},
|
| 525 |
+
"engines": {
|
| 526 |
+
"node": ">=14.17"
|
| 527 |
+
}
|
| 528 |
+
},
|
| 529 |
+
"node_modules/vue": {
|
| 530 |
+
"version": "3.5.13",
|
| 531 |
+
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz",
|
| 532 |
+
"integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
|
| 533 |
+
"dev": true,
|
| 534 |
+
"peer": true,
|
| 535 |
+
"dependencies": {
|
| 536 |
+
"@vue/compiler-dom": "3.5.13",
|
| 537 |
+
"@vue/compiler-sfc": "3.5.13",
|
| 538 |
+
"@vue/runtime-dom": "3.5.13",
|
| 539 |
+
"@vue/server-renderer": "3.5.13",
|
| 540 |
+
"@vue/shared": "3.5.13"
|
| 541 |
+
},
|
| 542 |
+
"peerDependencies": {
|
| 543 |
+
"typescript": "*"
|
| 544 |
+
},
|
| 545 |
+
"peerDependenciesMeta": {
|
| 546 |
+
"typescript": {
|
| 547 |
+
"optional": true
|
| 548 |
+
}
|
| 549 |
+
}
|
| 550 |
+
},
|
| 551 |
+
"node_modules/web-tree-sitter": {
|
| 552 |
+
"version": "0.25.6",
|
| 553 |
+
"resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.25.6.tgz",
|
| 554 |
+
"integrity": "sha512-WG+/YGbxw8r+rLlzzhV+OvgiOJCWdIpOucG3qBf3RCBFMkGDb1CanUi2BxCxjnkpzU3/hLWPT8VO5EKsMk9Fxg==",
|
| 555 |
+
"dev": true
|
| 556 |
+
},
|
| 557 |
+
"node_modules/zod": {
|
| 558 |
+
"version": "3.24.2",
|
| 559 |
+
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
|
| 560 |
+
"integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
|
| 561 |
+
"dev": true,
|
| 562 |
+
"peer": true,
|
| 563 |
+
"funding": {
|
| 564 |
+
"url": "https://github.com/sponsors/colinhacks"
|
| 565 |
+
}
|
| 566 |
+
}
|
| 567 |
+
}
|
| 568 |
+
}
|
custom_nodes/rgthree-comfy/package.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"devDependencies": {
|
| 3 |
+
"prettier": "^3.3.3",
|
| 4 |
+
"typescript": "^5.5.4",
|
| 5 |
+
"sass": "^1.77.8",
|
| 6 |
+
"@comfyorg/comfyui-frontend-types": "^1.32.4",
|
| 7 |
+
"web-tree-sitter": "0.25.6"
|
| 8 |
+
},
|
| 9 |
+
"scripts": {
|
| 10 |
+
"build": "./__build__.py || python .\\__build__.py"
|
| 11 |
+
}
|
| 12 |
+
}
|
custom_nodes/rgthree-comfy/pnpm-lock.yaml
ADDED
|
@@ -0,0 +1,507 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
lockfileVersion: '9.0'
|
| 2 |
+
|
| 3 |
+
settings:
|
| 4 |
+
autoInstallPeers: true
|
| 5 |
+
excludeLinksFromLockfile: false
|
| 6 |
+
|
| 7 |
+
importers:
|
| 8 |
+
|
| 9 |
+
.:
|
| 10 |
+
devDependencies:
|
| 11 |
+
'@comfyorg/comfyui-frontend-types':
|
| 12 |
+
specifier: ^1.32.4
|
| 13 |
+
version: 1.32.4(vue@3.5.24(typescript@5.9.3))(zod@3.25.76)
|
| 14 |
+
prettier:
|
| 15 |
+
specifier: ^3.3.3
|
| 16 |
+
version: 3.6.2
|
| 17 |
+
sass:
|
| 18 |
+
specifier: ^1.77.8
|
| 19 |
+
version: 1.93.3
|
| 20 |
+
typescript:
|
| 21 |
+
specifier: ^5.5.4
|
| 22 |
+
version: 5.9.3
|
| 23 |
+
web-tree-sitter:
|
| 24 |
+
specifier: 0.25.6
|
| 25 |
+
version: 0.25.6
|
| 26 |
+
|
| 27 |
+
packages:
|
| 28 |
+
|
| 29 |
+
'@babel/helper-string-parser@7.27.1':
|
| 30 |
+
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
|
| 31 |
+
engines: {node: '>=6.9.0'}
|
| 32 |
+
|
| 33 |
+
'@babel/helper-validator-identifier@7.28.5':
|
| 34 |
+
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
|
| 35 |
+
engines: {node: '>=6.9.0'}
|
| 36 |
+
|
| 37 |
+
'@babel/parser@7.28.5':
|
| 38 |
+
resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
|
| 39 |
+
engines: {node: '>=6.0.0'}
|
| 40 |
+
hasBin: true
|
| 41 |
+
|
| 42 |
+
'@babel/types@7.28.5':
|
| 43 |
+
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
|
| 44 |
+
engines: {node: '>=6.9.0'}
|
| 45 |
+
|
| 46 |
+
'@comfyorg/comfyui-frontend-types@1.32.4':
|
| 47 |
+
resolution: {integrity: sha512-9EBFwQPaSMLI151caWN5nA0qVmY5S9BR9lHP5NboPnTsmkFwnWdrAPadAnafwmZcK40ZWntKJZxO0Uuju0jw/w==}
|
| 48 |
+
peerDependencies:
|
| 49 |
+
vue: ^3.5.13
|
| 50 |
+
zod: ^3.23.8
|
| 51 |
+
|
| 52 |
+
'@jridgewell/sourcemap-codec@1.5.5':
|
| 53 |
+
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
| 54 |
+
|
| 55 |
+
'@parcel/watcher-android-arm64@2.5.1':
|
| 56 |
+
resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
|
| 57 |
+
engines: {node: '>= 10.0.0'}
|
| 58 |
+
cpu: [arm64]
|
| 59 |
+
os: [android]
|
| 60 |
+
|
| 61 |
+
'@parcel/watcher-darwin-arm64@2.5.1':
|
| 62 |
+
resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
|
| 63 |
+
engines: {node: '>= 10.0.0'}
|
| 64 |
+
cpu: [arm64]
|
| 65 |
+
os: [darwin]
|
| 66 |
+
|
| 67 |
+
'@parcel/watcher-darwin-x64@2.5.1':
|
| 68 |
+
resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
|
| 69 |
+
engines: {node: '>= 10.0.0'}
|
| 70 |
+
cpu: [x64]
|
| 71 |
+
os: [darwin]
|
| 72 |
+
|
| 73 |
+
'@parcel/watcher-freebsd-x64@2.5.1':
|
| 74 |
+
resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==}
|
| 75 |
+
engines: {node: '>= 10.0.0'}
|
| 76 |
+
cpu: [x64]
|
| 77 |
+
os: [freebsd]
|
| 78 |
+
|
| 79 |
+
'@parcel/watcher-linux-arm-glibc@2.5.1':
|
| 80 |
+
resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==}
|
| 81 |
+
engines: {node: '>= 10.0.0'}
|
| 82 |
+
cpu: [arm]
|
| 83 |
+
os: [linux]
|
| 84 |
+
|
| 85 |
+
'@parcel/watcher-linux-arm-musl@2.5.1':
|
| 86 |
+
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
|
| 87 |
+
engines: {node: '>= 10.0.0'}
|
| 88 |
+
cpu: [arm]
|
| 89 |
+
os: [linux]
|
| 90 |
+
|
| 91 |
+
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
| 92 |
+
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
|
| 93 |
+
engines: {node: '>= 10.0.0'}
|
| 94 |
+
cpu: [arm64]
|
| 95 |
+
os: [linux]
|
| 96 |
+
|
| 97 |
+
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
| 98 |
+
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
|
| 99 |
+
engines: {node: '>= 10.0.0'}
|
| 100 |
+
cpu: [arm64]
|
| 101 |
+
os: [linux]
|
| 102 |
+
|
| 103 |
+
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
| 104 |
+
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
|
| 105 |
+
engines: {node: '>= 10.0.0'}
|
| 106 |
+
cpu: [x64]
|
| 107 |
+
os: [linux]
|
| 108 |
+
|
| 109 |
+
'@parcel/watcher-linux-x64-musl@2.5.1':
|
| 110 |
+
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
|
| 111 |
+
engines: {node: '>= 10.0.0'}
|
| 112 |
+
cpu: [x64]
|
| 113 |
+
os: [linux]
|
| 114 |
+
|
| 115 |
+
'@parcel/watcher-win32-arm64@2.5.1':
|
| 116 |
+
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
|
| 117 |
+
engines: {node: '>= 10.0.0'}
|
| 118 |
+
cpu: [arm64]
|
| 119 |
+
os: [win32]
|
| 120 |
+
|
| 121 |
+
'@parcel/watcher-win32-ia32@2.5.1':
|
| 122 |
+
resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==}
|
| 123 |
+
engines: {node: '>= 10.0.0'}
|
| 124 |
+
cpu: [ia32]
|
| 125 |
+
os: [win32]
|
| 126 |
+
|
| 127 |
+
'@parcel/watcher-win32-x64@2.5.1':
|
| 128 |
+
resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
|
| 129 |
+
engines: {node: '>= 10.0.0'}
|
| 130 |
+
cpu: [x64]
|
| 131 |
+
os: [win32]
|
| 132 |
+
|
| 133 |
+
'@parcel/watcher@2.5.1':
|
| 134 |
+
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
|
| 135 |
+
engines: {node: '>= 10.0.0'}
|
| 136 |
+
|
| 137 |
+
'@vue/compiler-core@3.5.24':
|
| 138 |
+
resolution: {integrity: sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==}
|
| 139 |
+
|
| 140 |
+
'@vue/compiler-dom@3.5.24':
|
| 141 |
+
resolution: {integrity: sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==}
|
| 142 |
+
|
| 143 |
+
'@vue/compiler-sfc@3.5.24':
|
| 144 |
+
resolution: {integrity: sha512-8EG5YPRgmTB+YxYBM3VXy8zHD9SWHUJLIGPhDovo3Z8VOgvP+O7UP5vl0J4BBPWYD9vxtBabzW1EuEZ+Cqs14g==}
|
| 145 |
+
|
| 146 |
+
'@vue/compiler-ssr@3.5.24':
|
| 147 |
+
resolution: {integrity: sha512-trOvMWNBMQ/odMRHW7Ae1CdfYx+7MuiQu62Jtu36gMLXcaoqKvAyh+P73sYG9ll+6jLB6QPovqoKGGZROzkFFg==}
|
| 148 |
+
|
| 149 |
+
'@vue/reactivity@3.5.24':
|
| 150 |
+
resolution: {integrity: sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==}
|
| 151 |
+
|
| 152 |
+
'@vue/runtime-core@3.5.24':
|
| 153 |
+
resolution: {integrity: sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==}
|
| 154 |
+
|
| 155 |
+
'@vue/runtime-dom@3.5.24':
|
| 156 |
+
resolution: {integrity: sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==}
|
| 157 |
+
|
| 158 |
+
'@vue/server-renderer@3.5.24':
|
| 159 |
+
resolution: {integrity: sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==}
|
| 160 |
+
peerDependencies:
|
| 161 |
+
vue: 3.5.24
|
| 162 |
+
|
| 163 |
+
'@vue/shared@3.5.24':
|
| 164 |
+
resolution: {integrity: sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==}
|
| 165 |
+
|
| 166 |
+
braces@3.0.3:
|
| 167 |
+
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
| 168 |
+
engines: {node: '>=8'}
|
| 169 |
+
|
| 170 |
+
chokidar@4.0.3:
|
| 171 |
+
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
| 172 |
+
engines: {node: '>= 14.16.0'}
|
| 173 |
+
|
| 174 |
+
csstype@3.1.3:
|
| 175 |
+
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
| 176 |
+
|
| 177 |
+
detect-libc@1.0.3:
|
| 178 |
+
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
|
| 179 |
+
engines: {node: '>=0.10'}
|
| 180 |
+
hasBin: true
|
| 181 |
+
|
| 182 |
+
entities@4.5.0:
|
| 183 |
+
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
| 184 |
+
engines: {node: '>=0.12'}
|
| 185 |
+
|
| 186 |
+
estree-walker@2.0.2:
|
| 187 |
+
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
| 188 |
+
|
| 189 |
+
fill-range@7.1.1:
|
| 190 |
+
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
| 191 |
+
engines: {node: '>=8'}
|
| 192 |
+
|
| 193 |
+
immutable@5.1.4:
|
| 194 |
+
resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==}
|
| 195 |
+
|
| 196 |
+
is-extglob@2.1.1:
|
| 197 |
+
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
| 198 |
+
engines: {node: '>=0.10.0'}
|
| 199 |
+
|
| 200 |
+
is-glob@4.0.3:
|
| 201 |
+
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
| 202 |
+
engines: {node: '>=0.10.0'}
|
| 203 |
+
|
| 204 |
+
is-number@7.0.0:
|
| 205 |
+
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
| 206 |
+
engines: {node: '>=0.12.0'}
|
| 207 |
+
|
| 208 |
+
magic-string@0.30.21:
|
| 209 |
+
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
| 210 |
+
|
| 211 |
+
micromatch@4.0.8:
|
| 212 |
+
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
| 213 |
+
engines: {node: '>=8.6'}
|
| 214 |
+
|
| 215 |
+
nanoid@3.3.11:
|
| 216 |
+
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
| 217 |
+
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
| 218 |
+
hasBin: true
|
| 219 |
+
|
| 220 |
+
node-addon-api@7.1.1:
|
| 221 |
+
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
| 222 |
+
|
| 223 |
+
picocolors@1.1.1:
|
| 224 |
+
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
| 225 |
+
|
| 226 |
+
picomatch@2.3.1:
|
| 227 |
+
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
| 228 |
+
engines: {node: '>=8.6'}
|
| 229 |
+
|
| 230 |
+
postcss@8.5.6:
|
| 231 |
+
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
| 232 |
+
engines: {node: ^10 || ^12 || >=14}
|
| 233 |
+
|
| 234 |
+
prettier@3.6.2:
|
| 235 |
+
resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
|
| 236 |
+
engines: {node: '>=14'}
|
| 237 |
+
hasBin: true
|
| 238 |
+
|
| 239 |
+
readdirp@4.1.2:
|
| 240 |
+
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
| 241 |
+
engines: {node: '>= 14.18.0'}
|
| 242 |
+
|
| 243 |
+
sass@1.93.3:
|
| 244 |
+
resolution: {integrity: sha512-elOcIZRTM76dvxNAjqYrucTSI0teAF/L2Lv0s6f6b7FOwcwIuA357bIE871580AjHJuSvLIRUosgV+lIWx6Rgg==}
|
| 245 |
+
engines: {node: '>=14.0.0'}
|
| 246 |
+
hasBin: true
|
| 247 |
+
|
| 248 |
+
source-map-js@1.2.1:
|
| 249 |
+
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
| 250 |
+
engines: {node: '>=0.10.0'}
|
| 251 |
+
|
| 252 |
+
to-regex-range@5.0.1:
|
| 253 |
+
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
| 254 |
+
engines: {node: '>=8.0'}
|
| 255 |
+
|
| 256 |
+
typescript@5.9.3:
|
| 257 |
+
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
| 258 |
+
engines: {node: '>=14.17'}
|
| 259 |
+
hasBin: true
|
| 260 |
+
|
| 261 |
+
vue@3.5.24:
|
| 262 |
+
resolution: {integrity: sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg==}
|
| 263 |
+
peerDependencies:
|
| 264 |
+
typescript: '*'
|
| 265 |
+
peerDependenciesMeta:
|
| 266 |
+
typescript:
|
| 267 |
+
optional: true
|
| 268 |
+
|
| 269 |
+
web-tree-sitter@0.25.6:
|
| 270 |
+
resolution: {integrity: sha512-WG+/YGbxw8r+rLlzzhV+OvgiOJCWdIpOucG3qBf3RCBFMkGDb1CanUi2BxCxjnkpzU3/hLWPT8VO5EKsMk9Fxg==}
|
| 271 |
+
|
| 272 |
+
zod@3.25.76:
|
| 273 |
+
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
| 274 |
+
|
| 275 |
+
snapshots:
|
| 276 |
+
|
| 277 |
+
'@babel/helper-string-parser@7.27.1': {}
|
| 278 |
+
|
| 279 |
+
'@babel/helper-validator-identifier@7.28.5': {}
|
| 280 |
+
|
| 281 |
+
'@babel/parser@7.28.5':
|
| 282 |
+
dependencies:
|
| 283 |
+
'@babel/types': 7.28.5
|
| 284 |
+
|
| 285 |
+
'@babel/types@7.28.5':
|
| 286 |
+
dependencies:
|
| 287 |
+
'@babel/helper-string-parser': 7.27.1
|
| 288 |
+
'@babel/helper-validator-identifier': 7.28.5
|
| 289 |
+
|
| 290 |
+
'@comfyorg/comfyui-frontend-types@1.32.4(vue@3.5.24(typescript@5.9.3))(zod@3.25.76)':
|
| 291 |
+
dependencies:
|
| 292 |
+
vue: 3.5.24(typescript@5.9.3)
|
| 293 |
+
zod: 3.25.76
|
| 294 |
+
|
| 295 |
+
'@jridgewell/sourcemap-codec@1.5.5': {}
|
| 296 |
+
|
| 297 |
+
'@parcel/watcher-android-arm64@2.5.1':
|
| 298 |
+
optional: true
|
| 299 |
+
|
| 300 |
+
'@parcel/watcher-darwin-arm64@2.5.1':
|
| 301 |
+
optional: true
|
| 302 |
+
|
| 303 |
+
'@parcel/watcher-darwin-x64@2.5.1':
|
| 304 |
+
optional: true
|
| 305 |
+
|
| 306 |
+
'@parcel/watcher-freebsd-x64@2.5.1':
|
| 307 |
+
optional: true
|
| 308 |
+
|
| 309 |
+
'@parcel/watcher-linux-arm-glibc@2.5.1':
|
| 310 |
+
optional: true
|
| 311 |
+
|
| 312 |
+
'@parcel/watcher-linux-arm-musl@2.5.1':
|
| 313 |
+
optional: true
|
| 314 |
+
|
| 315 |
+
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
| 316 |
+
optional: true
|
| 317 |
+
|
| 318 |
+
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
| 319 |
+
optional: true
|
| 320 |
+
|
| 321 |
+
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
| 322 |
+
optional: true
|
| 323 |
+
|
| 324 |
+
'@parcel/watcher-linux-x64-musl@2.5.1':
|
| 325 |
+
optional: true
|
| 326 |
+
|
| 327 |
+
'@parcel/watcher-win32-arm64@2.5.1':
|
| 328 |
+
optional: true
|
| 329 |
+
|
| 330 |
+
'@parcel/watcher-win32-ia32@2.5.1':
|
| 331 |
+
optional: true
|
| 332 |
+
|
| 333 |
+
'@parcel/watcher-win32-x64@2.5.1':
|
| 334 |
+
optional: true
|
| 335 |
+
|
| 336 |
+
'@parcel/watcher@2.5.1':
|
| 337 |
+
dependencies:
|
| 338 |
+
detect-libc: 1.0.3
|
| 339 |
+
is-glob: 4.0.3
|
| 340 |
+
micromatch: 4.0.8
|
| 341 |
+
node-addon-api: 7.1.1
|
| 342 |
+
optionalDependencies:
|
| 343 |
+
'@parcel/watcher-android-arm64': 2.5.1
|
| 344 |
+
'@parcel/watcher-darwin-arm64': 2.5.1
|
| 345 |
+
'@parcel/watcher-darwin-x64': 2.5.1
|
| 346 |
+
'@parcel/watcher-freebsd-x64': 2.5.1
|
| 347 |
+
'@parcel/watcher-linux-arm-glibc': 2.5.1
|
| 348 |
+
'@parcel/watcher-linux-arm-musl': 2.5.1
|
| 349 |
+
'@parcel/watcher-linux-arm64-glibc': 2.5.1
|
| 350 |
+
'@parcel/watcher-linux-arm64-musl': 2.5.1
|
| 351 |
+
'@parcel/watcher-linux-x64-glibc': 2.5.1
|
| 352 |
+
'@parcel/watcher-linux-x64-musl': 2.5.1
|
| 353 |
+
'@parcel/watcher-win32-arm64': 2.5.1
|
| 354 |
+
'@parcel/watcher-win32-ia32': 2.5.1
|
| 355 |
+
'@parcel/watcher-win32-x64': 2.5.1
|
| 356 |
+
optional: true
|
| 357 |
+
|
| 358 |
+
'@vue/compiler-core@3.5.24':
|
| 359 |
+
dependencies:
|
| 360 |
+
'@babel/parser': 7.28.5
|
| 361 |
+
'@vue/shared': 3.5.24
|
| 362 |
+
entities: 4.5.0
|
| 363 |
+
estree-walker: 2.0.2
|
| 364 |
+
source-map-js: 1.2.1
|
| 365 |
+
|
| 366 |
+
'@vue/compiler-dom@3.5.24':
|
| 367 |
+
dependencies:
|
| 368 |
+
'@vue/compiler-core': 3.5.24
|
| 369 |
+
'@vue/shared': 3.5.24
|
| 370 |
+
|
| 371 |
+
'@vue/compiler-sfc@3.5.24':
|
| 372 |
+
dependencies:
|
| 373 |
+
'@babel/parser': 7.28.5
|
| 374 |
+
'@vue/compiler-core': 3.5.24
|
| 375 |
+
'@vue/compiler-dom': 3.5.24
|
| 376 |
+
'@vue/compiler-ssr': 3.5.24
|
| 377 |
+
'@vue/shared': 3.5.24
|
| 378 |
+
estree-walker: 2.0.2
|
| 379 |
+
magic-string: 0.30.21
|
| 380 |
+
postcss: 8.5.6
|
| 381 |
+
source-map-js: 1.2.1
|
| 382 |
+
|
| 383 |
+
'@vue/compiler-ssr@3.5.24':
|
| 384 |
+
dependencies:
|
| 385 |
+
'@vue/compiler-dom': 3.5.24
|
| 386 |
+
'@vue/shared': 3.5.24
|
| 387 |
+
|
| 388 |
+
'@vue/reactivity@3.5.24':
|
| 389 |
+
dependencies:
|
| 390 |
+
'@vue/shared': 3.5.24
|
| 391 |
+
|
| 392 |
+
'@vue/runtime-core@3.5.24':
|
| 393 |
+
dependencies:
|
| 394 |
+
'@vue/reactivity': 3.5.24
|
| 395 |
+
'@vue/shared': 3.5.24
|
| 396 |
+
|
| 397 |
+
'@vue/runtime-dom@3.5.24':
|
| 398 |
+
dependencies:
|
| 399 |
+
'@vue/reactivity': 3.5.24
|
| 400 |
+
'@vue/runtime-core': 3.5.24
|
| 401 |
+
'@vue/shared': 3.5.24
|
| 402 |
+
csstype: 3.1.3
|
| 403 |
+
|
| 404 |
+
'@vue/server-renderer@3.5.24(vue@3.5.24(typescript@5.9.3))':
|
| 405 |
+
dependencies:
|
| 406 |
+
'@vue/compiler-ssr': 3.5.24
|
| 407 |
+
'@vue/shared': 3.5.24
|
| 408 |
+
vue: 3.5.24(typescript@5.9.3)
|
| 409 |
+
|
| 410 |
+
'@vue/shared@3.5.24': {}
|
| 411 |
+
|
| 412 |
+
braces@3.0.3:
|
| 413 |
+
dependencies:
|
| 414 |
+
fill-range: 7.1.1
|
| 415 |
+
optional: true
|
| 416 |
+
|
| 417 |
+
chokidar@4.0.3:
|
| 418 |
+
dependencies:
|
| 419 |
+
readdirp: 4.1.2
|
| 420 |
+
|
| 421 |
+
csstype@3.1.3: {}
|
| 422 |
+
|
| 423 |
+
detect-libc@1.0.3:
|
| 424 |
+
optional: true
|
| 425 |
+
|
| 426 |
+
entities@4.5.0: {}
|
| 427 |
+
|
| 428 |
+
estree-walker@2.0.2: {}
|
| 429 |
+
|
| 430 |
+
fill-range@7.1.1:
|
| 431 |
+
dependencies:
|
| 432 |
+
to-regex-range: 5.0.1
|
| 433 |
+
optional: true
|
| 434 |
+
|
| 435 |
+
immutable@5.1.4: {}
|
| 436 |
+
|
| 437 |
+
is-extglob@2.1.1:
|
| 438 |
+
optional: true
|
| 439 |
+
|
| 440 |
+
is-glob@4.0.3:
|
| 441 |
+
dependencies:
|
| 442 |
+
is-extglob: 2.1.1
|
| 443 |
+
optional: true
|
| 444 |
+
|
| 445 |
+
is-number@7.0.0:
|
| 446 |
+
optional: true
|
| 447 |
+
|
| 448 |
+
magic-string@0.30.21:
|
| 449 |
+
dependencies:
|
| 450 |
+
'@jridgewell/sourcemap-codec': 1.5.5
|
| 451 |
+
|
| 452 |
+
micromatch@4.0.8:
|
| 453 |
+
dependencies:
|
| 454 |
+
braces: 3.0.3
|
| 455 |
+
picomatch: 2.3.1
|
| 456 |
+
optional: true
|
| 457 |
+
|
| 458 |
+
nanoid@3.3.11: {}
|
| 459 |
+
|
| 460 |
+
node-addon-api@7.1.1:
|
| 461 |
+
optional: true
|
| 462 |
+
|
| 463 |
+
picocolors@1.1.1: {}
|
| 464 |
+
|
| 465 |
+
picomatch@2.3.1:
|
| 466 |
+
optional: true
|
| 467 |
+
|
| 468 |
+
postcss@8.5.6:
|
| 469 |
+
dependencies:
|
| 470 |
+
nanoid: 3.3.11
|
| 471 |
+
picocolors: 1.1.1
|
| 472 |
+
source-map-js: 1.2.1
|
| 473 |
+
|
| 474 |
+
prettier@3.6.2: {}
|
| 475 |
+
|
| 476 |
+
readdirp@4.1.2: {}
|
| 477 |
+
|
| 478 |
+
sass@1.93.3:
|
| 479 |
+
dependencies:
|
| 480 |
+
chokidar: 4.0.3
|
| 481 |
+
immutable: 5.1.4
|
| 482 |
+
source-map-js: 1.2.1
|
| 483 |
+
optionalDependencies:
|
| 484 |
+
'@parcel/watcher': 2.5.1
|
| 485 |
+
|
| 486 |
+
source-map-js@1.2.1: {}
|
| 487 |
+
|
| 488 |
+
to-regex-range@5.0.1:
|
| 489 |
+
dependencies:
|
| 490 |
+
is-number: 7.0.0
|
| 491 |
+
optional: true
|
| 492 |
+
|
| 493 |
+
typescript@5.9.3: {}
|
| 494 |
+
|
| 495 |
+
vue@3.5.24(typescript@5.9.3):
|
| 496 |
+
dependencies:
|
| 497 |
+
'@vue/compiler-dom': 3.5.24
|
| 498 |
+
'@vue/compiler-sfc': 3.5.24
|
| 499 |
+
'@vue/runtime-dom': 3.5.24
|
| 500 |
+
'@vue/server-renderer': 3.5.24(vue@3.5.24(typescript@5.9.3))
|
| 501 |
+
'@vue/shared': 3.5.24
|
| 502 |
+
optionalDependencies:
|
| 503 |
+
typescript: 5.9.3
|
| 504 |
+
|
| 505 |
+
web-tree-sitter@0.25.6: {}
|
| 506 |
+
|
| 507 |
+
zod@3.25.76: {}
|
custom_nodes/rgthree-comfy/prestartup_script.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import folder_paths
|
| 2 |
+
|
| 3 |
+
# Removed Saved Prompts feature; No sure it worked any longer. UI should fail gracefully.
|
| 4 |
+
# TODO: See if anyone actually used this.
|
| 5 |
+
# folder_paths.folder_names_and_paths['saved_prompts'] = ([], set(['.txt']))
|
custom_nodes/rgthree-comfy/py/__init__.py
ADDED
|
File without changes
|
custom_nodes/rgthree-comfy/py/any_switch.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .context_utils import is_context_empty
|
| 2 |
+
from .constants import get_category, get_name
|
| 3 |
+
from .utils import FlexibleOptionalInputType, any_type
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def is_none(value):
|
| 7 |
+
"""Checks if a value is none. Pulled out in case we want to expand what 'None' means."""
|
| 8 |
+
if value is not None:
|
| 9 |
+
if isinstance(value, dict) and 'model' in value and 'clip' in value:
|
| 10 |
+
return is_context_empty(value)
|
| 11 |
+
return value is None
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class RgthreeAnySwitch:
|
| 15 |
+
"""The dynamic Any Switch. """
|
| 16 |
+
|
| 17 |
+
NAME = get_name("Any Switch")
|
| 18 |
+
CATEGORY = get_category()
|
| 19 |
+
|
| 20 |
+
@classmethod
|
| 21 |
+
def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring
|
| 22 |
+
return {
|
| 23 |
+
"required": {},
|
| 24 |
+
"optional": FlexibleOptionalInputType(any_type),
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
RETURN_TYPES = (any_type,)
|
| 28 |
+
RETURN_NAMES = ('*',)
|
| 29 |
+
FUNCTION = "switch"
|
| 30 |
+
|
| 31 |
+
def switch(self, **kwargs):
|
| 32 |
+
"""Chooses the first non-empty item to output."""
|
| 33 |
+
any_value = None
|
| 34 |
+
for key, value in kwargs.items():
|
| 35 |
+
if key.startswith('any_') and not is_none(value):
|
| 36 |
+
any_value = value
|
| 37 |
+
break
|
| 38 |
+
return (any_value,)
|
custom_nodes/rgthree-comfy/py/config.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
|
| 4 |
+
from .utils import get_dict_value, set_dict_value, dict_has_key, load_json_file
|
| 5 |
+
from .pyproject import VERSION
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def get_config_value(key, default=None):
|
| 9 |
+
return get_dict_value(RGTHREE_CONFIG, key, default)
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def extend_config(default_config, user_config):
|
| 13 |
+
""" Returns a new config dict combining user_config into defined keys for default_config."""
|
| 14 |
+
cfg = {}
|
| 15 |
+
for key, value in default_config.items():
|
| 16 |
+
if key not in user_config:
|
| 17 |
+
cfg[key] = value
|
| 18 |
+
elif isinstance(value, dict):
|
| 19 |
+
cfg[key] = extend_config(value, user_config[key])
|
| 20 |
+
else:
|
| 21 |
+
cfg[key] = user_config[key] if key in user_config else value
|
| 22 |
+
return cfg
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def set_user_config(data: dict):
|
| 26 |
+
""" Sets the user configuration."""
|
| 27 |
+
count = 0
|
| 28 |
+
for key, value in data.items():
|
| 29 |
+
if dict_has_key(DEFAULT_CONFIG, key):
|
| 30 |
+
set_dict_value(USER_CONFIG, key, value)
|
| 31 |
+
set_dict_value(RGTHREE_CONFIG, key, value)
|
| 32 |
+
count += 1
|
| 33 |
+
if count > 0:
|
| 34 |
+
write_user_config()
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def get_rgthree_default_config():
|
| 38 |
+
""" Gets the default configuration."""
|
| 39 |
+
return load_json_file(DEFAULT_CONFIG_FILE, default={})
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def get_rgthree_user_config():
|
| 43 |
+
""" Gets the user configuration."""
|
| 44 |
+
return load_json_file(USER_CONFIG_FILE, default={})
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def write_user_config():
|
| 48 |
+
""" Writes the user configuration."""
|
| 49 |
+
with open(USER_CONFIG_FILE, 'w+', encoding='UTF-8') as file:
|
| 50 |
+
json.dump(USER_CONFIG, file, sort_keys=True, indent=2, separators=(",", ": "))
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 54 |
+
DEFAULT_CONFIG_FILE = os.path.join(THIS_DIR, '..', 'rgthree_config.json.default')
|
| 55 |
+
USER_CONFIG_FILE = os.path.join(THIS_DIR, '..', 'rgthree_config.json')
|
| 56 |
+
|
| 57 |
+
DEFAULT_CONFIG = {}
|
| 58 |
+
USER_CONFIG = {}
|
| 59 |
+
RGTHREE_CONFIG = {}
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def refresh_config():
|
| 63 |
+
"""Refreshes the config."""
|
| 64 |
+
global DEFAULT_CONFIG, USER_CONFIG, RGTHREE_CONFIG
|
| 65 |
+
DEFAULT_CONFIG = get_rgthree_default_config()
|
| 66 |
+
USER_CONFIG = get_rgthree_user_config()
|
| 67 |
+
|
| 68 |
+
# Migrate old config options into "features"
|
| 69 |
+
needs_to_write_user_config = False
|
| 70 |
+
if 'patch_recursive_execution' in USER_CONFIG:
|
| 71 |
+
del USER_CONFIG['patch_recursive_execution']
|
| 72 |
+
needs_to_write_user_config = True
|
| 73 |
+
|
| 74 |
+
if 'features' in USER_CONFIG and 'patch_recursive_execution' in USER_CONFIG['features']:
|
| 75 |
+
del USER_CONFIG['features']['patch_recursive_execution']
|
| 76 |
+
needs_to_write_user_config = True
|
| 77 |
+
|
| 78 |
+
if 'show_alerts_for_corrupt_workflows' in USER_CONFIG:
|
| 79 |
+
if 'features' not in USER_CONFIG:
|
| 80 |
+
USER_CONFIG['features'] = {}
|
| 81 |
+
USER_CONFIG['features']['show_alerts_for_corrupt_workflows'] = USER_CONFIG[
|
| 82 |
+
'show_alerts_for_corrupt_workflows']
|
| 83 |
+
del USER_CONFIG['show_alerts_for_corrupt_workflows']
|
| 84 |
+
needs_to_write_user_config = True
|
| 85 |
+
|
| 86 |
+
if 'monitor_for_corrupt_links' in USER_CONFIG:
|
| 87 |
+
if 'features' not in USER_CONFIG:
|
| 88 |
+
USER_CONFIG['features'] = {}
|
| 89 |
+
USER_CONFIG['features']['monitor_for_corrupt_links'] = USER_CONFIG['monitor_for_corrupt_links']
|
| 90 |
+
del USER_CONFIG['monitor_for_corrupt_links']
|
| 91 |
+
needs_to_write_user_config = True
|
| 92 |
+
|
| 93 |
+
if needs_to_write_user_config is True:
|
| 94 |
+
print('writing new user config.')
|
| 95 |
+
write_user_config()
|
| 96 |
+
|
| 97 |
+
RGTHREE_CONFIG = {"version": VERSION} | extend_config(DEFAULT_CONFIG, USER_CONFIG)
|
| 98 |
+
|
| 99 |
+
if "unreleased" in USER_CONFIG and "unreleased" not in RGTHREE_CONFIG:
|
| 100 |
+
RGTHREE_CONFIG["unreleased"] = USER_CONFIG["unreleased"]
|
| 101 |
+
|
| 102 |
+
if "debug" in USER_CONFIG and "debug" not in RGTHREE_CONFIG:
|
| 103 |
+
RGTHREE_CONFIG["debug"] = USER_CONFIG["debug"]
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def get_config():
|
| 107 |
+
"""Returns the congfig."""
|
| 108 |
+
return RGTHREE_CONFIG
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
refresh_config()
|
custom_nodes/rgthree-comfy/py/constants.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
NAMESPACE='rgthree'
|
| 3 |
+
|
| 4 |
+
def get_name(name):
|
| 5 |
+
return '{} ({})'.format(name, NAMESPACE)
|
| 6 |
+
|
| 7 |
+
def get_category(sub_dirs = None):
|
| 8 |
+
if sub_dirs is None:
|
| 9 |
+
return NAMESPACE
|
| 10 |
+
else:
|
| 11 |
+
return "{}/utils".format(NAMESPACE)
|
custom_nodes/rgthree-comfy/py/context.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""The Context node."""
|
| 2 |
+
from .context_utils import (ORIG_CTX_OPTIONAL_INPUTS, ORIG_CTX_RETURN_NAMES, ORIG_CTX_RETURN_TYPES,
|
| 3 |
+
get_orig_context_return_tuple, new_context)
|
| 4 |
+
from .constants import get_category, get_name
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class RgthreeContext:
|
| 8 |
+
"""The initial Context node.
|
| 9 |
+
|
| 10 |
+
For now, this nodes' outputs will remain as-is, as they are perfect for most 1.5 application, but
|
| 11 |
+
is also backwards compatible with other Context nodes.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
NAME = get_name("Context")
|
| 15 |
+
CATEGORY = get_category()
|
| 16 |
+
|
| 17 |
+
@classmethod
|
| 18 |
+
def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring
|
| 19 |
+
return {
|
| 20 |
+
"required": {},
|
| 21 |
+
"optional": ORIG_CTX_OPTIONAL_INPUTS,
|
| 22 |
+
"hidden": {
|
| 23 |
+
"version": "FLOAT"
|
| 24 |
+
},
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
RETURN_TYPES = ORIG_CTX_RETURN_TYPES
|
| 28 |
+
RETURN_NAMES = ORIG_CTX_RETURN_NAMES
|
| 29 |
+
FUNCTION = "convert"
|
| 30 |
+
|
| 31 |
+
def convert(self, base_ctx=None, **kwargs): # pylint: disable = missing-function-docstring
|
| 32 |
+
ctx = new_context(base_ctx, **kwargs)
|
| 33 |
+
return get_orig_context_return_tuple(ctx)
|
custom_nodes/rgthree-comfy/py/context_big.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""The Conmtext big node."""
|
| 2 |
+
from .constants import get_category, get_name
|
| 3 |
+
from .context_utils import (ALL_CTX_OPTIONAL_INPUTS, ALL_CTX_RETURN_NAMES, ALL_CTX_RETURN_TYPES,
|
| 4 |
+
new_context, get_context_return_tuple)
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class RgthreeBigContext:
|
| 8 |
+
"""The Context Big node.
|
| 9 |
+
|
| 10 |
+
This context node will expose all context fields as inputs and outputs. It is backwards compatible
|
| 11 |
+
with other context nodes and can be intertwined with them.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
NAME = get_name("Context Big")
|
| 15 |
+
CATEGORY = get_category()
|
| 16 |
+
|
| 17 |
+
@classmethod
|
| 18 |
+
def INPUT_TYPES(cls): # pylint: disable = invalid-name,missing-function-docstring
|
| 19 |
+
return {
|
| 20 |
+
"required": {},
|
| 21 |
+
"optional": ALL_CTX_OPTIONAL_INPUTS,
|
| 22 |
+
"hidden": {},
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
RETURN_TYPES = ALL_CTX_RETURN_TYPES
|
| 26 |
+
RETURN_NAMES = ALL_CTX_RETURN_NAMES
|
| 27 |
+
FUNCTION = "convert"
|
| 28 |
+
|
| 29 |
+
def convert(self, base_ctx=None, **kwargs): # pylint: disable = missing-function-docstring
|
| 30 |
+
ctx = new_context(base_ctx, **kwargs)
|
| 31 |
+
return get_context_return_tuple(ctx)
|
custom_nodes/rgthree-comfy/py/context_merge.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""The Context Switch (Big)."""
|
| 2 |
+
from .constants import get_category, get_name
|
| 3 |
+
from .context_utils import (ORIG_CTX_RETURN_TYPES, ORIG_CTX_RETURN_NAMES, merge_new_context,
|
| 4 |
+
get_orig_context_return_tuple, is_context_empty)
|
| 5 |
+
from .utils import FlexibleOptionalInputType
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class RgthreeContextMerge:
|
| 9 |
+
"""The Context Merge node."""
|
| 10 |
+
|
| 11 |
+
NAME = get_name("Context Merge")
|
| 12 |
+
CATEGORY = get_category()
|
| 13 |
+
|
| 14 |
+
@classmethod
|
| 15 |
+
def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring
|
| 16 |
+
return {
|
| 17 |
+
"required": {},
|
| 18 |
+
"optional": FlexibleOptionalInputType("RGTHREE_CONTEXT"),
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
RETURN_TYPES = ORIG_CTX_RETURN_TYPES
|
| 22 |
+
RETURN_NAMES = ORIG_CTX_RETURN_NAMES
|
| 23 |
+
FUNCTION = "merge"
|
| 24 |
+
|
| 25 |
+
def get_return_tuple(self, ctx):
|
| 26 |
+
"""Returns the context data. Separated so it can be overridden."""
|
| 27 |
+
return get_orig_context_return_tuple(ctx)
|
| 28 |
+
|
| 29 |
+
def merge(self, **kwargs):
|
| 30 |
+
"""Merges any non-null passed contexts; later ones overriding earlier."""
|
| 31 |
+
ctxs = [
|
| 32 |
+
value for key, value in kwargs.items()
|
| 33 |
+
if key.startswith('ctx_') and not is_context_empty(value)
|
| 34 |
+
]
|
| 35 |
+
ctx = merge_new_context(*ctxs)
|
| 36 |
+
|
| 37 |
+
return self.get_return_tuple(ctx)
|
custom_nodes/rgthree-comfy/py/context_merge_big.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""The Context Switch (Big)."""
|
| 2 |
+
from .constants import get_category, get_name
|
| 3 |
+
from .context_utils import (ALL_CTX_RETURN_TYPES, ALL_CTX_RETURN_NAMES, get_context_return_tuple)
|
| 4 |
+
from .context_merge import RgthreeContextMerge
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class RgthreeContextMergeBig(RgthreeContextMerge):
|
| 8 |
+
"""The Context Merge Big node."""
|
| 9 |
+
|
| 10 |
+
NAME = get_name("Context Merge Big")
|
| 11 |
+
RETURN_TYPES = ALL_CTX_RETURN_TYPES
|
| 12 |
+
RETURN_NAMES = ALL_CTX_RETURN_NAMES
|
| 13 |
+
|
| 14 |
+
def get_return_tuple(self, ctx):
|
| 15 |
+
"""Returns the context data. Separated so it can be overridden."""
|
| 16 |
+
return get_context_return_tuple(ctx)
|
custom_nodes/rgthree-comfy/py/context_switch.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""The original Context Switch."""
|
| 2 |
+
from .constants import get_category, get_name
|
| 3 |
+
from .context_utils import (ORIG_CTX_RETURN_TYPES, ORIG_CTX_RETURN_NAMES, is_context_empty,
|
| 4 |
+
get_orig_context_return_tuple)
|
| 5 |
+
from .utils import FlexibleOptionalInputType
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class RgthreeContextSwitch:
|
| 9 |
+
"""The (original) Context Switch node."""
|
| 10 |
+
|
| 11 |
+
NAME = get_name("Context Switch")
|
| 12 |
+
CATEGORY = get_category()
|
| 13 |
+
|
| 14 |
+
@classmethod
|
| 15 |
+
def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring
|
| 16 |
+
return {
|
| 17 |
+
"required": {},
|
| 18 |
+
"optional": FlexibleOptionalInputType("RGTHREE_CONTEXT"),
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
RETURN_TYPES = ORIG_CTX_RETURN_TYPES
|
| 22 |
+
RETURN_NAMES = ORIG_CTX_RETURN_NAMES
|
| 23 |
+
FUNCTION = "switch"
|
| 24 |
+
|
| 25 |
+
def get_return_tuple(self, ctx):
|
| 26 |
+
"""Returns the context data. Separated so it can be overridden."""
|
| 27 |
+
return get_orig_context_return_tuple(ctx)
|
| 28 |
+
|
| 29 |
+
def switch(self, **kwargs):
|
| 30 |
+
"""Chooses the first non-empty Context to output."""
|
| 31 |
+
ctx = None
|
| 32 |
+
for key, value in kwargs.items():
|
| 33 |
+
if key.startswith('ctx_') and not is_context_empty(value):
|
| 34 |
+
ctx = value
|
| 35 |
+
break
|
| 36 |
+
return self.get_return_tuple(ctx)
|
custom_nodes/rgthree-comfy/py/context_switch_big.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""The Context Switch (Big)."""
|
| 2 |
+
from .constants import get_category, get_name
|
| 3 |
+
from .context_utils import (ALL_CTX_RETURN_TYPES, ALL_CTX_RETURN_NAMES, get_context_return_tuple)
|
| 4 |
+
from .context_switch import RgthreeContextSwitch
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class RgthreeContextSwitchBig(RgthreeContextSwitch):
|
| 8 |
+
"""The Context Switch Big node."""
|
| 9 |
+
|
| 10 |
+
NAME = get_name("Context Switch Big")
|
| 11 |
+
RETURN_TYPES = ALL_CTX_RETURN_TYPES
|
| 12 |
+
RETURN_NAMES = ALL_CTX_RETURN_NAMES
|
| 13 |
+
|
| 14 |
+
def get_return_tuple(self, ctx):
|
| 15 |
+
"""Overrides the RgthreeContextSwitch `get_return_tuple` to return big context data."""
|
| 16 |
+
return get_context_return_tuple(ctx)
|
custom_nodes/rgthree-comfy/py/context_utils.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""A set of constants and utilities for handling contexts.
|
| 2 |
+
|
| 3 |
+
Sets up the inputs and outputs for the Context going forward, with additional functions for
|
| 4 |
+
creating and exporting context objects.
|
| 5 |
+
"""
|
| 6 |
+
import comfy.samplers
|
| 7 |
+
import folder_paths
|
| 8 |
+
|
| 9 |
+
_all_context_input_output_data = {
|
| 10 |
+
"base_ctx": ("base_ctx", "RGTHREE_CONTEXT", "CONTEXT"),
|
| 11 |
+
"model": ("model", "MODEL", "MODEL"),
|
| 12 |
+
"clip": ("clip", "CLIP", "CLIP"),
|
| 13 |
+
"vae": ("vae", "VAE", "VAE"),
|
| 14 |
+
"positive": ("positive", "CONDITIONING", "POSITIVE"),
|
| 15 |
+
"negative": ("negative", "CONDITIONING", "NEGATIVE"),
|
| 16 |
+
"latent": ("latent", "LATENT", "LATENT"),
|
| 17 |
+
"images": ("images", "IMAGE", "IMAGE"),
|
| 18 |
+
"seed": ("seed", "INT", "SEED"),
|
| 19 |
+
"steps": ("steps", "INT", "STEPS"),
|
| 20 |
+
"step_refiner": ("step_refiner", "INT", "STEP_REFINER"),
|
| 21 |
+
"cfg": ("cfg", "FLOAT", "CFG"),
|
| 22 |
+
"ckpt_name": ("ckpt_name", folder_paths.get_filename_list("checkpoints"), "CKPT_NAME"),
|
| 23 |
+
"sampler": ("sampler", comfy.samplers.KSampler.SAMPLERS, "SAMPLER"),
|
| 24 |
+
"scheduler": ("scheduler", comfy.samplers.KSampler.SCHEDULERS, "SCHEDULER"),
|
| 25 |
+
"clip_width": ("clip_width", "INT", "CLIP_WIDTH"),
|
| 26 |
+
"clip_height": ("clip_height", "INT", "CLIP_HEIGHT"),
|
| 27 |
+
"text_pos_g": ("text_pos_g", "STRING", "TEXT_POS_G"),
|
| 28 |
+
"text_pos_l": ("text_pos_l", "STRING", "TEXT_POS_L"),
|
| 29 |
+
"text_neg_g": ("text_neg_g", "STRING", "TEXT_NEG_G"),
|
| 30 |
+
"text_neg_l": ("text_neg_l", "STRING", "TEXT_NEG_L"),
|
| 31 |
+
"mask": ("mask", "MASK", "MASK"),
|
| 32 |
+
"control_net": ("control_net", "CONTROL_NET", "CONTROL_NET"),
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
force_input_types = ["INT", "STRING", "FLOAT"]
|
| 36 |
+
force_input_names = ["sampler", "scheduler", "ckpt_name"]
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _create_context_data(input_list=None):
|
| 40 |
+
"""Returns a tuple of context inputs, return types, and return names to use in a node"s def"""
|
| 41 |
+
if input_list is None:
|
| 42 |
+
input_list = _all_context_input_output_data.keys()
|
| 43 |
+
list_ctx_return_types = []
|
| 44 |
+
list_ctx_return_names = []
|
| 45 |
+
ctx_optional_inputs = {}
|
| 46 |
+
for inp in input_list:
|
| 47 |
+
data = _all_context_input_output_data[inp]
|
| 48 |
+
list_ctx_return_types.append(data[1])
|
| 49 |
+
list_ctx_return_names.append(data[2])
|
| 50 |
+
ctx_optional_inputs[data[0]] = tuple([data[1]] + ([{
|
| 51 |
+
"forceInput": True
|
| 52 |
+
}] if data[1] in force_input_types or data[0] in force_input_names else []))
|
| 53 |
+
|
| 54 |
+
ctx_return_types = tuple(list_ctx_return_types)
|
| 55 |
+
ctx_return_names = tuple(list_ctx_return_names)
|
| 56 |
+
return (ctx_optional_inputs, ctx_return_types, ctx_return_names)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
ALL_CTX_OPTIONAL_INPUTS, ALL_CTX_RETURN_TYPES, ALL_CTX_RETURN_NAMES = _create_context_data()
|
| 60 |
+
|
| 61 |
+
_original_ctx_inputs_list = [
|
| 62 |
+
"base_ctx", "model", "clip", "vae", "positive", "negative", "latent", "images", "seed"
|
| 63 |
+
]
|
| 64 |
+
ORIG_CTX_OPTIONAL_INPUTS, ORIG_CTX_RETURN_TYPES, ORIG_CTX_RETURN_NAMES = _create_context_data(
|
| 65 |
+
_original_ctx_inputs_list)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def new_context(base_ctx, **kwargs):
|
| 69 |
+
"""Creates a new context from the provided data, with an optional base ctx to start."""
|
| 70 |
+
context = base_ctx if base_ctx is not None else None
|
| 71 |
+
new_ctx = {}
|
| 72 |
+
for key in _all_context_input_output_data:
|
| 73 |
+
if key == "base_ctx":
|
| 74 |
+
continue
|
| 75 |
+
v = kwargs[key] if key in kwargs else None
|
| 76 |
+
new_ctx[key] = v if v is not None else context[
|
| 77 |
+
key] if context is not None and key in context else None
|
| 78 |
+
return new_ctx
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def merge_new_context(*args):
|
| 82 |
+
"""Creates a new context by merging provided contexts with the latter overriding same fields."""
|
| 83 |
+
new_ctx = {}
|
| 84 |
+
for key in _all_context_input_output_data:
|
| 85 |
+
if key == "base_ctx":
|
| 86 |
+
continue
|
| 87 |
+
v = None
|
| 88 |
+
# Move backwards through the passed contexts until we find a value and use it.
|
| 89 |
+
for ctx in reversed(args):
|
| 90 |
+
v = ctx[key] if not is_context_empty(ctx) and key in ctx else None
|
| 91 |
+
if v is not None:
|
| 92 |
+
break
|
| 93 |
+
new_ctx[key] = v
|
| 94 |
+
return new_ctx
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def get_context_return_tuple(ctx, inputs_list=None):
|
| 98 |
+
"""Returns a tuple for returning in the order of the inputs list."""
|
| 99 |
+
if inputs_list is None:
|
| 100 |
+
inputs_list = _all_context_input_output_data.keys()
|
| 101 |
+
tup_list = [
|
| 102 |
+
ctx,
|
| 103 |
+
]
|
| 104 |
+
for key in inputs_list:
|
| 105 |
+
if key == "base_ctx":
|
| 106 |
+
continue
|
| 107 |
+
tup_list.append(ctx[key] if ctx is not None and key in ctx else None)
|
| 108 |
+
return tuple(tup_list)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def get_orig_context_return_tuple(ctx):
|
| 112 |
+
"""Returns a tuple for returning from a node with only the original context keys."""
|
| 113 |
+
return get_context_return_tuple(ctx, _original_ctx_inputs_list)
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def is_context_empty(ctx):
|
| 117 |
+
"""Checks if the provided ctx is None or contains just None values."""
|
| 118 |
+
return not ctx or all(v is None for v in ctx.values())
|
custom_nodes/rgthree-comfy/py/display_any.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from .constants import get_category, get_name
|
| 3 |
+
from .utils import any_type, get_dict_value
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class RgthreeDisplayAny:
|
| 7 |
+
"""Display any data node."""
|
| 8 |
+
|
| 9 |
+
NAME = get_name('Display Any')
|
| 10 |
+
CATEGORY = get_category()
|
| 11 |
+
|
| 12 |
+
@classmethod
|
| 13 |
+
def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring
|
| 14 |
+
return {
|
| 15 |
+
"required": {
|
| 16 |
+
"source": (any_type, {}),
|
| 17 |
+
},
|
| 18 |
+
"hidden": {
|
| 19 |
+
"unique_id": "UNIQUE_ID",
|
| 20 |
+
"extra_pnginfo": "EXTRA_PNGINFO",
|
| 21 |
+
},
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
RETURN_TYPES = ()
|
| 25 |
+
FUNCTION = "main"
|
| 26 |
+
OUTPUT_NODE = True
|
| 27 |
+
|
| 28 |
+
def main(self, source=None, unique_id=None, extra_pnginfo=None):
|
| 29 |
+
value = 'None'
|
| 30 |
+
if isinstance(source, str):
|
| 31 |
+
value = source
|
| 32 |
+
elif isinstance(source, (int, float, bool)):
|
| 33 |
+
value = str(source)
|
| 34 |
+
elif source is not None:
|
| 35 |
+
try:
|
| 36 |
+
value = json.dumps(source)
|
| 37 |
+
except Exception:
|
| 38 |
+
try:
|
| 39 |
+
value = str(source)
|
| 40 |
+
except Exception:
|
| 41 |
+
value = 'source exists, but could not be serialized.'
|
| 42 |
+
|
| 43 |
+
# Save the output to the pnginfo so it's pre-filled when loading the data.
|
| 44 |
+
if extra_pnginfo and unique_id:
|
| 45 |
+
for node in get_dict_value(extra_pnginfo, 'workflow.nodes', []):
|
| 46 |
+
if str(node['id']) == str(unique_id):
|
| 47 |
+
node['widgets_values'] = [value]
|
| 48 |
+
break
|
| 49 |
+
|
| 50 |
+
return {"ui": {"text": (value,)}}
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class RgthreeDisplayInt:
|
| 54 |
+
"""Old DisplayInt node.
|
| 55 |
+
|
| 56 |
+
Can be ported over to DisplayAny if https://github.com/comfyanonymous/ComfyUI/issues/1527 fixed.
|
| 57 |
+
"""
|
| 58 |
+
|
| 59 |
+
NAME = get_name('Display Int')
|
| 60 |
+
CATEGORY = get_category()
|
| 61 |
+
|
| 62 |
+
@classmethod
|
| 63 |
+
def INPUT_TYPES(s):
|
| 64 |
+
return {
|
| 65 |
+
"required": {
|
| 66 |
+
"input": ("INT", {
|
| 67 |
+
"forceInput": True
|
| 68 |
+
}),
|
| 69 |
+
},
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
RETURN_TYPES = ()
|
| 73 |
+
FUNCTION = "main"
|
| 74 |
+
OUTPUT_NODE = True
|
| 75 |
+
|
| 76 |
+
def main(self, input=None):
|
| 77 |
+
return {"ui": {"text": (input,)}}
|
custom_nodes/rgthree-comfy/py/dynamic_context.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""The Dynamic Context node."""
|
| 2 |
+
from mimetypes import add_type
|
| 3 |
+
from .constants import get_category, get_name
|
| 4 |
+
from .utils import ByPassTypeTuple, FlexibleOptionalInputType
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class RgthreeDynamicContext:
|
| 8 |
+
"""The Dynamic Context node.
|
| 9 |
+
|
| 10 |
+
Similar to the static Context and Context Big nodes, this allows users to add any number and
|
| 11 |
+
variety of inputs to a Dynamic Context node, and return the outputs by key name.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
NAME = get_name("Dynamic Context")
|
| 15 |
+
CATEGORY = get_category()
|
| 16 |
+
|
| 17 |
+
@classmethod
|
| 18 |
+
def INPUT_TYPES(cls): # pylint: disable = invalid-name,missing-function-docstring
|
| 19 |
+
return {
|
| 20 |
+
"required": {},
|
| 21 |
+
"optional": FlexibleOptionalInputType(add_type),
|
| 22 |
+
"hidden": {},
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
RETURN_TYPES = ByPassTypeTuple(("RGTHREE_DYNAMIC_CONTEXT",))
|
| 26 |
+
RETURN_NAMES = ByPassTypeTuple(("CONTEXT",))
|
| 27 |
+
FUNCTION = "main"
|
| 28 |
+
|
| 29 |
+
def main(self, **kwargs):
|
| 30 |
+
"""Creates a new context from the provided data, with an optional base ctx to start.
|
| 31 |
+
|
| 32 |
+
This node takes a list of named inputs that are the named keys (with an optional "+ " prefix)
|
| 33 |
+
which are to be stored within the ctx dict as well as a list of keys contained in `output_keys`
|
| 34 |
+
to determine the list of output data.
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
base_ctx = kwargs.get('base_ctx', None)
|
| 38 |
+
output_keys = kwargs.get('output_keys', None)
|
| 39 |
+
|
| 40 |
+
new_ctx = base_ctx.copy() if base_ctx is not None else {}
|
| 41 |
+
|
| 42 |
+
for key_raw, value in kwargs.items():
|
| 43 |
+
if key_raw in ['base_ctx', 'output_keys']:
|
| 44 |
+
continue
|
| 45 |
+
key = key_raw.upper()
|
| 46 |
+
if key.startswith('+ '):
|
| 47 |
+
key = key[2:]
|
| 48 |
+
new_ctx[key] = value
|
| 49 |
+
|
| 50 |
+
print(new_ctx)
|
| 51 |
+
|
| 52 |
+
res = [new_ctx]
|
| 53 |
+
output_keys = output_keys.split(',') if output_keys is not None else []
|
| 54 |
+
for key in output_keys:
|
| 55 |
+
res.append(new_ctx[key] if key in new_ctx else None)
|
| 56 |
+
return tuple(res)
|
custom_nodes/rgthree-comfy/py/dynamic_context_switch.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""The original Context Switch."""
|
| 2 |
+
from .constants import get_category, get_name
|
| 3 |
+
from .context_utils import is_context_empty
|
| 4 |
+
from .utils import ByPassTypeTuple, FlexibleOptionalInputType
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class RgthreeDynamicContextSwitch:
|
| 8 |
+
"""The initial Context Switch node."""
|
| 9 |
+
|
| 10 |
+
NAME = get_name("Dynamic Context Switch")
|
| 11 |
+
CATEGORY = get_category()
|
| 12 |
+
|
| 13 |
+
@classmethod
|
| 14 |
+
def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring
|
| 15 |
+
return {
|
| 16 |
+
"required": {},
|
| 17 |
+
"optional": FlexibleOptionalInputType("RGTHREE_DYNAMIC_CONTEXT"),
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
RETURN_TYPES = ByPassTypeTuple(("RGTHREE_DYNAMIC_CONTEXT",))
|
| 21 |
+
RETURN_NAMES = ByPassTypeTuple(("CONTEXT",))
|
| 22 |
+
FUNCTION = "switch"
|
| 23 |
+
|
| 24 |
+
def switch(self, **kwargs):
|
| 25 |
+
"""Chooses the first non-empty Context to output."""
|
| 26 |
+
|
| 27 |
+
output_keys = kwargs.get('output_keys', None)
|
| 28 |
+
|
| 29 |
+
ctx = None
|
| 30 |
+
for key, value in kwargs.items():
|
| 31 |
+
if key.startswith('ctx_') and not is_context_empty(value):
|
| 32 |
+
ctx = value
|
| 33 |
+
break
|
| 34 |
+
|
| 35 |
+
res = [ctx]
|
| 36 |
+
output_keys = output_keys.split(',') if output_keys is not None else []
|
| 37 |
+
for key in output_keys:
|
| 38 |
+
res.append(ctx[key] if ctx is not None and key in ctx else None)
|
| 39 |
+
return tuple(res)
|
custom_nodes/rgthree-comfy/py/image_comparer.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from nodes import PreviewImage
|
| 2 |
+
|
| 3 |
+
from .constants import get_category, get_name
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class RgthreeImageComparer(PreviewImage):
|
| 7 |
+
"""A node that compares two images in the UI."""
|
| 8 |
+
|
| 9 |
+
NAME = get_name('Image Comparer')
|
| 10 |
+
CATEGORY = get_category()
|
| 11 |
+
FUNCTION = "compare_images"
|
| 12 |
+
DESCRIPTION = "Compares two images with a hover slider, or click from properties."
|
| 13 |
+
|
| 14 |
+
@classmethod
|
| 15 |
+
def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring
|
| 16 |
+
return {
|
| 17 |
+
"required": {},
|
| 18 |
+
"optional": {
|
| 19 |
+
"image_a": ("IMAGE",),
|
| 20 |
+
"image_b": ("IMAGE",),
|
| 21 |
+
},
|
| 22 |
+
"hidden": {
|
| 23 |
+
"prompt": "PROMPT",
|
| 24 |
+
"extra_pnginfo": "EXTRA_PNGINFO"
|
| 25 |
+
},
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
def compare_images(self,
|
| 29 |
+
image_a=None,
|
| 30 |
+
image_b=None,
|
| 31 |
+
filename_prefix="rgthree.compare.",
|
| 32 |
+
prompt=None,
|
| 33 |
+
extra_pnginfo=None):
|
| 34 |
+
|
| 35 |
+
result = { "ui": { "a_images":[], "b_images": [] } }
|
| 36 |
+
if image_a is not None and len(image_a) > 0:
|
| 37 |
+
result['ui']['a_images'] = self.save_images(image_a, filename_prefix, prompt, extra_pnginfo)['ui']['images']
|
| 38 |
+
|
| 39 |
+
if image_b is not None and len(image_b) > 0:
|
| 40 |
+
result['ui']['b_images'] = self.save_images(image_b, filename_prefix, prompt, extra_pnginfo)['ui']['images']
|
| 41 |
+
|
| 42 |
+
return result
|
custom_nodes/rgthree-comfy/py/image_inset_crop.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Image Inset Crop, with percentages."""
|
| 2 |
+
from .log import log_node_info
|
| 3 |
+
from .constants import get_category, get_name
|
| 4 |
+
from nodes import MAX_RESOLUTION
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def get_new_bounds(width, height, left, right, top, bottom):
|
| 8 |
+
"""Returns the new bounds for an image with inset crop data."""
|
| 9 |
+
left = 0 + left
|
| 10 |
+
right = width - right
|
| 11 |
+
top = 0 + top
|
| 12 |
+
bottom = height - bottom
|
| 13 |
+
return (left, right, top, bottom)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class RgthreeImageInsetCrop:
|
| 17 |
+
"""Image Inset Crop, with percentages."""
|
| 18 |
+
|
| 19 |
+
NAME = get_name('Image Inset Crop')
|
| 20 |
+
CATEGORY = get_category()
|
| 21 |
+
|
| 22 |
+
@classmethod
|
| 23 |
+
def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring
|
| 24 |
+
return {
|
| 25 |
+
"required": {
|
| 26 |
+
"image": ("IMAGE",),
|
| 27 |
+
"measurement": (['Pixels', 'Percentage'],),
|
| 28 |
+
"left": ("INT", {
|
| 29 |
+
"default": 0,
|
| 30 |
+
"min": 0,
|
| 31 |
+
"max": MAX_RESOLUTION,
|
| 32 |
+
"step": 8
|
| 33 |
+
}),
|
| 34 |
+
"right": ("INT", {
|
| 35 |
+
"default": 0,
|
| 36 |
+
"min": 0,
|
| 37 |
+
"max": MAX_RESOLUTION,
|
| 38 |
+
"step": 8
|
| 39 |
+
}),
|
| 40 |
+
"top": ("INT", {
|
| 41 |
+
"default": 0,
|
| 42 |
+
"min": 0,
|
| 43 |
+
"max": MAX_RESOLUTION,
|
| 44 |
+
"step": 8
|
| 45 |
+
}),
|
| 46 |
+
"bottom": ("INT", {
|
| 47 |
+
"default": 0,
|
| 48 |
+
"min": 0,
|
| 49 |
+
"max": MAX_RESOLUTION,
|
| 50 |
+
"step": 8
|
| 51 |
+
}),
|
| 52 |
+
},
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
RETURN_TYPES = ("IMAGE",)
|
| 56 |
+
FUNCTION = "crop"
|
| 57 |
+
|
| 58 |
+
# pylint: disable = too-many-arguments
|
| 59 |
+
def crop(self, measurement, left, right, top, bottom, image=None):
|
| 60 |
+
"""Does the crop."""
|
| 61 |
+
|
| 62 |
+
_, height, width, _ = image.shape
|
| 63 |
+
|
| 64 |
+
if measurement == 'Percentage':
|
| 65 |
+
left = int(width - (width * (100 - left) / 100))
|
| 66 |
+
right = int(width - (width * (100 - right) / 100))
|
| 67 |
+
top = int(height - (height * (100 - top) / 100))
|
| 68 |
+
bottom = int(height - (height * (100 - bottom) / 100))
|
| 69 |
+
|
| 70 |
+
# Snap to 8 pixels
|
| 71 |
+
left = left // 8 * 8
|
| 72 |
+
right = right // 8 * 8
|
| 73 |
+
top = top // 8 * 8
|
| 74 |
+
bottom = bottom // 8 * 8
|
| 75 |
+
|
| 76 |
+
if left == 0 and right == 0 and bottom == 0 and top == 0:
|
| 77 |
+
return (image,)
|
| 78 |
+
|
| 79 |
+
inset_left, inset_right, inset_top, inset_bottom = get_new_bounds(width, height, left, right,
|
| 80 |
+
top, bottom)
|
| 81 |
+
if inset_top > inset_bottom:
|
| 82 |
+
raise ValueError(
|
| 83 |
+
f"Invalid cropping dimensions top ({inset_top}) exceeds bottom ({inset_bottom})")
|
| 84 |
+
if inset_left > inset_right:
|
| 85 |
+
raise ValueError(
|
| 86 |
+
f"Invalid cropping dimensions left ({inset_left}) exceeds right ({inset_right})")
|
| 87 |
+
|
| 88 |
+
log_node_info(
|
| 89 |
+
self.NAME, f'Cropping image {width}x{height} width inset by {inset_left},{inset_right}, ' +
|
| 90 |
+
f'and height inset by {inset_top}, {inset_bottom}')
|
| 91 |
+
image = image[:, inset_top:inset_bottom, inset_left:inset_right, :]
|
| 92 |
+
|
| 93 |
+
return (image,)
|
custom_nodes/rgthree-comfy/py/image_or_latent_size.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .utils import FlexibleOptionalInputType, any_type
|
| 2 |
+
from .constants import get_category, get_name
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class RgthreeImageOrLatentSize:
|
| 6 |
+
"""The ImageOrLatentSize Node."""
|
| 7 |
+
|
| 8 |
+
NAME = get_name('Image or Latent Size')
|
| 9 |
+
CATEGORY = get_category()
|
| 10 |
+
|
| 11 |
+
@classmethod
|
| 12 |
+
def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring
|
| 13 |
+
return {
|
| 14 |
+
"required": {},
|
| 15 |
+
"optional": FlexibleOptionalInputType(any_type),
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
RETURN_TYPES = ("INT", "INT")
|
| 19 |
+
RETURN_NAMES = ('WIDTH', 'HEIGHT')
|
| 20 |
+
FUNCTION = "main"
|
| 21 |
+
|
| 22 |
+
def main(self, **kwargs):
|
| 23 |
+
"""Does the node's work."""
|
| 24 |
+
image_or_latent_or_mask = kwargs.get('input', None)
|
| 25 |
+
|
| 26 |
+
if isinstance(image_or_latent_or_mask, dict) and 'samples' in image_or_latent_or_mask:
|
| 27 |
+
count, _, height, width = image_or_latent_or_mask['samples'].shape
|
| 28 |
+
return (width * 8, height * 8)
|
| 29 |
+
|
| 30 |
+
batch, height, width, channel = image_or_latent_or_mask.shape
|
| 31 |
+
return (width, height)
|
custom_nodes/rgthree-comfy/py/image_resize.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import comfy.utils
|
| 3 |
+
import nodes
|
| 4 |
+
|
| 5 |
+
from .constants import get_category, get_name
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class RgthreeImageResize:
|
| 9 |
+
"""Image Resize."""
|
| 10 |
+
|
| 11 |
+
NAME = get_name("Image Resize")
|
| 12 |
+
CATEGORY = get_category()
|
| 13 |
+
|
| 14 |
+
@classmethod
|
| 15 |
+
def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring
|
| 16 |
+
return {
|
| 17 |
+
"required": {
|
| 18 |
+
"image": ("IMAGE",),
|
| 19 |
+
"measurement": (["pixels", "percentage"],),
|
| 20 |
+
"width": (
|
| 21 |
+
"INT", {
|
| 22 |
+
"default": 0,
|
| 23 |
+
"min": 0,
|
| 24 |
+
"max": nodes.MAX_RESOLUTION,
|
| 25 |
+
"step": 1,
|
| 26 |
+
"tooltip": (
|
| 27 |
+
"The width of the desired resize. A pixel value if measurement is 'pixels' or a"
|
| 28 |
+
" 100% scale percentage value if measurement is 'percentage'. Passing '0' will"
|
| 29 |
+
" calculate the dimension based on the height."
|
| 30 |
+
),
|
| 31 |
+
},
|
| 32 |
+
),
|
| 33 |
+
"height": ("INT", {
|
| 34 |
+
"default": 0,
|
| 35 |
+
"min": 0,
|
| 36 |
+
"max": nodes.MAX_RESOLUTION,
|
| 37 |
+
"step": 1
|
| 38 |
+
}),
|
| 39 |
+
"fit": (["crop", "pad", "contain"], {
|
| 40 |
+
"tooltip": (
|
| 41 |
+
"'crop' resizes so the image covers the desired width and height, and center-crops the"
|
| 42 |
+
" excess, returning exactly the desired width and height."
|
| 43 |
+
"\n'pad' resizes so the image fits inside the desired width and height, and fills the"
|
| 44 |
+
" empty space returning exactly the desired width and height."
|
| 45 |
+
"\n'contain' resizes so the image fits inside the desired width and height, and"
|
| 46 |
+
" returns the image with it's new size, with one side liekly smaller than the desired."
|
| 47 |
+
"\n\nNote, if either width or height is '0', the effective fit is 'contain'."
|
| 48 |
+
)
|
| 49 |
+
},
|
| 50 |
+
),
|
| 51 |
+
"method": (nodes.ImageScale.upscale_methods,),
|
| 52 |
+
},
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
RETURN_TYPES = ("IMAGE", "INT", "INT",)
|
| 56 |
+
RETURN_NAMES = ("IMAGE", "WIDTH", "HEIGHT",)
|
| 57 |
+
FUNCTION = "main"
|
| 58 |
+
DESCRIPTION = """Resize the image."""
|
| 59 |
+
|
| 60 |
+
def main(self, image, measurement, width, height, method, fit):
|
| 61 |
+
"""Resizes the image."""
|
| 62 |
+
_, H, W, _ = image.shape
|
| 63 |
+
|
| 64 |
+
if measurement == "percentage":
|
| 65 |
+
width = round(width * W / 100)
|
| 66 |
+
height = round(height * H / 100)
|
| 67 |
+
|
| 68 |
+
if (width == 0 and height == 0) or (width == W and height == H):
|
| 69 |
+
return (image, W, H)
|
| 70 |
+
|
| 71 |
+
# If one dimension is 0, then calculate the desired value from the ratio of the set dimension.
|
| 72 |
+
# This also implies a 'contain' fit since the width and height will be scaled with a locked
|
| 73 |
+
# aspect ratio.
|
| 74 |
+
if width == 0 or height == 0:
|
| 75 |
+
width = round(height / H * W) if width == 0 else width
|
| 76 |
+
height = round(width / W * H) if height == 0 else height
|
| 77 |
+
fit = "contain"
|
| 78 |
+
|
| 79 |
+
# At this point, width and height are our output height, but our resize sizes will be different.
|
| 80 |
+
resized_width = width
|
| 81 |
+
resized_height = height
|
| 82 |
+
if fit == "crop":
|
| 83 |
+
# If we resize against the opposite ratio, then choose the ratio that has the overhang.
|
| 84 |
+
if (height / H * W) > width:
|
| 85 |
+
resized_width = round(height / H * W)
|
| 86 |
+
elif (width / W * H) > height:
|
| 87 |
+
resized_height = round(width / W * H)
|
| 88 |
+
elif fit == "contain" or fit == "pad":
|
| 89 |
+
# If we resize against the opposite ratio, then choose the ratio that has the overhang.
|
| 90 |
+
if (height / H * W) > width:
|
| 91 |
+
resized_height = round(width / W * H)
|
| 92 |
+
elif (width / W * H) > height:
|
| 93 |
+
resized_width = round(height / H * W)
|
| 94 |
+
|
| 95 |
+
out_image = comfy.utils.common_upscale(
|
| 96 |
+
image.clone().movedim(-1, 1), resized_width, resized_height, method, crop="disabled"
|
| 97 |
+
).movedim(1, -1)
|
| 98 |
+
OB, OH, OW, OC = out_image.shape
|
| 99 |
+
|
| 100 |
+
if fit != "contain":
|
| 101 |
+
# First, we crop, then we pad; no need to check fit (other than not 'contain') since the size
|
| 102 |
+
# should already be correct.
|
| 103 |
+
if OW > width:
|
| 104 |
+
out_image = out_image.narrow(-2, (OW - width) // 2, width)
|
| 105 |
+
if OH > height:
|
| 106 |
+
out_image = out_image.narrow(-3, (OH - height) // 2, height)
|
| 107 |
+
|
| 108 |
+
OB, OH, OW, OC = out_image.shape
|
| 109 |
+
if width != OW or height != OH:
|
| 110 |
+
padded_image = torch.zeros((OB, height, width, OC), dtype=image.dtype, device=image.device)
|
| 111 |
+
x = (width - OW) // 2
|
| 112 |
+
y = (height - OH) // 2
|
| 113 |
+
for b in range(OB):
|
| 114 |
+
padded_image[b, y:y + OH, x:x + OW, :] = out_image[b]
|
| 115 |
+
out_image = padded_image
|
| 116 |
+
|
| 117 |
+
return (out_image, out_image.shape[2], out_image.shape[1])
|
custom_nodes/rgthree-comfy/py/ksampler_config.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Some basic config stuff I use for SDXL."""
|
| 2 |
+
|
| 3 |
+
from .constants import get_category, get_name
|
| 4 |
+
from nodes import MAX_RESOLUTION
|
| 5 |
+
import comfy.samplers
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class RgthreeKSamplerConfig:
|
| 9 |
+
"""Some basic config stuff I started using for SDXL, but useful in other spots too."""
|
| 10 |
+
|
| 11 |
+
NAME = get_name('KSampler Config')
|
| 12 |
+
CATEGORY = get_category()
|
| 13 |
+
|
| 14 |
+
@classmethod
|
| 15 |
+
def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring
|
| 16 |
+
return {
|
| 17 |
+
"required": {
|
| 18 |
+
"steps_total": ("INT", {
|
| 19 |
+
"default": 30,
|
| 20 |
+
"min": 1,
|
| 21 |
+
"max": MAX_RESOLUTION,
|
| 22 |
+
"step": 1,
|
| 23 |
+
}),
|
| 24 |
+
"refiner_step": ("INT", {
|
| 25 |
+
"default": 24,
|
| 26 |
+
"min": 1,
|
| 27 |
+
"max": MAX_RESOLUTION,
|
| 28 |
+
"step": 1,
|
| 29 |
+
}),
|
| 30 |
+
"cfg": ("FLOAT", {
|
| 31 |
+
"default": 8.0,
|
| 32 |
+
"min": 0.0,
|
| 33 |
+
"max": 100.0,
|
| 34 |
+
"step": 0.5,
|
| 35 |
+
}),
|
| 36 |
+
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
|
| 37 |
+
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
|
| 38 |
+
#"refiner_ascore_pos": ("FLOAT", {"default": 6.0, "min": 0.0, "max": 1000.0, "step": 0.01}),
|
| 39 |
+
#"refiner_ascore_neg": ("FLOAT", {"default": 6.0, "min": 0.0, "max": 1000.0, "step": 0.01}),
|
| 40 |
+
},
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
RETURN_TYPES = ("INT", "INT", "FLOAT", comfy.samplers.KSampler.SAMPLERS,
|
| 44 |
+
comfy.samplers.KSampler.SCHEDULERS)
|
| 45 |
+
RETURN_NAMES = ("STEPS", "REFINER_STEP", "CFG", "SAMPLER", "SCHEDULER")
|
| 46 |
+
FUNCTION = "main"
|
| 47 |
+
|
| 48 |
+
def main(self, steps_total, refiner_step, cfg, sampler_name, scheduler):
|
| 49 |
+
"""main"""
|
| 50 |
+
return (
|
| 51 |
+
steps_total,
|
| 52 |
+
refiner_step,
|
| 53 |
+
cfg,
|
| 54 |
+
sampler_name,
|
| 55 |
+
scheduler,
|
| 56 |
+
)
|
custom_nodes/rgthree-comfy/py/log.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import datetime
|
| 2 |
+
import time
|
| 3 |
+
from .pyproject import NAME
|
| 4 |
+
|
| 5 |
+
# https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences
|
| 6 |
+
# https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit
|
| 7 |
+
COLORS = {
|
| 8 |
+
'BLACK': '\33[30m',
|
| 9 |
+
'RED': '\33[31m',
|
| 10 |
+
'GREEN': '\33[32m',
|
| 11 |
+
'YELLOW': '\33[33m',
|
| 12 |
+
'BLUE': '\33[34m',
|
| 13 |
+
'MAGENTA': '\33[35m',
|
| 14 |
+
'CYAN': '\33[36m',
|
| 15 |
+
'WHITE': '\33[37m',
|
| 16 |
+
'GREY': '\33[90m',
|
| 17 |
+
'BRIGHT_RED': '\33[91m',
|
| 18 |
+
'BRIGHT_GREEN': '\33[92m',
|
| 19 |
+
'BRIGHT_YELLOW': '\33[93m',
|
| 20 |
+
'BRIGHT_BLUE': '\33[94m',
|
| 21 |
+
'BRIGHT_MAGENTA': '\33[95m',
|
| 22 |
+
'BRIGHT_CYAN': '\33[96m',
|
| 23 |
+
'BRIGHT_WHITE': '\33[97m',
|
| 24 |
+
# Styles.
|
| 25 |
+
'RESET': '\33[0m', # Note, Portainer doesn't like 00 here, so we'll use 0. Should be fine...
|
| 26 |
+
'BOLD': '\33[01m',
|
| 27 |
+
'NORMAL': '\33[22m',
|
| 28 |
+
'ITALIC': '\33[03m',
|
| 29 |
+
'UNDERLINE': '\33[04m',
|
| 30 |
+
'BLINK': '\33[05m',
|
| 31 |
+
'BLINK2': '\33[06m',
|
| 32 |
+
'SELECTED': '\33[07m',
|
| 33 |
+
# Backgrounds
|
| 34 |
+
'BG_BLACK': '\33[40m',
|
| 35 |
+
'BG_RED': '\33[41m',
|
| 36 |
+
'BG_GREEN': '\33[42m',
|
| 37 |
+
'BG_YELLOW': '\33[43m',
|
| 38 |
+
'BG_BLUE': '\33[44m',
|
| 39 |
+
'BG_MAGENTA': '\33[45m',
|
| 40 |
+
'BG_CYAN': '\33[46m',
|
| 41 |
+
'BG_WHITE': '\33[47m',
|
| 42 |
+
'BG_GREY': '\33[100m',
|
| 43 |
+
'BG_BRIGHT_RED': '\33[101m',
|
| 44 |
+
'BG_BRIGHT_GREEN': '\33[102m',
|
| 45 |
+
'BG_BRIGHT_YELLOW': '\33[103m',
|
| 46 |
+
'BG_BRIGHT_BLUE': '\33[104m',
|
| 47 |
+
'BG_BRIGHT_MAGENTA': '\33[105m',
|
| 48 |
+
'BG_BRIGHT_CYAN': '\33[106m',
|
| 49 |
+
'BG_BRIGHT_WHITE': '\33[107m',
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def log_node_success(node_name, message, msg_color='RESET'):
|
| 54 |
+
"""Logs a success message."""
|
| 55 |
+
_log_node("BRIGHT_GREEN", node_name, message, msg_color=msg_color)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def log_node_info(node_name, message, msg_color='RESET'):
|
| 59 |
+
"""Logs an info message."""
|
| 60 |
+
_log_node("CYAN", node_name, message, msg_color=msg_color)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def log_node_error(node_name, message, msg_color='RESET'):
|
| 64 |
+
"""Logs an info message."""
|
| 65 |
+
_log_node("RED", node_name, message, msg_color=msg_color)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def log_node_warn(node_name, message, msg_color='RESET'):
|
| 69 |
+
"""Logs an warn message."""
|
| 70 |
+
_log_node("YELLOW", node_name, message, msg_color=msg_color)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def log_node(node_name, message, msg_color='RESET'):
|
| 74 |
+
"""Logs a message."""
|
| 75 |
+
_log_node("CYAN", node_name, message, msg_color=msg_color)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def _log_node(color, node_name, message, msg_color='RESET'):
|
| 79 |
+
"""Logs for a node message."""
|
| 80 |
+
log(message, color=color, prefix=node_name.replace(" (rgthree)", ""), msg_color=msg_color)
|
| 81 |
+
|
| 82 |
+
LOGGED = {}
|
| 83 |
+
|
| 84 |
+
def log(message, color=None, msg_color=None, prefix=None, id=None, at_most_secs=None):
|
| 85 |
+
"""Basic logging."""
|
| 86 |
+
now = int(time.time())
|
| 87 |
+
if id:
|
| 88 |
+
if at_most_secs is None:
|
| 89 |
+
raise ValueError('at_most_secs should be set if an id is set.')
|
| 90 |
+
if id in LOGGED:
|
| 91 |
+
last_logged = LOGGED[id]
|
| 92 |
+
if now < last_logged + at_most_secs:
|
| 93 |
+
return
|
| 94 |
+
LOGGED[id] = now
|
| 95 |
+
color = COLORS[color] if color is not None and color in COLORS else COLORS["BRIGHT_GREEN"]
|
| 96 |
+
msg_color = COLORS[msg_color] if msg_color is not None and msg_color in COLORS else ''
|
| 97 |
+
prefix = f'[{prefix}]' if prefix is not None else ''
|
| 98 |
+
msg = f'{color}[{NAME}]{prefix}'
|
| 99 |
+
msg += f'{msg_color} {message}{COLORS["RESET"]}'
|
| 100 |
+
print(msg)
|
custom_nodes/rgthree-comfy/py/lora_stack.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .constants import get_category, get_name
|
| 2 |
+
from nodes import LoraLoader
|
| 3 |
+
import folder_paths
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class RgthreeLoraLoaderStack:
|
| 7 |
+
|
| 8 |
+
NAME = get_name('Lora Loader Stack')
|
| 9 |
+
CATEGORY = get_category()
|
| 10 |
+
|
| 11 |
+
@classmethod
|
| 12 |
+
def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring
|
| 13 |
+
return {
|
| 14 |
+
"required": {
|
| 15 |
+
"model": ("MODEL",),
|
| 16 |
+
"clip": ("CLIP", ),
|
| 17 |
+
|
| 18 |
+
"lora_01": (['None'] + folder_paths.get_filename_list("loras"), ),
|
| 19 |
+
"strength_01":("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
| 20 |
+
|
| 21 |
+
"lora_02": (['None'] + folder_paths.get_filename_list("loras"), ),
|
| 22 |
+
"strength_02":("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
| 23 |
+
|
| 24 |
+
"lora_03": (['None'] + folder_paths.get_filename_list("loras"), ),
|
| 25 |
+
"strength_03":("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
| 26 |
+
|
| 27 |
+
"lora_04": (['None'] + folder_paths.get_filename_list("loras"), ),
|
| 28 |
+
"strength_04":("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
| 29 |
+
}
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
RETURN_TYPES = ("MODEL", "CLIP")
|
| 33 |
+
FUNCTION = "load_lora"
|
| 34 |
+
|
| 35 |
+
def load_lora(self, model, clip, lora_01, strength_01, lora_02, strength_02, lora_03, strength_03, lora_04, strength_04):
|
| 36 |
+
if lora_01 != "None" and strength_01 != 0:
|
| 37 |
+
model, clip = LoraLoader().load_lora(model, clip, lora_01, strength_01, strength_01)
|
| 38 |
+
if lora_02 != "None" and strength_02 != 0:
|
| 39 |
+
model, clip = LoraLoader().load_lora(model, clip, lora_02, strength_02, strength_02)
|
| 40 |
+
if lora_03 != "None" and strength_03 != 0:
|
| 41 |
+
model, clip = LoraLoader().load_lora(model, clip, lora_03, strength_03, strength_03)
|
| 42 |
+
if lora_04 != "None" and strength_04 != 0:
|
| 43 |
+
model, clip = LoraLoader().load_lora(model, clip, lora_04, strength_04, strength_04)
|
| 44 |
+
|
| 45 |
+
return (model, clip)
|
| 46 |
+
|
custom_nodes/rgthree-comfy/py/power_lora_loader.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import folder_paths
|
| 2 |
+
|
| 3 |
+
from typing import Union
|
| 4 |
+
|
| 5 |
+
from nodes import LoraLoader
|
| 6 |
+
from .constants import get_category, get_name
|
| 7 |
+
from .power_prompt_utils import get_lora_by_filename
|
| 8 |
+
from .utils import FlexibleOptionalInputType, any_type
|
| 9 |
+
from .server.utils_info import get_model_info_file_data
|
| 10 |
+
from .log import log_node_warn
|
| 11 |
+
|
| 12 |
+
NODE_NAME = get_name('Power Lora Loader')
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class RgthreePowerLoraLoader:
|
| 16 |
+
""" The Power Lora Loader is a powerful, flexible node to add multiple loras to a model/clip."""
|
| 17 |
+
|
| 18 |
+
NAME = NODE_NAME
|
| 19 |
+
CATEGORY = get_category()
|
| 20 |
+
|
| 21 |
+
@classmethod
|
| 22 |
+
def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring
|
| 23 |
+
return {
|
| 24 |
+
"required": {
|
| 25 |
+
},
|
| 26 |
+
# Since we will pass any number of loras in from the UI, this needs to always allow an
|
| 27 |
+
"optional": FlexibleOptionalInputType(type=any_type, data={
|
| 28 |
+
"model": ("MODEL",),
|
| 29 |
+
"clip": ("CLIP",),
|
| 30 |
+
}),
|
| 31 |
+
"hidden": {},
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
RETURN_TYPES = ("MODEL", "CLIP")
|
| 35 |
+
RETURN_NAMES = ("MODEL", "CLIP")
|
| 36 |
+
FUNCTION = "load_loras"
|
| 37 |
+
|
| 38 |
+
def load_loras(self, model=None, clip=None, **kwargs):
|
| 39 |
+
"""Loops over the provided loras in kwargs and applies valid ones."""
|
| 40 |
+
for key, value in kwargs.items():
|
| 41 |
+
key = key.upper()
|
| 42 |
+
if key.startswith('LORA_') and 'on' in value and 'lora' in value and 'strength' in value:
|
| 43 |
+
strength_model = value['strength']
|
| 44 |
+
# If we just passed one strength value, then use it for both, if we passed a strengthTwo
|
| 45 |
+
# as well, then our `strength` will be for the model, and `strengthTwo` for clip.
|
| 46 |
+
strength_clip = value['strengthTwo'] if 'strengthTwo' in value else None
|
| 47 |
+
if clip is None:
|
| 48 |
+
if strength_clip is not None and strength_clip != 0:
|
| 49 |
+
log_node_warn(NODE_NAME, 'Recieved clip strength eventhough no clip supplied!')
|
| 50 |
+
strength_clip = 0
|
| 51 |
+
else:
|
| 52 |
+
strength_clip = strength_clip if strength_clip is not None else strength_model
|
| 53 |
+
if value['on'] and (strength_model != 0 or strength_clip != 0):
|
| 54 |
+
lora = get_lora_by_filename(value['lora'], log_node=self.NAME)
|
| 55 |
+
if model is not None and lora is not None:
|
| 56 |
+
model, clip = LoraLoader().load_lora(model, clip, lora, strength_model, strength_clip)
|
| 57 |
+
|
| 58 |
+
return (model, clip)
|
| 59 |
+
|
| 60 |
+
@classmethod
|
| 61 |
+
def get_enabled_loras_from_prompt_node(cls,
|
| 62 |
+
prompt_node: dict) -> list[dict[str, Union[str, float]]]:
|
| 63 |
+
"""Gets enabled loras of a node within a server prompt."""
|
| 64 |
+
result = []
|
| 65 |
+
for name, lora in prompt_node['inputs'].items():
|
| 66 |
+
if name.startswith('lora_') and lora['on']:
|
| 67 |
+
lora_file = get_lora_by_filename(lora['lora'], log_node=cls.NAME)
|
| 68 |
+
if lora_file is not None: # Add the same safety check
|
| 69 |
+
lora_dict = {
|
| 70 |
+
'name': lora['lora'],
|
| 71 |
+
'strength': lora['strength'],
|
| 72 |
+
'path': folder_paths.get_full_path("loras", lora_file)
|
| 73 |
+
}
|
| 74 |
+
if 'strengthTwo' in lora:
|
| 75 |
+
lora_dict['strength_clip'] = lora['strengthTwo']
|
| 76 |
+
result.append(lora_dict)
|
| 77 |
+
return result
|
| 78 |
+
|
| 79 |
+
@classmethod
|
| 80 |
+
def get_enabled_triggers_from_prompt_node(cls, prompt_node: dict, max_each: int = 1):
|
| 81 |
+
"""Gets trigger words up to the max for enabled loras of a node within a server prompt."""
|
| 82 |
+
loras = [l['name'] for l in cls.get_enabled_loras_from_prompt_node(prompt_node)]
|
| 83 |
+
trained_words = []
|
| 84 |
+
for lora in loras:
|
| 85 |
+
info = get_model_info_file_data(lora, 'loras', default={})
|
| 86 |
+
if not info or not info.keys():
|
| 87 |
+
log_node_warn(
|
| 88 |
+
NODE_NAME,
|
| 89 |
+
f'No info found for lora {lora} when grabbing triggers. Have you generated an info file'
|
| 90 |
+
' from the Power Lora Loader "Show Info" dialog?'
|
| 91 |
+
)
|
| 92 |
+
continue
|
| 93 |
+
if 'trainedWords' not in info or not info['trainedWords']:
|
| 94 |
+
log_node_warn(
|
| 95 |
+
NODE_NAME,
|
| 96 |
+
f'No trained words for lora {lora} when grabbing triggers. Have you fetched data from'
|
| 97 |
+
'civitai or manually added words?'
|
| 98 |
+
)
|
| 99 |
+
continue
|
| 100 |
+
trained_words += [w for wi in info['trainedWords'][:max_each] if (wi and (w := wi['word']))]
|
| 101 |
+
return trained_words
|
custom_nodes/rgthree-comfy/py/power_primitive.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
|
| 3 |
+
from .utils import FlexibleOptionalInputType, any_type
|
| 4 |
+
from .constants import get_category, get_name
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def cast_to_str(x):
|
| 8 |
+
"""Handles our cast to a string."""
|
| 9 |
+
if x is None:
|
| 10 |
+
return ''
|
| 11 |
+
try:
|
| 12 |
+
return str(x)
|
| 13 |
+
except (ValueError, TypeError):
|
| 14 |
+
return ''
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def cast_to_float(x):
|
| 18 |
+
"""Handles our cast to a float."""
|
| 19 |
+
try:
|
| 20 |
+
return float(x)
|
| 21 |
+
except (ValueError, TypeError):
|
| 22 |
+
return 0.0
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def cast_to_bool(x):
|
| 26 |
+
"""Handles our cast to a bool."""
|
| 27 |
+
try:
|
| 28 |
+
return bool(float(x))
|
| 29 |
+
except (ValueError, TypeError):
|
| 30 |
+
return str(x).lower() not in ['0', 'false', 'null', 'none', '']
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
output_to_type = {
|
| 34 |
+
'STRING': {
|
| 35 |
+
'cast': cast_to_str,
|
| 36 |
+
'null': '',
|
| 37 |
+
},
|
| 38 |
+
'FLOAT': {
|
| 39 |
+
'cast': cast_to_float,
|
| 40 |
+
'null': 0.0,
|
| 41 |
+
},
|
| 42 |
+
'INT': {
|
| 43 |
+
'cast': lambda x: int(cast_to_float(x)),
|
| 44 |
+
'null': 0,
|
| 45 |
+
},
|
| 46 |
+
'BOOLEAN': {
|
| 47 |
+
'cast': cast_to_bool,
|
| 48 |
+
'null': False,
|
| 49 |
+
},
|
| 50 |
+
# This can be removed soon, there was a bug where this should have been BOOLEAN
|
| 51 |
+
'BOOL': {
|
| 52 |
+
'cast': cast_to_bool,
|
| 53 |
+
'null': False,
|
| 54 |
+
},
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
class RgthreePowerPrimitive:
|
| 59 |
+
"""The Power Primitive Node."""
|
| 60 |
+
|
| 61 |
+
NAME = get_name('Power Primitive')
|
| 62 |
+
CATEGORY = get_category()
|
| 63 |
+
|
| 64 |
+
@classmethod
|
| 65 |
+
def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring
|
| 66 |
+
return {
|
| 67 |
+
"required": {},
|
| 68 |
+
"optional": FlexibleOptionalInputType(any_type),
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
RETURN_TYPES = (any_type,)
|
| 72 |
+
RETURN_NAMES = ('*',)
|
| 73 |
+
FUNCTION = "main"
|
| 74 |
+
|
| 75 |
+
def main(self, **kwargs):
|
| 76 |
+
"""Outputs the expected type."""
|
| 77 |
+
output = kwargs.get('value', None)
|
| 78 |
+
output_type = re.sub(r'\s*\([^\)]*\)\s*$', '', kwargs.get('type', ''))
|
| 79 |
+
output_type = output_to_type[output_type]
|
| 80 |
+
cast = output_type['cast']
|
| 81 |
+
output = cast(output)
|
| 82 |
+
|
| 83 |
+
return (output,)
|
custom_nodes/rgthree-comfy/py/power_prompt.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
from .log import log_node_warn, log_node_info, log_node_success
|
| 4 |
+
|
| 5 |
+
from .constants import get_category, get_name
|
| 6 |
+
from .power_prompt_utils import get_and_strip_loras
|
| 7 |
+
from nodes import LoraLoader, CLIPTextEncode
|
| 8 |
+
import folder_paths
|
| 9 |
+
|
| 10 |
+
NODE_NAME = get_name('Power Prompt')
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class RgthreePowerPrompt:
|
| 14 |
+
|
| 15 |
+
NAME = NODE_NAME
|
| 16 |
+
CATEGORY = get_category()
|
| 17 |
+
|
| 18 |
+
@classmethod
|
| 19 |
+
def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring
|
| 20 |
+
# Removed Saved Prompts feature; No sure it worked any longer. UI should fail gracefully,
|
| 21 |
+
# TODO: Rip out saved prompt input data
|
| 22 |
+
SAVED_PROMPTS_FILES=[]
|
| 23 |
+
SAVED_PROMPTS_CONTENT=[]
|
| 24 |
+
return {
|
| 25 |
+
'required': {
|
| 26 |
+
'prompt': ('STRING', {
|
| 27 |
+
'multiline': True,
|
| 28 |
+
'dynamicPrompts': True
|
| 29 |
+
}),
|
| 30 |
+
},
|
| 31 |
+
'optional': {
|
| 32 |
+
"opt_model": ("MODEL",),
|
| 33 |
+
"opt_clip": ("CLIP",),
|
| 34 |
+
'insert_lora': (['CHOOSE', 'DISABLE LORAS'] +
|
| 35 |
+
[os.path.splitext(x)[0] for x in folder_paths.get_filename_list('loras')],),
|
| 36 |
+
'insert_embedding': ([
|
| 37 |
+
'CHOOSE',
|
| 38 |
+
] + [os.path.splitext(x)[0] for x in folder_paths.get_filename_list('embeddings')],),
|
| 39 |
+
'insert_saved': ([
|
| 40 |
+
'CHOOSE',
|
| 41 |
+
] + SAVED_PROMPTS_FILES,),
|
| 42 |
+
},
|
| 43 |
+
'hidden': {
|
| 44 |
+
'values_insert_saved': (['CHOOSE'] + SAVED_PROMPTS_CONTENT,),
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
RETURN_TYPES = (
|
| 49 |
+
'CONDITIONING',
|
| 50 |
+
'MODEL',
|
| 51 |
+
'CLIP',
|
| 52 |
+
'STRING',
|
| 53 |
+
)
|
| 54 |
+
RETURN_NAMES = (
|
| 55 |
+
'CONDITIONING',
|
| 56 |
+
'MODEL',
|
| 57 |
+
'CLIP',
|
| 58 |
+
'TEXT',
|
| 59 |
+
)
|
| 60 |
+
FUNCTION = 'main'
|
| 61 |
+
|
| 62 |
+
def main(self,
|
| 63 |
+
prompt,
|
| 64 |
+
opt_model=None,
|
| 65 |
+
opt_clip=None,
|
| 66 |
+
insert_lora=None,
|
| 67 |
+
insert_embedding=None,
|
| 68 |
+
insert_saved=None,
|
| 69 |
+
values_insert_saved=None):
|
| 70 |
+
if insert_lora == 'DISABLE LORAS':
|
| 71 |
+
prompt, loras, skipped, unfound = get_and_strip_loras(prompt, log_node=NODE_NAME, silent=True)
|
| 72 |
+
log_node_info(
|
| 73 |
+
NODE_NAME,
|
| 74 |
+
f'Disabling all found loras ({len(loras)}) and stripping lora tags for TEXT output.')
|
| 75 |
+
elif opt_model is not None and opt_clip is not None:
|
| 76 |
+
prompt, loras, skipped, unfound = get_and_strip_loras(prompt, log_node=NODE_NAME)
|
| 77 |
+
if len(loras) > 0:
|
| 78 |
+
for lora in loras:
|
| 79 |
+
opt_model, opt_clip = LoraLoader().load_lora(opt_model, opt_clip, lora['lora'],
|
| 80 |
+
lora['strength'], lora['strength'])
|
| 81 |
+
log_node_success(NODE_NAME, f'Loaded "{lora["lora"]}" from prompt')
|
| 82 |
+
log_node_info(NODE_NAME, f'{len(loras)} Loras processed; stripping tags for TEXT output.')
|
| 83 |
+
elif '<lora:' in prompt:
|
| 84 |
+
prompt, loras, skipped, unfound = get_and_strip_loras(prompt, log_node=NODE_NAME, silent=True)
|
| 85 |
+
total_loras = len(loras) + len(skipped) + len(unfound)
|
| 86 |
+
if total_loras:
|
| 87 |
+
log_node_warn(
|
| 88 |
+
NODE_NAME, f'Found {len(loras)} lora tags in prompt but model & clip were not supplied!')
|
| 89 |
+
log_node_info(NODE_NAME, 'Loras not processed, keeping for TEXT output.')
|
| 90 |
+
|
| 91 |
+
conditioning = None
|
| 92 |
+
if opt_clip is not None:
|
| 93 |
+
conditioning = CLIPTextEncode().encode(opt_clip, prompt)[0]
|
| 94 |
+
|
| 95 |
+
return (conditioning, opt_model, opt_clip, prompt)
|
custom_nodes/rgthree-comfy/py/power_prompt_simple.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import folder_paths
|
| 3 |
+
from nodes import CLIPTextEncode
|
| 4 |
+
from .constants import get_category, get_name
|
| 5 |
+
from .power_prompt import RgthreePowerPrompt
|
| 6 |
+
|
| 7 |
+
class RgthreePowerPromptSimple(RgthreePowerPrompt):
|
| 8 |
+
|
| 9 |
+
NAME=get_name('Power Prompt - Simple')
|
| 10 |
+
CATEGORY = get_category()
|
| 11 |
+
|
| 12 |
+
@classmethod
|
| 13 |
+
def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring
|
| 14 |
+
# Removed Saved Prompts feature; No sure it worked any longer. UI should fail gracefully,
|
| 15 |
+
# TODO: Rip out saved prompt input data
|
| 16 |
+
SAVED_PROMPTS_FILES=[]
|
| 17 |
+
SAVED_PROMPTS_CONTENT=[]
|
| 18 |
+
return {
|
| 19 |
+
'required': {
|
| 20 |
+
'prompt': ('STRING', {'multiline': True, 'dynamicPrompts': True}),
|
| 21 |
+
},
|
| 22 |
+
'optional': {
|
| 23 |
+
"opt_clip": ("CLIP", ),
|
| 24 |
+
'insert_embedding': (['CHOOSE',] + [os.path.splitext(x)[0] for x in folder_paths.get_filename_list('embeddings')],),
|
| 25 |
+
'insert_saved': (['CHOOSE',] + SAVED_PROMPTS_FILES,),
|
| 26 |
+
},
|
| 27 |
+
'hidden': {
|
| 28 |
+
'values_insert_saved': (['CHOOSE'] + SAVED_PROMPTS_CONTENT,),
|
| 29 |
+
}
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
RETURN_TYPES = ('CONDITIONING', 'STRING',)
|
| 33 |
+
RETURN_NAMES = ('CONDITIONING', 'TEXT',)
|
| 34 |
+
FUNCTION = 'main'
|
| 35 |
+
|
| 36 |
+
def main(self, prompt, opt_clip=None, insert_embedding=None, insert_saved=None, values_insert_saved=None):
|
| 37 |
+
conditioning=None
|
| 38 |
+
if opt_clip != None:
|
| 39 |
+
conditioning = CLIPTextEncode().encode(opt_clip, prompt)[0]
|
| 40 |
+
|
| 41 |
+
return (conditioning, prompt)
|
| 42 |
+
|
custom_nodes/rgthree-comfy/py/power_prompt_utils.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Utilities for Power Prompt nodes."""
|
| 2 |
+
import re
|
| 3 |
+
import os
|
| 4 |
+
import folder_paths
|
| 5 |
+
|
| 6 |
+
from .log import log_node_warn, log_node_info
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def get_and_strip_loras(prompt, silent=False, log_node="Power Prompt"):
|
| 10 |
+
"""Collects and strips lora tags from a prompt."""
|
| 11 |
+
pattern = r'<lora:([^:>]*?)(?::(-?\d*(?:\.\d*)?))?>'
|
| 12 |
+
lora_paths = folder_paths.get_filename_list('loras')
|
| 13 |
+
|
| 14 |
+
matches = re.findall(pattern, prompt)
|
| 15 |
+
|
| 16 |
+
loras = []
|
| 17 |
+
unfound_loras = []
|
| 18 |
+
skipped_loras = []
|
| 19 |
+
for match in matches:
|
| 20 |
+
tag_path = match[0]
|
| 21 |
+
|
| 22 |
+
strength = float(match[1] if len(match) > 1 and len(match[1]) else 1.0)
|
| 23 |
+
if strength == 0:
|
| 24 |
+
if not silent:
|
| 25 |
+
log_node_info(log_node, f'Skipping "{tag_path}" with strength of zero')
|
| 26 |
+
skipped_loras.append({'lora': tag_path, 'strength': strength})
|
| 27 |
+
continue
|
| 28 |
+
|
| 29 |
+
lora_path = get_lora_by_filename(tag_path, lora_paths, log_node=None if silent else log_node)
|
| 30 |
+
if lora_path is None:
|
| 31 |
+
unfound_loras.append({'lora': tag_path, 'strength': strength})
|
| 32 |
+
continue
|
| 33 |
+
|
| 34 |
+
loras.append({'lora': lora_path, 'strength': strength})
|
| 35 |
+
|
| 36 |
+
return (re.sub(pattern, '', prompt), loras, skipped_loras, unfound_loras)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
# pylint: disable = too-many-return-statements, too-many-branches
|
| 40 |
+
def get_lora_by_filename(file_path, lora_paths=None, log_node=None):
|
| 41 |
+
"""Returns a lora by filename, looking for exactl paths and then fuzzier matching."""
|
| 42 |
+
lora_paths = lora_paths if lora_paths is not None else folder_paths.get_filename_list('loras')
|
| 43 |
+
|
| 44 |
+
if file_path in lora_paths:
|
| 45 |
+
return file_path
|
| 46 |
+
|
| 47 |
+
lora_paths_no_ext = [os.path.splitext(x)[0] for x in lora_paths]
|
| 48 |
+
|
| 49 |
+
# See if we've entered the exact path, but without the extension
|
| 50 |
+
if file_path in lora_paths_no_ext:
|
| 51 |
+
found = lora_paths[lora_paths_no_ext.index(file_path)]
|
| 52 |
+
return found
|
| 53 |
+
|
| 54 |
+
# Same check, but ensure file_path is without extension.
|
| 55 |
+
file_path_force_no_ext = os.path.splitext(file_path)[0]
|
| 56 |
+
if file_path_force_no_ext in lora_paths_no_ext:
|
| 57 |
+
found = lora_paths[lora_paths_no_ext.index(file_path_force_no_ext)]
|
| 58 |
+
return found
|
| 59 |
+
|
| 60 |
+
# See if we passed just the name, without paths.
|
| 61 |
+
lora_filenames_only = [os.path.basename(x) for x in lora_paths]
|
| 62 |
+
if file_path in lora_filenames_only:
|
| 63 |
+
found = lora_paths[lora_filenames_only.index(file_path)]
|
| 64 |
+
if log_node is not None:
|
| 65 |
+
log_node_info(log_node, f'Matched Lora input "{file_path}" to "{found}".')
|
| 66 |
+
return found
|
| 67 |
+
|
| 68 |
+
# Same, but force the input to be without paths
|
| 69 |
+
file_path_force_filename = os.path.basename(file_path)
|
| 70 |
+
lora_filenames_only = [os.path.basename(x) for x in lora_paths]
|
| 71 |
+
if file_path_force_filename in lora_filenames_only:
|
| 72 |
+
found = lora_paths[lora_filenames_only.index(file_path_force_filename)]
|
| 73 |
+
if log_node is not None:
|
| 74 |
+
log_node_info(log_node, f'Matched Lora input "{file_path}" to "{found}".')
|
| 75 |
+
return found
|
| 76 |
+
|
| 77 |
+
# Check the filenames and without extension.
|
| 78 |
+
lora_filenames_and_no_ext = [os.path.splitext(os.path.basename(x))[0] for x in lora_paths]
|
| 79 |
+
if file_path in lora_filenames_and_no_ext:
|
| 80 |
+
found = lora_paths[lora_filenames_and_no_ext.index(file_path)]
|
| 81 |
+
if log_node is not None:
|
| 82 |
+
log_node_info(log_node, f'Matched Lora input "{file_path}" to "{found}".')
|
| 83 |
+
return found
|
| 84 |
+
|
| 85 |
+
# And, one last forcing the input to be the same
|
| 86 |
+
file_path_force_filename_and_no_ext = os.path.splitext(os.path.basename(file_path))[0]
|
| 87 |
+
if file_path_force_filename_and_no_ext in lora_filenames_and_no_ext:
|
| 88 |
+
found = lora_paths[lora_filenames_and_no_ext.index(file_path_force_filename_and_no_ext)]
|
| 89 |
+
if log_node is not None:
|
| 90 |
+
log_node_info(log_node, f'Matched Lora input "{file_path}" to "{found}".')
|
| 91 |
+
return found
|
| 92 |
+
|
| 93 |
+
# Finally, super fuzzy, we'll just check if the input exists in the path at all.
|
| 94 |
+
for index, lora_path in enumerate(lora_paths):
|
| 95 |
+
if file_path in lora_path:
|
| 96 |
+
found = lora_paths[index]
|
| 97 |
+
if log_node is not None:
|
| 98 |
+
log_node_warn(log_node, f'Fuzzy-matched Lora input "{file_path}" to "{found}".')
|
| 99 |
+
return found
|
| 100 |
+
|
| 101 |
+
if log_node is not None:
|
| 102 |
+
log_node_warn(log_node, f'Lora "{file_path}" not found, skipping.')
|
| 103 |
+
|
| 104 |
+
return None
|
custom_nodes/rgthree-comfy/py/power_puter.py
ADDED
|
@@ -0,0 +1,842 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""The Power Puter is a powerful node that can compute and evaluate Python-like code safely allowing
|
| 2 |
+
for complex operations for primitives and workflow items for output. From string concatenation, to
|
| 3 |
+
math operations, list comprehension, and node value output.
|
| 4 |
+
|
| 5 |
+
Originally based off https://github.com/pythongosssss/ComfyUI-Custom-Scripts/blob/aac13aa7ce35b07d43633c3bbe654a38c00d74f5/py/math_expression.py
|
| 6 |
+
under an MIT License https://github.com/pythongosssss/ComfyUI-Custom-Scripts/blob/aac13aa7ce35b07d43633c3bbe654a38c00d74f5/LICENSE
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import math
|
| 10 |
+
import ast
|
| 11 |
+
import json
|
| 12 |
+
import random
|
| 13 |
+
import dataclasses
|
| 14 |
+
import re
|
| 15 |
+
import time
|
| 16 |
+
import operator as op
|
| 17 |
+
import datetime
|
| 18 |
+
import numpy as np
|
| 19 |
+
|
| 20 |
+
from typing import Any, Callable, Iterable, Optional, Union
|
| 21 |
+
from types import MappingProxyType
|
| 22 |
+
|
| 23 |
+
from .constants import get_category, get_name
|
| 24 |
+
from .utils import ByPassTypeTuple, FlexibleOptionalInputType, any_type, get_dict_value
|
| 25 |
+
from .log import log_node_error, log_node_warn, log_node_info
|
| 26 |
+
|
| 27 |
+
from .power_lora_loader import RgthreePowerLoraLoader
|
| 28 |
+
|
| 29 |
+
from nodes import ImageBatch
|
| 30 |
+
from comfy_extras.nodes_latent import LatentBatch
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class LoopBreak(Exception):
|
| 34 |
+
"""A special error type that is caught in a loop for correct breaking behavior."""
|
| 35 |
+
|
| 36 |
+
def __init__(self):
|
| 37 |
+
super().__init__('Cannot use "break" outside of a loop.')
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class LoopContinue(Exception):
|
| 41 |
+
"""A special error type that is caught in a loop for correct continue behavior."""
|
| 42 |
+
|
| 43 |
+
def __init__(self):
|
| 44 |
+
super().__init__('Cannot use "continue" outside of a loop.')
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
@dataclasses.dataclass(frozen=True) # Note, kw_only=True is only python 3.10+
|
| 48 |
+
class Function():
|
| 49 |
+
"""Function data.
|
| 50 |
+
|
| 51 |
+
Attributes:
|
| 52 |
+
name: The name of the function as called from the node.
|
| 53 |
+
call: The callable (reference, lambda, etc), or a string if on _Puter instance.
|
| 54 |
+
args: A tuple that represents the minimum and maximum number of args (or arg for no limit).
|
| 55 |
+
"""
|
| 56 |
+
|
| 57 |
+
name: str
|
| 58 |
+
call: Union[Callable, str]
|
| 59 |
+
args: tuple[int, Optional[int]]
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def purge_vram(purge_models=True):
|
| 63 |
+
"""Purges vram and, optionally, unloads models."""
|
| 64 |
+
import gc
|
| 65 |
+
import torch
|
| 66 |
+
gc.collect()
|
| 67 |
+
if torch.cuda.is_available():
|
| 68 |
+
torch.cuda.empty_cache()
|
| 69 |
+
torch.cuda.ipc_collect()
|
| 70 |
+
if purge_models:
|
| 71 |
+
import comfy
|
| 72 |
+
comfy.model_management.unload_all_models()
|
| 73 |
+
comfy.model_management.soft_empty_cache()
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def batch(*args):
|
| 77 |
+
"""Batches multiple image or latents together."""
|
| 78 |
+
|
| 79 |
+
def check_is_latent(item) -> bool:
|
| 80 |
+
return isinstance(item, dict) and 'samples' in item
|
| 81 |
+
|
| 82 |
+
args = list(args)
|
| 83 |
+
result = args.pop(0)
|
| 84 |
+
is_latent = check_is_latent(result)
|
| 85 |
+
node = LatentBatch() if is_latent else ImageBatch()
|
| 86 |
+
|
| 87 |
+
for arg in args:
|
| 88 |
+
if is_latent != check_is_latent(arg):
|
| 89 |
+
raise ValueError(
|
| 90 |
+
f'batch() error: Expecting "{"LATENT" if is_latent else "IMAGE"}"'
|
| 91 |
+
f' but got "{"IMAGE" if is_latent else "LATENT"}".'
|
| 92 |
+
)
|
| 93 |
+
result = node.batch(result, arg)[0]
|
| 94 |
+
return result
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
_BUILTIN_FN_PREFIX = '__rgthreefn.'
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def _get_built_in_fn_key(fn: Function) -> str:
|
| 101 |
+
"""Returns a key for a built-in function."""
|
| 102 |
+
return f'{_BUILTIN_FN_PREFIX}{hash(fn.name)}'
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def _get_built_in_fn_by_key(fn_key: str):
|
| 106 |
+
"""Returns the `Function` for the provided key (purposefully, not name)."""
|
| 107 |
+
if not fn_key.startswith(_BUILTIN_FN_PREFIX) or fn_key not in _BUILT_INS_BY_NAME_AND_KEY:
|
| 108 |
+
raise ValueError('No built in function found.')
|
| 109 |
+
return _BUILT_INS_BY_NAME_AND_KEY[fn_key]
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
_BUILT_IN_FNS_LIST = [
|
| 113 |
+
Function(name="round", call=round, args=(1, 2)),
|
| 114 |
+
Function(name="ceil", call=math.ceil, args=(1, 1)),
|
| 115 |
+
Function(name="floor", call=math.floor, args=(1, 1)),
|
| 116 |
+
Function(name="sqrt", call=math.sqrt, args=(1, 1)),
|
| 117 |
+
Function(name="min", call=min, args=(2, None)),
|
| 118 |
+
Function(name="max", call=max, args=(2, None)),
|
| 119 |
+
Function(name=".random_int", call=random.randint, args=(2, 2)),
|
| 120 |
+
Function(name=".random_choice", call=random.choice, args=(1, 1)),
|
| 121 |
+
Function(name=".random_seed", call=random.seed, args=(1, 1)),
|
| 122 |
+
Function(name="re", call=re.compile, args=(1, 1)),
|
| 123 |
+
Function(name="len", call=len, args=(1, 1)),
|
| 124 |
+
Function(name="enumerate", call=enumerate, args=(1, 1)),
|
| 125 |
+
Function(name="range", call=range, args=(1, 3)),
|
| 126 |
+
# Casts
|
| 127 |
+
Function(name="int", call=int, args=(1, 1)),
|
| 128 |
+
Function(name="float", call=float, args=(1, 1)),
|
| 129 |
+
Function(name="str", call=str, args=(1, 1)),
|
| 130 |
+
Function(name="bool", call=bool, args=(1, 1)),
|
| 131 |
+
Function(name="list", call=list, args=(1, 1)),
|
| 132 |
+
Function(name="tuple", call=tuple, args=(1, 1)),
|
| 133 |
+
# Special
|
| 134 |
+
Function(name="dir", call=dir, args=(1, 1)),
|
| 135 |
+
Function(name="type", call=type, args=(1, 1)),
|
| 136 |
+
Function(name="print", call=print, args=(0, None)),
|
| 137 |
+
# Comfy Specials
|
| 138 |
+
Function(name="node", call='_get_node', args=(0, 1)),
|
| 139 |
+
Function(name="nodes", call='_get_nodes', args=(0, 1)),
|
| 140 |
+
Function(name="input_node", call='_get_input_node', args=(0, 1)),
|
| 141 |
+
Function(name="purge_vram", call=purge_vram, args=(0, 1)),
|
| 142 |
+
Function(name="batch", call=batch, args=(2, None)),
|
| 143 |
+
]
|
| 144 |
+
|
| 145 |
+
_BUILT_INS_BY_NAME_AND_KEY = {
|
| 146 |
+
fn.name: fn for fn in _BUILT_IN_FNS_LIST
|
| 147 |
+
} | {
|
| 148 |
+
key: fn for fn in _BUILT_IN_FNS_LIST if (key := _get_built_in_fn_key(fn))
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
_BUILT_INS = MappingProxyType(
|
| 152 |
+
{fn.name: key for fn in _BUILT_IN_FNS_LIST if (key := _get_built_in_fn_key(fn))} | {
|
| 153 |
+
'random':
|
| 154 |
+
MappingProxyType({
|
| 155 |
+
'int': _get_built_in_fn_key(_BUILT_INS_BY_NAME_AND_KEY['.random_int']),
|
| 156 |
+
'choice': _get_built_in_fn_key(_BUILT_INS_BY_NAME_AND_KEY['.random_choice']),
|
| 157 |
+
'seed': _get_built_in_fn_key(_BUILT_INS_BY_NAME_AND_KEY['.random_seed']),
|
| 158 |
+
}),
|
| 159 |
+
}
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
# A dict of types to blocked attributes/methods. Used to disallow file system access or other
|
| 163 |
+
# invocations we may want to block. Necessary for any instance type that is possible to create from
|
| 164 |
+
# the code or standard ComfyUI inputs.
|
| 165 |
+
#
|
| 166 |
+
# For instance, a user does not have access to the numpy module directly, so they cannot invoke
|
| 167 |
+
# `numpy.save`. However, a user can access a numpy.ndarray instance from a tensor and, from there,
|
| 168 |
+
# an attempt to call `tofile` or `dump` etc. would need to be blocked.
|
| 169 |
+
_BLOCKED_METHODS_OR_ATTRS = MappingProxyType({np.ndarray: ['tofile', 'dump']})
|
| 170 |
+
|
| 171 |
+
# Special functions by class type (called from the Attrs.)
|
| 172 |
+
_SPECIAL_FUNCTIONS = {
|
| 173 |
+
RgthreePowerLoraLoader.NAME: {
|
| 174 |
+
# Get a list of the enabled loras from a power lora loader.
|
| 175 |
+
"loras": RgthreePowerLoraLoader.get_enabled_loras_from_prompt_node,
|
| 176 |
+
"triggers": RgthreePowerLoraLoader.get_enabled_triggers_from_prompt_node,
|
| 177 |
+
}
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
# Series of regex checks for usage of a non-deterministic function. Using these is fine, but means
|
| 181 |
+
# the output can't be cached because it's either random, or is associated with another node that is
|
| 182 |
+
# not connected to ours (like looking up a node in the prompt). Using these means downstream nodes
|
| 183 |
+
# would always be run; that is fine for something like a final JSON output, but less so for a prompt
|
| 184 |
+
# text.
|
| 185 |
+
_NON_DETERMINISTIC_FUNCTION_CHECKS = [r'(?<!input_)(nodes?)\(',]
|
| 186 |
+
|
| 187 |
+
_OPERATORS = {
|
| 188 |
+
# operator
|
| 189 |
+
ast.Add: op.add,
|
| 190 |
+
ast.Sub: op.sub,
|
| 191 |
+
ast.Mult: op.mul,
|
| 192 |
+
ast.MatMult: op.matmul,
|
| 193 |
+
ast.Div: op.truediv,
|
| 194 |
+
ast.Mod: op.mod,
|
| 195 |
+
ast.Pow: op.pow,
|
| 196 |
+
ast.RShift: op.rshift,
|
| 197 |
+
ast.LShift: op.lshift,
|
| 198 |
+
ast.BitOr: op.or_,
|
| 199 |
+
ast.BitXor: op.xor,
|
| 200 |
+
ast.BitAnd: op.and_,
|
| 201 |
+
ast.FloorDiv: op.floordiv,
|
| 202 |
+
# boolop
|
| 203 |
+
ast.And: lambda a, b: a and b,
|
| 204 |
+
ast.Or: lambda a, b: a or b,
|
| 205 |
+
# unaryop
|
| 206 |
+
ast.Invert: op.invert,
|
| 207 |
+
ast.Not: lambda a: 0 if a else 1,
|
| 208 |
+
ast.USub: op.neg,
|
| 209 |
+
# cmpop
|
| 210 |
+
ast.Eq: op.eq,
|
| 211 |
+
ast.NotEq: op.ne,
|
| 212 |
+
ast.Lt: op.lt,
|
| 213 |
+
ast.LtE: op.le,
|
| 214 |
+
ast.Gt: op.gt,
|
| 215 |
+
ast.GtE: op.ge,
|
| 216 |
+
ast.Is: op.is_,
|
| 217 |
+
ast.IsNot: op.is_not,
|
| 218 |
+
ast.In: lambda a, b: a in b,
|
| 219 |
+
ast.NotIn: lambda a, b: a not in b,
|
| 220 |
+
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
_NODE_NAME = get_name("Power Puter")
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def _update_code(code: str, unique_id: str, log=False):
|
| 227 |
+
"""Updates the code to either newer syntax or general cleaning."""
|
| 228 |
+
|
| 229 |
+
# Change usage of `input_node` so the passed variable is a string, if it isn't. So, instead of
|
| 230 |
+
# `input_node(a)` it needs to be `input_node('a')`
|
| 231 |
+
code = re.sub(r'input_node\(([^\'"].*?)\)', r'input_node("\1")', code)
|
| 232 |
+
|
| 233 |
+
# Update use of `random_int` to `random.int`
|
| 234 |
+
srch = re.compile(r'random_int\(')
|
| 235 |
+
if re.search(srch, code):
|
| 236 |
+
if log:
|
| 237 |
+
log_node_warn(
|
| 238 |
+
_NODE_NAME, f"Power Puter node #{unique_id} should update to use the `random.int`"
|
| 239 |
+
" built-in instead of `random_int`."
|
| 240 |
+
)
|
| 241 |
+
code = re.sub(srch, 'random.int(', code)
|
| 242 |
+
|
| 243 |
+
# Update use of `random_choice` to `random.choice`
|
| 244 |
+
srch = re.compile(r'random_choice\(')
|
| 245 |
+
if re.search(srch, code):
|
| 246 |
+
if log:
|
| 247 |
+
log_node_warn(
|
| 248 |
+
_NODE_NAME, f"Power Puter node #{unique_id} should update to use the `random.choice`"
|
| 249 |
+
" built-in instead of `random_choice`."
|
| 250 |
+
)
|
| 251 |
+
code = re.sub(srch, 'random.choice(', code)
|
| 252 |
+
return code
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
class RgthreePowerPuter:
|
| 256 |
+
"""A powerful node that can compute and evaluate expressions and output as various types."""
|
| 257 |
+
|
| 258 |
+
NAME = _NODE_NAME
|
| 259 |
+
CATEGORY = get_category()
|
| 260 |
+
|
| 261 |
+
@classmethod
|
| 262 |
+
def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring
|
| 263 |
+
return {
|
| 264 |
+
"required": {},
|
| 265 |
+
"optional": FlexibleOptionalInputType(any_type),
|
| 266 |
+
"hidden": {
|
| 267 |
+
"unique_id": "UNIQUE_ID",
|
| 268 |
+
"extra_pnginfo": "EXTRA_PNGINFO",
|
| 269 |
+
"prompt": "PROMPT",
|
| 270 |
+
"dynprompt": "DYNPROMPT"
|
| 271 |
+
},
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
RETURN_TYPES = ByPassTypeTuple((any_type,))
|
| 275 |
+
RETURN_NAMES = ByPassTypeTuple(("*",))
|
| 276 |
+
FUNCTION = "main"
|
| 277 |
+
|
| 278 |
+
@classmethod
|
| 279 |
+
def IS_CHANGED(cls, **kwargs):
|
| 280 |
+
"""Forces a changed state if we could be unaware of data changes (like using `node()`)."""
|
| 281 |
+
|
| 282 |
+
code = _update_code(kwargs['code'], unique_id=kwargs['unique_id'])
|
| 283 |
+
# Strip string literals and comments.
|
| 284 |
+
code = re.sub(r"'[^']+?'", "''", code)
|
| 285 |
+
code = re.sub(r'"[^"]+?"', '""', code)
|
| 286 |
+
code = re.sub(r'#.*\n', '\n', code)
|
| 287 |
+
|
| 288 |
+
# If we have a non-deterministic function, then we'll always consider ourself changed since we
|
| 289 |
+
# cannot be sure that the data would be the same (random, another unconnected node, etc).
|
| 290 |
+
for check in _NON_DETERMINISTIC_FUNCTION_CHECKS:
|
| 291 |
+
matches = re.search(check, code)
|
| 292 |
+
if matches:
|
| 293 |
+
log_node_warn(
|
| 294 |
+
_NODE_NAME,
|
| 295 |
+
f"Note, Power Puter (node #{kwargs['unique_id']}) cannot be cached b/c it's using a"
|
| 296 |
+
f" non-deterministic function call. Matches function call for '{matches.group(1)}'."
|
| 297 |
+
)
|
| 298 |
+
return time.time()
|
| 299 |
+
|
| 300 |
+
# Advanced checks.
|
| 301 |
+
has_rand_seed = re.search(r'random\.seed\(', code)
|
| 302 |
+
has_rand_int_or_choice = re.search(r'(?<!\.)(random\.(int|choice))\(', code)
|
| 303 |
+
if has_rand_int_or_choice:
|
| 304 |
+
if not has_rand_seed or has_rand_seed.span()[0] > has_rand_int_or_choice.span()[0]:
|
| 305 |
+
log_node_warn(
|
| 306 |
+
_NODE_NAME,
|
| 307 |
+
f"Note, Power Puter (node #{kwargs['unique_id']}) cannot be cached b/c it's using a"
|
| 308 |
+
" non-deterministic function call. Matches function call for"
|
| 309 |
+
f" `{has_rand_int_or_choice.group(1)}`."
|
| 310 |
+
)
|
| 311 |
+
return time.time()
|
| 312 |
+
if has_rand_seed:
|
| 313 |
+
log_node_info(
|
| 314 |
+
_NODE_NAME,
|
| 315 |
+
f"Power Puter node #{kwargs['unique_id']} WILL be cached eventhough it's using"
|
| 316 |
+
f" a non-deterministic random call `{has_rand_int_or_choice.group(1)}` because it also"
|
| 317 |
+
f" calls `random.seed` first. NOTE: Please ensure that the seed value is deterministic."
|
| 318 |
+
)
|
| 319 |
+
|
| 320 |
+
return 42
|
| 321 |
+
|
| 322 |
+
def main(self, **kwargs):
|
| 323 |
+
"""Does the nodes' work."""
|
| 324 |
+
code = kwargs['code']
|
| 325 |
+
unique_id = kwargs['unique_id']
|
| 326 |
+
pnginfo = kwargs['extra_pnginfo']
|
| 327 |
+
workflow = pnginfo["workflow"] if "workflow" in pnginfo else {"nodes": []}
|
| 328 |
+
prompt = kwargs['prompt']
|
| 329 |
+
dynprompt = kwargs['dynprompt']
|
| 330 |
+
|
| 331 |
+
outputs = get_dict_value(kwargs, 'outputs.outputs', None)
|
| 332 |
+
if not outputs:
|
| 333 |
+
output = kwargs.get('output', None)
|
| 334 |
+
if not output:
|
| 335 |
+
output = 'STRING'
|
| 336 |
+
outputs = [output]
|
| 337 |
+
|
| 338 |
+
ctx = {}
|
| 339 |
+
# Set variable names, defaulting to None instead of KeyErrors
|
| 340 |
+
for c in list('abcdefghijklmnopqrstuvwxyz'):
|
| 341 |
+
ctx[c] = kwargs[c] if c in kwargs else None
|
| 342 |
+
|
| 343 |
+
code = _update_code(kwargs['code'], unique_id=kwargs['unique_id'], log=True)
|
| 344 |
+
|
| 345 |
+
eva = _Puter(
|
| 346 |
+
code=code,
|
| 347 |
+
ctx=ctx,
|
| 348 |
+
workflow=workflow,
|
| 349 |
+
prompt=prompt,
|
| 350 |
+
dynprompt=dynprompt,
|
| 351 |
+
unique_id=unique_id
|
| 352 |
+
)
|
| 353 |
+
values = eva.execute()
|
| 354 |
+
|
| 355 |
+
# Check if we have multiple outputs that the returned value is a tuple and raise if not.
|
| 356 |
+
if len(outputs) > 1 and not isinstance(values, tuple):
|
| 357 |
+
t = re.sub(r'^<[a-z]*\s(.*?)>$', r'\1', str(type(values)))
|
| 358 |
+
msg = (
|
| 359 |
+
f"When using multiple node outputs, the value from the code should be a 'tuple' with the"
|
| 360 |
+
f" number of items equal to the number of outputs. But value from code was of type {t}."
|
| 361 |
+
)
|
| 362 |
+
log_node_error(_NODE_NAME, f'{msg}\n')
|
| 363 |
+
raise ValueError(msg)
|
| 364 |
+
|
| 365 |
+
if len(outputs) == 1:
|
| 366 |
+
values = (values,)
|
| 367 |
+
|
| 368 |
+
if len(values) > len(outputs):
|
| 369 |
+
log_node_warn(
|
| 370 |
+
_NODE_NAME,
|
| 371 |
+
f"Expected value from code to be tuple with {len(outputs)} items, but value from code had"
|
| 372 |
+
f" {len(values)} items. Extra values will be dropped."
|
| 373 |
+
)
|
| 374 |
+
elif len(values) < len(outputs):
|
| 375 |
+
log_node_warn(
|
| 376 |
+
_NODE_NAME,
|
| 377 |
+
f"Expected value from code to be tuple with {len(outputs)} items, but value from code had"
|
| 378 |
+
f" {len(values)} items. Extra outputs will be null."
|
| 379 |
+
)
|
| 380 |
+
|
| 381 |
+
# Now, we'll go over out return tuple, and cast as the output types.
|
| 382 |
+
response = []
|
| 383 |
+
for i, output in enumerate(outputs):
|
| 384 |
+
value = values[i] if len(values) > i else None
|
| 385 |
+
if value is not None:
|
| 386 |
+
if output == 'INT':
|
| 387 |
+
value = int(value)
|
| 388 |
+
elif output == 'FLOAT':
|
| 389 |
+
value = float(value)
|
| 390 |
+
# Accidentally defined "BOOL" when should have been "BOOLEAN."
|
| 391 |
+
# TODO: Can prob get rid of BOOl after a bit when UIs would be updated from sending
|
| 392 |
+
# BOOL incorrectly.
|
| 393 |
+
elif output in ('BOOL', 'BOOLEAN'):
|
| 394 |
+
value = bool(value)
|
| 395 |
+
elif output == 'STRING':
|
| 396 |
+
if isinstance(value, (dict, list)):
|
| 397 |
+
value = json.dumps(value, indent=2)
|
| 398 |
+
else:
|
| 399 |
+
value = str(value)
|
| 400 |
+
elif output == '*':
|
| 401 |
+
# Do nothing, the output will be passed as-is. This could be anything and it's up to the
|
| 402 |
+
# user to control the intended output, like passing through an input value, etc.
|
| 403 |
+
pass
|
| 404 |
+
response.append(value)
|
| 405 |
+
return tuple(response)
|
| 406 |
+
|
| 407 |
+
|
| 408 |
+
class _Puter:
|
| 409 |
+
"""The main computation evaluator, using ast.parse the code.
|
| 410 |
+
|
| 411 |
+
See https://www.basicexamples.com/example/python/ast for examples.
|
| 412 |
+
"""
|
| 413 |
+
|
| 414 |
+
def __init__(self, *, code: str, ctx: dict[str, Any], workflow, prompt, dynprompt, unique_id):
|
| 415 |
+
ctx = ctx or {}
|
| 416 |
+
self._ctx = {**ctx}
|
| 417 |
+
self._code = code
|
| 418 |
+
self._workflow = workflow
|
| 419 |
+
self._prompt = prompt
|
| 420 |
+
self._unique_id = unique_id
|
| 421 |
+
self._dynprompt = dynprompt
|
| 422 |
+
# These are now expanded lazily when needed.
|
| 423 |
+
self._prompt_nodes = None
|
| 424 |
+
self._prompt_node = None
|
| 425 |
+
|
| 426 |
+
def execute(self, code=Optional[str]) -> Any:
|
| 427 |
+
"""Evaluates a the code block."""
|
| 428 |
+
|
| 429 |
+
# Always store random state and initialize a new seed. We'll restore the state later.
|
| 430 |
+
initial_random_state = random.getstate()
|
| 431 |
+
random.seed(datetime.datetime.now().timestamp())
|
| 432 |
+
last_value = None
|
| 433 |
+
try:
|
| 434 |
+
code = code or self._code
|
| 435 |
+
node = ast.parse(self._code)
|
| 436 |
+
ctx = {**self._ctx}
|
| 437 |
+
for body in node.body:
|
| 438 |
+
last_value = self._eval_statement(body, ctx)
|
| 439 |
+
# If we got a return, then that's it folks.
|
| 440 |
+
if isinstance(body, ast.Return):
|
| 441 |
+
break
|
| 442 |
+
except:
|
| 443 |
+
random.setstate(initial_random_state)
|
| 444 |
+
raise
|
| 445 |
+
random.setstate(initial_random_state)
|
| 446 |
+
return last_value
|
| 447 |
+
|
| 448 |
+
def _get_prompt_nodes(self):
|
| 449 |
+
"""Expands the prompt nodes lazily from the dynamic prompt.
|
| 450 |
+
|
| 451 |
+
https://github.com/comfyanonymous/ComfyUI/blob/fc657f471a29d07696ca16b566000e8e555d67d1/comfy_execution/graph.py#L22
|
| 452 |
+
"""
|
| 453 |
+
if self._prompt_nodes is None:
|
| 454 |
+
self._prompt_nodes = []
|
| 455 |
+
if self._dynprompt:
|
| 456 |
+
all_ids = self._dynprompt.all_node_ids()
|
| 457 |
+
self._prompt_nodes = [{'id': k} | {**self._dynprompt.get_node(k)} for k in all_ids]
|
| 458 |
+
return self._prompt_nodes
|
| 459 |
+
|
| 460 |
+
def _get_prompt_node(self):
|
| 461 |
+
if self._prompt_nodes is None:
|
| 462 |
+
self._prompt_node = [n for n in self._get_prompt_nodes() if n['id'] == self._unique_id][0]
|
| 463 |
+
return self._prompt_node
|
| 464 |
+
|
| 465 |
+
def _get_nodes(self, node_id: Union[int, str, re.Pattern, None] = None) -> list[Any]:
|
| 466 |
+
"""Get a list of the nodes that match the node_id, or all the nodes in the prompt."""
|
| 467 |
+
nodes = self._get_prompt_nodes().copy()
|
| 468 |
+
if not node_id:
|
| 469 |
+
return nodes
|
| 470 |
+
|
| 471 |
+
if isinstance(node_id, re.Pattern):
|
| 472 |
+
found = [n for n in nodes if re.search(node_id, get_dict_value(n, '_meta.title', ''))]
|
| 473 |
+
else:
|
| 474 |
+
node_id = str(node_id)
|
| 475 |
+
found = None
|
| 476 |
+
if re.match(r'\d+$', node_id):
|
| 477 |
+
found = [n for n in nodes if node_id == n['id']]
|
| 478 |
+
if not found:
|
| 479 |
+
found = [n for n in nodes if node_id == get_dict_value(n, '_meta.title', '')]
|
| 480 |
+
return found
|
| 481 |
+
|
| 482 |
+
def _get_node(self, node_id: Union[int, str, re.Pattern, None] = None) -> Union[Any, None]:
|
| 483 |
+
"""Returns a prompt-node from the hidden prompt."""
|
| 484 |
+
if node_id is None:
|
| 485 |
+
return self._get_prompt_node()
|
| 486 |
+
nodes = self._get_nodes(node_id)
|
| 487 |
+
if nodes and len(nodes) > 1:
|
| 488 |
+
log_node_warn(_NODE_NAME, f"More than one node found for '{node_id}'. Returning first.")
|
| 489 |
+
return nodes[0] if nodes else None
|
| 490 |
+
|
| 491 |
+
def _get_input_node(self, input_name, node=None):
|
| 492 |
+
"""Gets the (non-muted) node of an input connection from a node (default to the power puter)."""
|
| 493 |
+
node = node if node else self._get_prompt_node()
|
| 494 |
+
try:
|
| 495 |
+
connected_node_id = node['inputs'][input_name][0]
|
| 496 |
+
return [n for n in self._get_prompt_nodes() if n['id'] == connected_node_id][0]
|
| 497 |
+
except (TypeError, IndexError, KeyError):
|
| 498 |
+
log_node_warn(_NODE_NAME, f'No input node found for "{input_name}". ')
|
| 499 |
+
return None
|
| 500 |
+
|
| 501 |
+
def _eval_statement(self, stmt: ast.AST, ctx: dict, prev_stmt: Union[ast.AST, None] = None):
|
| 502 |
+
"""Evaluates an ast.stmt."""
|
| 503 |
+
|
| 504 |
+
if '__returned__' in ctx:
|
| 505 |
+
return ctx['__returned__']
|
| 506 |
+
|
| 507 |
+
# print('\n\n----: _eval_statement')
|
| 508 |
+
# print(type(stmt))
|
| 509 |
+
# print(ctx)
|
| 510 |
+
|
| 511 |
+
if isinstance(stmt, (ast.FormattedValue, ast.Expr)):
|
| 512 |
+
return self._eval_statement(stmt.value, ctx=ctx)
|
| 513 |
+
|
| 514 |
+
if isinstance(stmt, (ast.Constant, ast.Num)):
|
| 515 |
+
return stmt.n
|
| 516 |
+
|
| 517 |
+
if isinstance(stmt, ast.BinOp):
|
| 518 |
+
left = self._eval_statement(stmt.left, ctx=ctx)
|
| 519 |
+
right = self._eval_statement(stmt.right, ctx=ctx)
|
| 520 |
+
return _OPERATORS[type(stmt.op)](left, right)
|
| 521 |
+
|
| 522 |
+
if isinstance(stmt, ast.BoolOp):
|
| 523 |
+
is_and = isinstance(stmt.op, ast.And)
|
| 524 |
+
is_or = isinstance(stmt.op, ast.Or)
|
| 525 |
+
stmt_value_eval = None
|
| 526 |
+
for stmt_value in stmt.values:
|
| 527 |
+
stmt_value_eval = self._eval_statement(stmt_value, ctx=ctx)
|
| 528 |
+
# If we're an and operator and have a falsyt value, then we stop and return. Likewise, if
|
| 529 |
+
# we're an or operator and have a truthy value, we can stop and return.
|
| 530 |
+
if (is_and and not stmt_value_eval) or (is_or and stmt_value_eval):
|
| 531 |
+
return stmt_value_eval
|
| 532 |
+
# Always return the last if we made it here w/o success.
|
| 533 |
+
return stmt_value_eval
|
| 534 |
+
|
| 535 |
+
if isinstance(stmt, ast.UnaryOp):
|
| 536 |
+
return _OPERATORS[type(stmt.op)](self._eval_statement(stmt.operand, ctx=ctx))
|
| 537 |
+
|
| 538 |
+
if isinstance(stmt, (ast.Attribute, ast.Subscript)):
|
| 539 |
+
# Like: node(14).inputs.sampler_name (Attribute)
|
| 540 |
+
# Like: node(14)['inputs']['sampler_name'] (Subscript)
|
| 541 |
+
item = self._eval_statement(stmt.value, ctx=ctx)
|
| 542 |
+
attr = None
|
| 543 |
+
# if hasattr(stmt, 'attr'):
|
| 544 |
+
if isinstance(stmt, ast.Attribute):
|
| 545 |
+
attr = stmt.attr
|
| 546 |
+
else:
|
| 547 |
+
# Slice could be a name or a constant; evaluate it
|
| 548 |
+
attr = self._eval_statement(stmt.slice, ctx=ctx)
|
| 549 |
+
# Check if we're blocking access to this attribute/method on this item type.
|
| 550 |
+
for typ, names in _BLOCKED_METHODS_OR_ATTRS.items():
|
| 551 |
+
if isinstance(item, typ) and isinstance(attr, str) and attr in names:
|
| 552 |
+
raise ValueError(f'Disallowed access to "{attr}" for type {typ}.')
|
| 553 |
+
try:
|
| 554 |
+
val = item[attr]
|
| 555 |
+
except (TypeError, IndexError, KeyError):
|
| 556 |
+
try:
|
| 557 |
+
val = getattr(item, attr)
|
| 558 |
+
except AttributeError:
|
| 559 |
+
# If we're a dict, then just return None instead of error; saves time.
|
| 560 |
+
if isinstance(item, dict):
|
| 561 |
+
# Any special cases in the _SPECIAL_FUNCTIONS
|
| 562 |
+
class_type = get_dict_value(item, "class_type")
|
| 563 |
+
if class_type in _SPECIAL_FUNCTIONS and attr in _SPECIAL_FUNCTIONS[class_type]:
|
| 564 |
+
val = _SPECIAL_FUNCTIONS[class_type][attr]
|
| 565 |
+
# If our previous statment was a Call, then send back a tuple of the callable and
|
| 566 |
+
# the evaluated item, and it will make the call; perhaps also adding other arguments
|
| 567 |
+
# only it knows about.
|
| 568 |
+
if isinstance(prev_stmt, ast.Call):
|
| 569 |
+
return (val, item)
|
| 570 |
+
val = val(item)
|
| 571 |
+
else:
|
| 572 |
+
val = None
|
| 573 |
+
else:
|
| 574 |
+
raise
|
| 575 |
+
return val
|
| 576 |
+
|
| 577 |
+
if isinstance(stmt, (ast.List, ast.Tuple)):
|
| 578 |
+
value = []
|
| 579 |
+
for elt in stmt.elts:
|
| 580 |
+
value.append(self._eval_statement(elt, ctx=ctx))
|
| 581 |
+
return tuple(value) if isinstance(stmt, ast.Tuple) else value
|
| 582 |
+
|
| 583 |
+
if isinstance(stmt, ast.Dict):
|
| 584 |
+
the_dict = {}
|
| 585 |
+
if stmt.keys:
|
| 586 |
+
if len(stmt.keys) != len(stmt.values):
|
| 587 |
+
raise ValueError('Expected same number of keys as values for dict.')
|
| 588 |
+
for i, k in enumerate(stmt.keys):
|
| 589 |
+
item_key = self._eval_statement(k, ctx=ctx)
|
| 590 |
+
item_value = self._eval_statement(stmt.values[i], ctx=ctx)
|
| 591 |
+
the_dict[item_key] = item_value
|
| 592 |
+
return the_dict
|
| 593 |
+
|
| 594 |
+
# f-strings: https://www.basicexamples.com/example/python/ast-JoinedStr
|
| 595 |
+
# Note, this will str() all evaluated items in the fstrings, and doesn't handle f-string
|
| 596 |
+
# directives, like padding, etc.
|
| 597 |
+
if isinstance(stmt, ast.JoinedStr):
|
| 598 |
+
vals = [str(self._eval_statement(v, ctx=ctx)) for v in stmt.values]
|
| 599 |
+
val = ''.join(vals)
|
| 600 |
+
return val
|
| 601 |
+
|
| 602 |
+
if isinstance(stmt, ast.Slice):
|
| 603 |
+
if not stmt.lower or not stmt.upper:
|
| 604 |
+
raise ValueError('Unhandled Slice w/o lower or upper.')
|
| 605 |
+
slice_lower = self._eval_statement(stmt.lower, ctx=ctx)
|
| 606 |
+
slice_upper = self._eval_statement(stmt.upper, ctx=ctx)
|
| 607 |
+
if stmt.step:
|
| 608 |
+
slice_step = self._eval_statement(stmt.step, ctx=ctx)
|
| 609 |
+
return slice(slice_lower, slice_upper, slice_step)
|
| 610 |
+
return slice(slice_lower, slice_upper)
|
| 611 |
+
|
| 612 |
+
if isinstance(stmt, ast.Name):
|
| 613 |
+
if stmt.id in ctx:
|
| 614 |
+
val = ctx[stmt.id]
|
| 615 |
+
return val
|
| 616 |
+
if stmt.id in _BUILT_INS:
|
| 617 |
+
val = _BUILT_INS[stmt.id]
|
| 618 |
+
return val
|
| 619 |
+
raise NameError(f"Name not found: {stmt.id}")
|
| 620 |
+
|
| 621 |
+
if isinstance(stmt, ast.For):
|
| 622 |
+
for_iter = self._eval_statement(stmt.iter, ctx=ctx)
|
| 623 |
+
for item in for_iter:
|
| 624 |
+
# Set the for var(s)
|
| 625 |
+
if isinstance(stmt.target, ast.Name):
|
| 626 |
+
ctx[stmt.target.id] = item
|
| 627 |
+
elif isinstance(stmt.target, ast.Tuple): # dict, like `for k, v in d.entries()`
|
| 628 |
+
for i, elt in enumerate(stmt.target.elts):
|
| 629 |
+
ctx[elt.id] = item[i]
|
| 630 |
+
bodies = stmt.body if isinstance(stmt.body, list) else [stmt.body]
|
| 631 |
+
breaked = False
|
| 632 |
+
for body in bodies:
|
| 633 |
+
# Catch any breaks or continues and handle inside the loop normally.
|
| 634 |
+
try:
|
| 635 |
+
value = self._eval_statement(body, ctx=ctx)
|
| 636 |
+
except (LoopBreak, LoopContinue) as e:
|
| 637 |
+
breaked = isinstance(e, LoopBreak)
|
| 638 |
+
break
|
| 639 |
+
if breaked:
|
| 640 |
+
break
|
| 641 |
+
return None
|
| 642 |
+
|
| 643 |
+
if isinstance(stmt, ast.While):
|
| 644 |
+
while self._eval_statement(stmt.test, ctx=ctx):
|
| 645 |
+
bodies = stmt.body if isinstance(stmt.body, list) else [stmt.body]
|
| 646 |
+
breaked = False
|
| 647 |
+
for body in bodies:
|
| 648 |
+
# Catch any breaks or continues and handle inside the loop normally.
|
| 649 |
+
try:
|
| 650 |
+
value = self._eval_statement(body, ctx=ctx)
|
| 651 |
+
except (LoopBreak, LoopContinue) as e:
|
| 652 |
+
breaked = isinstance(e, LoopBreak)
|
| 653 |
+
break
|
| 654 |
+
if breaked:
|
| 655 |
+
break
|
| 656 |
+
return None
|
| 657 |
+
|
| 658 |
+
if isinstance(stmt, ast.ListComp):
|
| 659 |
+
# Like: [v.lora for name, v in node(19).inputs.items() if name.startswith('lora_')]
|
| 660 |
+
# Like: [v.lower() for v in lora_list]
|
| 661 |
+
# Like: [v for v in l if v.startswith('B')]
|
| 662 |
+
# Like: [v.lower() for v in l if v.startswith('B') or v.startswith('F')]
|
| 663 |
+
# ---
|
| 664 |
+
# Like: [l for n in nodes(re('Loras')).values() if (l := n.loras)]
|
| 665 |
+
final_list = []
|
| 666 |
+
|
| 667 |
+
gen_ctx = {**ctx}
|
| 668 |
+
|
| 669 |
+
generators = [*stmt.generators]
|
| 670 |
+
|
| 671 |
+
def handle_gen(generators: list[ast.comprehension]):
|
| 672 |
+
gen = generators.pop(0)
|
| 673 |
+
if isinstance(gen.target, ast.Name):
|
| 674 |
+
gen_ctx[gen.target.id] = None
|
| 675 |
+
elif isinstance(gen.target, ast.Tuple): # dict, like `for k, v in d.entries()`
|
| 676 |
+
for elt in gen.target.elts:
|
| 677 |
+
gen_ctx[elt.id] = None
|
| 678 |
+
else:
|
| 679 |
+
raise ValueError('Na')
|
| 680 |
+
|
| 681 |
+
gen_iters = None
|
| 682 |
+
# A call, like my_dct.items(), or a named ctx list
|
| 683 |
+
if isinstance(gen.iter, ast.Call):
|
| 684 |
+
gen_iters = self._eval_statement(gen.iter, ctx=gen_ctx)
|
| 685 |
+
elif isinstance(gen.iter, (ast.Name, ast.Attribute, ast.List, ast.Tuple)):
|
| 686 |
+
gen_iters = self._eval_statement(gen.iter, ctx=gen_ctx)
|
| 687 |
+
|
| 688 |
+
if not isinstance(gen_iters, Iterable):
|
| 689 |
+
raise ValueError('No iteraors found for list comprehension')
|
| 690 |
+
|
| 691 |
+
for gen_iter in gen_iters:
|
| 692 |
+
if_ctx = {**gen_ctx}
|
| 693 |
+
if isinstance(gen.target, ast.Tuple): # dict, like `for k, v in d.entries()`
|
| 694 |
+
for i, elt in enumerate(gen.target.elts):
|
| 695 |
+
if_ctx[elt.id] = gen_iter[i]
|
| 696 |
+
else:
|
| 697 |
+
if_ctx[gen.target.id] = gen_iter
|
| 698 |
+
good = True
|
| 699 |
+
for ifcall in gen.ifs:
|
| 700 |
+
if not self._eval_statement(ifcall, ctx=if_ctx):
|
| 701 |
+
good = False
|
| 702 |
+
break
|
| 703 |
+
if not good:
|
| 704 |
+
continue
|
| 705 |
+
gen_ctx.update(if_ctx)
|
| 706 |
+
if len(generators):
|
| 707 |
+
handle_gen(generators)
|
| 708 |
+
else:
|
| 709 |
+
final_list.append(self._eval_statement(stmt.elt, gen_ctx))
|
| 710 |
+
generators.insert(0, gen)
|
| 711 |
+
|
| 712 |
+
handle_gen(generators)
|
| 713 |
+
return final_list
|
| 714 |
+
|
| 715 |
+
if isinstance(stmt, ast.Call):
|
| 716 |
+
call = None
|
| 717 |
+
args = []
|
| 718 |
+
kwargs = {}
|
| 719 |
+
if isinstance(stmt.func, ast.Attribute):
|
| 720 |
+
call = self._eval_statement(stmt.func, prev_stmt=stmt, ctx=ctx)
|
| 721 |
+
if isinstance(call, tuple):
|
| 722 |
+
args.append(call[1])
|
| 723 |
+
call = call[0]
|
| 724 |
+
if not call:
|
| 725 |
+
raise ValueError(f'No call for ast.Call {stmt.func}')
|
| 726 |
+
|
| 727 |
+
name = ''
|
| 728 |
+
if isinstance(stmt.func, ast.Name):
|
| 729 |
+
name = stmt.func.id
|
| 730 |
+
if name in _BUILT_INS:
|
| 731 |
+
call = _BUILT_INS[name]
|
| 732 |
+
|
| 733 |
+
if isinstance(call, str) and call.startswith(_BUILTIN_FN_PREFIX):
|
| 734 |
+
fn = _get_built_in_fn_by_key(call)
|
| 735 |
+
call = fn.call
|
| 736 |
+
if isinstance(call, str):
|
| 737 |
+
call = getattr(self, call)
|
| 738 |
+
num_args = len(stmt.args)
|
| 739 |
+
if num_args < fn.args[0] or (fn.args[1] is not None and num_args > fn.args[1]):
|
| 740 |
+
toErr = " or more" if fn.args[1] is None else f" to {fn.args[1]}"
|
| 741 |
+
raise SyntaxError(f"Invalid function call: {fn.name} requires {fn.args[0]}{toErr} args")
|
| 742 |
+
|
| 743 |
+
if not call:
|
| 744 |
+
raise ValueError(f'No call for ast.Call {name}')
|
| 745 |
+
|
| 746 |
+
for arg in stmt.args:
|
| 747 |
+
args.append(self._eval_statement(arg, ctx=ctx))
|
| 748 |
+
for kwarg in stmt.keywords:
|
| 749 |
+
kwargs[kwarg.arg] = self._eval_statement(kwarg.value, ctx=ctx)
|
| 750 |
+
return call(*args, **kwargs)
|
| 751 |
+
|
| 752 |
+
if isinstance(stmt, ast.Compare):
|
| 753 |
+
l = self._eval_statement(stmt.left, ctx=ctx)
|
| 754 |
+
r = self._eval_statement(stmt.comparators[0], ctx=ctx)
|
| 755 |
+
if isinstance(stmt.ops[0], ast.Eq):
|
| 756 |
+
return 1 if l == r else 0
|
| 757 |
+
if isinstance(stmt.ops[0], ast.NotEq):
|
| 758 |
+
return 1 if l != r else 0
|
| 759 |
+
if isinstance(stmt.ops[0], ast.Gt):
|
| 760 |
+
return 1 if l > r else 0
|
| 761 |
+
if isinstance(stmt.ops[0], ast.GtE):
|
| 762 |
+
return 1 if l >= r else 0
|
| 763 |
+
if isinstance(stmt.ops[0], ast.Lt):
|
| 764 |
+
return 1 if l < r else 0
|
| 765 |
+
if isinstance(stmt.ops[0], ast.LtE):
|
| 766 |
+
return 1 if l <= r else 0
|
| 767 |
+
if isinstance(stmt.ops[0], ast.In):
|
| 768 |
+
return 1 if l in r else 0
|
| 769 |
+
if isinstance(stmt.ops[0], ast.Is):
|
| 770 |
+
return 1 if l is r else 0
|
| 771 |
+
if isinstance(stmt.ops[0], ast.IsNot):
|
| 772 |
+
return 1 if l is not r else 0
|
| 773 |
+
raise NotImplementedError("Operator " + stmt.ops[0].__class__.__name__ + " not supported.")
|
| 774 |
+
|
| 775 |
+
if isinstance(stmt, (ast.If, ast.IfExp)):
|
| 776 |
+
value = self._eval_statement(stmt.test, ctx=ctx)
|
| 777 |
+
if value:
|
| 778 |
+
# ast.If is a list, ast.IfExp is an object.
|
| 779 |
+
bodies = stmt.body if isinstance(stmt.body, list) else [stmt.body]
|
| 780 |
+
for body in bodies:
|
| 781 |
+
value = self._eval_statement(body, ctx=ctx)
|
| 782 |
+
elif stmt.orelse:
|
| 783 |
+
# ast.If is a list, ast.IfExp is an object. TBH, I don't know why the If is a list, it's
|
| 784 |
+
# only ever one item AFAICT.
|
| 785 |
+
orelses = stmt.orelse if isinstance(stmt.orelse, list) else [stmt.orelse]
|
| 786 |
+
for orelse in orelses:
|
| 787 |
+
value = self._eval_statement(orelse, ctx=ctx)
|
| 788 |
+
return value
|
| 789 |
+
|
| 790 |
+
# Assign a variable and add it to our ctx.
|
| 791 |
+
if isinstance(stmt, (ast.Assign, ast.AugAssign)):
|
| 792 |
+
if isinstance(stmt, ast.AugAssign):
|
| 793 |
+
left = self._eval_statement(stmt.target, ctx=ctx)
|
| 794 |
+
right = self._eval_statement(stmt.value, ctx=ctx)
|
| 795 |
+
value = _OPERATORS[type(stmt.op)](left, right)
|
| 796 |
+
target = stmt.target
|
| 797 |
+
else:
|
| 798 |
+
value = self._eval_statement(stmt.value, ctx=ctx)
|
| 799 |
+
if len(stmt.targets) != 1:
|
| 800 |
+
raise ValueError('Expected length of assign targets to be 1')
|
| 801 |
+
target = stmt.targets[0]
|
| 802 |
+
|
| 803 |
+
if isinstance(target, ast.Tuple): # like `a, z = (1,2)` (ast.Assign only)
|
| 804 |
+
for i, elt in enumerate(target.elts):
|
| 805 |
+
ctx[elt.id] = value[i]
|
| 806 |
+
elif isinstance(target, ast.Name): # like `a = 1``
|
| 807 |
+
ctx[target.id] = value
|
| 808 |
+
elif isinstance(target, ast.Subscript) and isinstance(target.value, ast.Name): # `a[0] = 1`
|
| 809 |
+
ctx[target.value.id][self._eval_statement(target.slice, ctx=ctx)] = value
|
| 810 |
+
else:
|
| 811 |
+
raise ValueError('Unhandled target type for Assign.')
|
| 812 |
+
return value
|
| 813 |
+
|
| 814 |
+
# For assigning a var in a list comprehension.
|
| 815 |
+
# Like [name for node in node_list if (name := node.name)]
|
| 816 |
+
if isinstance(stmt, ast.NamedExpr):
|
| 817 |
+
value = self._eval_statement(stmt.value, ctx=ctx)
|
| 818 |
+
ctx[stmt.target.id] = value
|
| 819 |
+
return value
|
| 820 |
+
|
| 821 |
+
if isinstance(stmt, ast.Return):
|
| 822 |
+
if stmt.value is None:
|
| 823 |
+
value = None
|
| 824 |
+
else:
|
| 825 |
+
value = self._eval_statement(stmt.value, ctx=ctx)
|
| 826 |
+
# Mark that we have a return value, as we may be deeper in evaluation, like going through an
|
| 827 |
+
# if condition's body.
|
| 828 |
+
ctx['__returned__'] = value
|
| 829 |
+
return value
|
| 830 |
+
|
| 831 |
+
# Raise an error for break or continue, which should be caught and handled inside of loops,
|
| 832 |
+
# otherwise the error will be raised (which is desired when used outside of a loop).
|
| 833 |
+
if isinstance(stmt, ast.Break):
|
| 834 |
+
raise LoopBreak()
|
| 835 |
+
if isinstance(stmt, ast.Continue):
|
| 836 |
+
raise LoopContinue()
|
| 837 |
+
|
| 838 |
+
# Literally nothing.
|
| 839 |
+
if isinstance(stmt, ast.Pass):
|
| 840 |
+
return None
|
| 841 |
+
|
| 842 |
+
raise TypeError(stmt)
|