Add files via upload
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
# ComfyUI-Pythza
|
||||
|
||||
A small collection of utility nodes for [ComfyUI](https://github.com/comfyanonymous/ComfyUI).
|
||||
A small utility node for [ComfyUI](https://github.com/comfyanonymous/ComfyUI).
|
||||
|
||||
## Nodes
|
||||
## 🧭 Node Navigator
|
||||
|
||||
### 🧭 Node Navigator
|
||||
Pan the canvas to any node by its ID. Useful for large workflows where you need to jump between distant nodes quickly.
|
||||
|
||||
- Enter a **node ID** and click **"Go to node"**
|
||||
@@ -13,36 +12,21 @@ Pan the canvas to any node by its ID. Useful for large workflows where you need
|
||||
|
||||
> **Tip:** You can find a node's ID by right-clicking it in ComfyUI — it's shown at the top of the context menu.
|
||||
|
||||
### 🎨 LoRA Formatter
|
||||
Parses a LoRA tag string (e.g. `<lora:folder/MyModel:0.8>`) and displays a clean, readable preview.
|
||||
|
||||
- Strips common prefixes like `SDXL_`, `PONY_`, etc.
|
||||
- Shows up to 3 LoRA blocks with labels (Custom, Slider, Detail)
|
||||
- Live preview widget on the node itself
|
||||
|
||||
### 🔢 Count True Booleans
|
||||
Counts how many of the connected boolean inputs are `True`. Accepts up to 20 optional boolean inputs.
|
||||
|
||||
Handy for conditional logic — e.g. "run this branch only if at least 3 options are enabled."
|
||||
|
||||
## Installation
|
||||
|
||||
### Manual
|
||||
```bash
|
||||
cd ComfyUI/custom_nodes
|
||||
git clone https://github.com/YOUR_USERNAME/ComfyUI-Pythza.git
|
||||
git clone https://github.com/Pythza/ComfyUI-Pythza.git
|
||||
```
|
||||
Restart ComfyUI.
|
||||
|
||||
### ComfyUI Manager
|
||||
Search for **Pythza** in the ComfyUI Manager install menu (once registered).
|
||||
|
||||
## Structure
|
||||
```
|
||||
ComfyUI-Pythza/
|
||||
├── __init__.py # Node definitions (Python)
|
||||
├── __init__.py # Node definition (Python)
|
||||
├── js/
|
||||
│ └── pythza.js # Frontend (canvas navigation + LoRA preview widget)
|
||||
│ └── pythza.js # Frontend (canvas navigation)
|
||||
└── README.md
|
||||
```
|
||||
|
||||
|
||||
+3
-110
@@ -2,114 +2,11 @@
|
||||
ComfyUI-Pythza — Utility nodes for ComfyUI
|
||||
https://github.com/Pythza/ComfyUI-Pythza
|
||||
"""
|
||||
import re
|
||||
|
||||
WEB_DIRECTORY = "./js"
|
||||
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
STRIP_PREFIXES = ["SDXL_PONY_", "PONY_SDXL_", "SDXL_", "PONY_", "_SDXL_", "_PONY_"]
|
||||
BLOCK_LABELS = ["Custom", "Slider", "Detail"]
|
||||
|
||||
|
||||
def strip_prefix(name):
|
||||
for prefix in STRIP_PREFIXES:
|
||||
if name.upper().startswith(prefix.upper()):
|
||||
return name[len(prefix):]
|
||||
return name
|
||||
|
||||
|
||||
def parse_lora(entry, label):
|
||||
entry = entry.strip()
|
||||
if not entry:
|
||||
return None
|
||||
match = re.match(r'<lora:([^>]+)>', entry)
|
||||
if not match:
|
||||
return None
|
||||
inner = match.group(1)
|
||||
parts = inner.rsplit(":", 1)
|
||||
if len(parts) != 2:
|
||||
return None
|
||||
path, strength = parts[0].strip(), parts[1].strip()
|
||||
filename = path.replace("\\", "/").split("/")[-1]
|
||||
filename = re.sub(r'\.safetensors$', '', filename, flags=re.IGNORECASE)
|
||||
clean = strip_prefix(filename)
|
||||
return f"{label}: {clean}\nStrength: {strength}"
|
||||
|
||||
|
||||
def format_lora_string(lora_string):
|
||||
top_level = []
|
||||
depth = 0
|
||||
current = []
|
||||
for ch in lora_string:
|
||||
if ch == "<":
|
||||
depth += 1
|
||||
elif ch == ">":
|
||||
depth -= 1
|
||||
if ch == "," and depth == 0:
|
||||
top_level.append("".join(current).strip())
|
||||
current = []
|
||||
else:
|
||||
current.append(ch)
|
||||
top_level.append("".join(current).strip())
|
||||
|
||||
lines = []
|
||||
for i, block in enumerate(top_level[:3]):
|
||||
label = BLOCK_LABELS[i] if i < len(BLOCK_LABELS) else f"Block{i+1}"
|
||||
parsed = parse_lora(block, label)
|
||||
if parsed:
|
||||
if lines:
|
||||
lines.append("")
|
||||
lines.append(parsed)
|
||||
|
||||
return "\n".join(lines) if lines else "(no LoRAs)"
|
||||
|
||||
|
||||
# ── Node 1: LoRA Formatter ───────────────────────────────────────────────────
|
||||
|
||||
class PythzaLoraFormatter:
|
||||
"""Parses a LoRA tag string and displays a clean formatted preview."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"lora_string": ("STRING", {"forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("formatted",)
|
||||
FUNCTION = "format"
|
||||
CATEGORY = "Pythza"
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def format(self, lora_string):
|
||||
result = format_lora_string(lora_string)
|
||||
return {"ui": {"text": [result]}, "result": (result,)}
|
||||
|
||||
|
||||
# ── Node 2: Count True Booleans ──────────────────────────────────────────────
|
||||
|
||||
class PythzaCountTrue:
|
||||
"""Counts how many of the connected boolean inputs are True."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
optional = {f"bool_{i}": ("BOOLEAN", {"default": False}) for i in range(1, 21)}
|
||||
return {"required": {}, "optional": optional}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
RETURN_NAMES = ("true_count",)
|
||||
FUNCTION = "count"
|
||||
CATEGORY = "Pythza"
|
||||
|
||||
def count(self, **kwargs):
|
||||
return (sum(1 for v in kwargs.values() if v is True),)
|
||||
|
||||
|
||||
# ── Node 3: Node Navigator ──────────────────────────────────────────────────
|
||||
# ── Node Navigator ───────────────────────────────────────────────────────────
|
||||
|
||||
class PythzaNodeNavigator:
|
||||
"""UI-only node with a button that pans the canvas to a target node by ID."""
|
||||
@@ -134,15 +31,11 @@ class PythzaNodeNavigator:
|
||||
# ── Registration ─────────────────────────────────────────────────────────────
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"PythzaLoraFormatter": PythzaLoraFormatter,
|
||||
"PythzaCountTrue": PythzaCountTrue,
|
||||
"PythzaNodeNavigator": PythzaNodeNavigator,
|
||||
"PythzaNodeNavigator": PythzaNodeNavigator,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"PythzaLoraFormatter": "LoRA Formatter (Pythza)",
|
||||
"PythzaCountTrue": "Count True Booleans (Pythza)",
|
||||
"PythzaNodeNavigator": "Node Navigator (Pythza)",
|
||||
"PythzaNodeNavigator": "Node Navigator (Pythza)",
|
||||
}
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY']
|
||||
|
||||
@@ -35,36 +35,3 @@ app.registerExtension({
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// ── LoRA Formatter ──────────────────────────────────────────────────────────
|
||||
app.registerExtension({
|
||||
name: "Pythza.LoraFormatter",
|
||||
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== "PythzaLoraFormatter") return;
|
||||
|
||||
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
if (origOnNodeCreated) origOnNodeCreated.apply(this, arguments);
|
||||
|
||||
const textWidget = this.addWidget("text", "preview", "", () => {}, {
|
||||
multiline: true,
|
||||
readonly: true,
|
||||
});
|
||||
textWidget.inputEl?.setAttribute("readonly", true);
|
||||
this._previewWidget = textWidget;
|
||||
this.setSize([300, 200]);
|
||||
};
|
||||
|
||||
const origOnExecuted = nodeType.prototype.onExecuted;
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
if (origOnExecuted) origOnExecuted.apply(this, arguments);
|
||||
if (message?.text?.[0] !== undefined && this._previewWidget) {
|
||||
this._previewWidget.value = message.text[0];
|
||||
const lines = message.text[0].split("\n").length;
|
||||
this.setSize([300, Math.max(120, 40 + lines * 20)]);
|
||||
this.setDirtyCanvas(true);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user