rebranded en updated prompt controler
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# ComfyUI-SmartResize
|
||||
# ComfyUI-Craftopia
|
||||
|
||||
Smart image resize nodes for ComfyUI — batch processing, LoRA prep & SeedVR support.
|
||||
Custom nodes for ComfyUI — image resizing, dataset prep & prompt automation for LoRA training workflows.
|
||||
|
||||
All nodes appear under the **Craftopia** category in the node menu.
|
||||
|
||||
@@ -8,6 +8,27 @@ All nodes appear under the **Craftopia** category in the node menu.
|
||||
|
||||
## Nodes
|
||||
|
||||
### 🎛️ Smart Prompt Controller
|
||||
Cycle through up to 4 prompt lists using a single incrementing counter. Counts lines automatically, selects the right prompt, and outputs which list is active — ideal for driving Switch nodes that control aspect ratio, latent size, or other per-category settings.
|
||||
|
||||
**Typical usecases:**
|
||||
- LoRA dataset generation with multiple pose categories (headshots, halfbody, fullbody, tall portrait), each with their own prompt list and aspect ratio
|
||||
- Any batch workflow where you need to rotate through different prompt sets and switch settings per set
|
||||
|
||||
| Input | Type | Description |
|
||||
|---|---|---|
|
||||
| counter | INT | Auto-incrementing counter (1-indexed) |
|
||||
| prompt_list_1 | STRING | First prompt list — one prompt per line (forceInput) |
|
||||
| prompt_list_2 | STRING | Second prompt list (optional, forceInput) |
|
||||
| prompt_list_3 | STRING | Third prompt list (optional, forceInput) |
|
||||
| prompt_list_4 | STRING | Fourth prompt list (optional, forceInput) |
|
||||
|
||||
**Outputs:** `prompt` (selected line as STRING), `list_index` (active list number as INT → feed to Switch nodes)
|
||||
|
||||
The node displays a status label showing the current position: `List 1 — 4/5 | total: 8`
|
||||
|
||||
---
|
||||
|
||||
### 📐 Smart Resize
|
||||
Resize any image (or batch) so the **longest side** equals `max_pixels`, with aspect ratio preserved.
|
||||
|
||||
@@ -72,20 +93,22 @@ Also useful for LoRA dataset prep — using mixed resolutions in your training s
|
||||
- `JWImageResizeByLongerSide` (comfyui-various) has no Lanczos in the official release
|
||||
- No existing node combines batch folder loading + longest-side resize + original filename preservation
|
||||
- No existing node outputs a ready-to-use `resolution` INT for SeedVR
|
||||
- No existing node cycles through multiple prompt lists with automatic list switching and counter
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
**ComfyUI Manager:** search for `ComfyUI-SmartResize` and install.
|
||||
**ComfyUI Manager:** search for `ComfyUI-Craftopia` and install.
|
||||
|
||||
**Manual:**
|
||||
```bash
|
||||
cd ComfyUI/custom_nodes
|
||||
git clone https://github.com/CraftopiaStudio/ComfyUI-SmartResize
|
||||
git clone https://github.com/CraftopiaStudio/ComfyUI-Craftopia
|
||||
```
|
||||
Restart ComfyUI. All nodes appear under **Craftopia** in the node menu:
|
||||
|
||||
- `Craftopia` → **Smart Prompt Controller 🎛️**
|
||||
- `Craftopia` → **Smart Resize 📐**
|
||||
- `Craftopia` → **Smart Batch Resize 📁**
|
||||
- `Craftopia` → **Smart Size Multiplier 📏**
|
||||
|
||||
+3
-1
@@ -1,7 +1,9 @@
|
||||
WEB_DIRECTORY = "./js"
|
||||
|
||||
from .smart_resize import NODE_CLASS_MAPPINGS as A, NODE_DISPLAY_NAME_MAPPINGS as A_NAMES
|
||||
from .smart_batch_resize import NODE_CLASS_MAPPINGS as B, NODE_DISPLAY_NAME_MAPPINGS as B_NAMES
|
||||
from .smart_size_multiplier import NODE_CLASS_MAPPINGS as C, NODE_DISPLAY_NAME_MAPPINGS as C_NAMES
|
||||
from .smart_batch_controller import NODE_CLASS_MAPPINGS as D, NODE_DISPLAY_NAME_MAPPINGS as D_NAMES
|
||||
from .smart_prompt_controller import NODE_CLASS_MAPPINGS as D, NODE_DISPLAY_NAME_MAPPINGS as D_NAMES
|
||||
|
||||
NODE_CLASS_MAPPINGS = {**A, **B, **C, **D}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {**A_NAMES, **B_NAMES, **C_NAMES, **D_NAMES}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "Craftopia.SmartBatchController",
|
||||
|
||||
async nodeCreated(node) {
|
||||
if (node.comfyClass !== "SmartBatchController") return;
|
||||
|
||||
// Create status widget — empty name so no label shows
|
||||
const widget = node.addWidget("text", "", "", () => {});
|
||||
widget.serializeValue = () => undefined;
|
||||
|
||||
// Listen for execution results
|
||||
const onExecuted = node.onExecuted;
|
||||
node.onExecuted = function (output) {
|
||||
// Store current size before update
|
||||
const w = node.size[0];
|
||||
const h = node.size[1];
|
||||
|
||||
onExecuted?.apply(this, arguments);
|
||||
|
||||
if (output?.text?.[0]) {
|
||||
widget.value = output.text[0];
|
||||
}
|
||||
|
||||
// Restore size so ComfyUI doesn't shrink the node
|
||||
node.size[0] = Math.max(w, node.size[0]);
|
||||
node.size[1] = Math.max(h, node.size[1]);
|
||||
node.setDirtyCanvas(true);
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "Craftopia.SmartPromptController",
|
||||
|
||||
async nodeCreated(node) {
|
||||
if (node.comfyClass !== "SmartPromptController") return;
|
||||
|
||||
// Status widget — no visible label name
|
||||
const widget = node.addWidget("text", "", "", () => {});
|
||||
widget.serializeValue = () => undefined;
|
||||
|
||||
const onExecuted = node.onExecuted;
|
||||
node.onExecuted = function (output) {
|
||||
const w = node.size[0];
|
||||
const h = node.size[1];
|
||||
|
||||
onExecuted?.apply(this, arguments);
|
||||
|
||||
if (output?.text?.[0]) {
|
||||
widget.value = output.text[0];
|
||||
}
|
||||
|
||||
// Preserve node size
|
||||
node.size[0] = Math.max(w, node.size[0]);
|
||||
node.size[1] = Math.max(h, node.size[1]);
|
||||
node.setDirtyCanvas(true);
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -1,86 +0,0 @@
|
||||
class SmartBatchController:
|
||||
"""
|
||||
Craftopia — Smart Batch Controller 🎛️
|
||||
Drives batch switching and prompt index from a single incrementing counter.
|
||||
Accepts up to 4 prompt lists as STRING inputs and counts lines automatically.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"counter": ("INT", {
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 99999,
|
||||
"step": 1,
|
||||
"display": "number",
|
||||
"control_after_generate": "increment",
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"batch_1": ("STRING", {"forceInput": True}),
|
||||
"batch_2": ("STRING", {"forceInput": True}),
|
||||
"batch_3": ("STRING", {"forceInput": True}),
|
||||
"batch_4": ("STRING", {"forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT", "INT", "INT")
|
||||
RETURN_NAMES = ("select", "prompt_index", "total_all")
|
||||
FUNCTION = "control"
|
||||
CATEGORY = "Craftopia"
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def _count_lines(self, text):
|
||||
"""Count non-empty lines in a prompt list string."""
|
||||
if not text:
|
||||
return 0
|
||||
return len([l for l in text.splitlines() if l.strip()])
|
||||
|
||||
def control(self, counter, batch_1=None, batch_2=None, batch_3=None, batch_4=None):
|
||||
raw = [batch_1, batch_2, batch_3, batch_4]
|
||||
|
||||
# Build active batch list: (original_index, line_count)
|
||||
active = []
|
||||
for i, text in enumerate(raw):
|
||||
count = self._count_lines(text)
|
||||
if count > 0:
|
||||
active.append((i, count))
|
||||
|
||||
total_all = sum(t for _, t in active)
|
||||
|
||||
if total_all == 0:
|
||||
return {"ui": {"text": ["no prompts"]}, "result": (0, 0, 0)}
|
||||
|
||||
# Wrap counter within total_all (1-based counter → 0-based position)
|
||||
position = (counter - 1) % total_all
|
||||
|
||||
# Find which batch and which prompt within that batch
|
||||
cumulative = 0
|
||||
batch_index = 0
|
||||
prompt_index = 0
|
||||
|
||||
for i, t in active:
|
||||
if position < cumulative + t:
|
||||
batch_index = i
|
||||
prompt_index = position - cumulative # 0-based within batch
|
||||
break
|
||||
cumulative += t
|
||||
|
||||
# UI label on node: "B2 — 3 / 7"
|
||||
batch_number = batch_index + 1
|
||||
batch_total = self._count_lines(raw[batch_index])
|
||||
prompt_display = prompt_index + 1 # 1-based for display
|
||||
label = f"B{batch_number} — {prompt_display} / {batch_total} | total: {total_all}"
|
||||
|
||||
return {"ui": {"text": [label]}, "result": (batch_index, prompt_index, total_all)}
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"SmartBatchController": SmartBatchController,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"SmartBatchController": "Smart Batch Controller 🎛️",
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
class SmartPromptController:
|
||||
"""
|
||||
Craftopia — Smart Prompt Controller 🎛️
|
||||
Cycles through up to 4 prompt lists using a single incrementing counter.
|
||||
Counts lines automatically and outputs the selected prompt + list number.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"counter": ("INT", {
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 99999,
|
||||
"step": 1,
|
||||
"display": "number",
|
||||
"control_after_generate": "increment",
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"prompt_list_1": ("STRING", {"forceInput": True}),
|
||||
"prompt_list_2": ("STRING", {"forceInput": True}),
|
||||
"prompt_list_3": ("STRING", {"forceInput": True}),
|
||||
"prompt_list_4": ("STRING", {"forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "INT")
|
||||
RETURN_NAMES = ("prompt", "list_index")
|
||||
FUNCTION = "control"
|
||||
CATEGORY = "Craftopia"
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def _get_lines(self, text):
|
||||
"""Return list of non-empty lines from a text block."""
|
||||
if not text:
|
||||
return []
|
||||
if isinstance(text, list):
|
||||
text = text[0] if text else ""
|
||||
return [l for l in str(text).splitlines() if l.strip()]
|
||||
|
||||
def control(self, counter, prompt_list_1="", prompt_list_2="",
|
||||
prompt_list_3="", prompt_list_4=""):
|
||||
raw = [prompt_list_1, prompt_list_2, prompt_list_3, prompt_list_4]
|
||||
|
||||
# Collect active lists: (list_index, [lines])
|
||||
active = []
|
||||
for i, text in enumerate(raw):
|
||||
lines = self._get_lines(text)
|
||||
if lines:
|
||||
active.append((i, lines))
|
||||
|
||||
total_all = sum(len(lines) for _, lines in active)
|
||||
|
||||
if total_all == 0:
|
||||
return {"ui": {"text": ["no prompts connected"]},
|
||||
"result": ("", 0)}
|
||||
|
||||
# Map counter position to list + line
|
||||
position = (counter - 1) % total_all
|
||||
|
||||
cumulative = 0
|
||||
selected_list_index = 0
|
||||
selected_line_index = 0
|
||||
selected_prompt = ""
|
||||
|
||||
for list_idx, lines in active:
|
||||
if position < cumulative + len(lines):
|
||||
selected_list_index = list_idx
|
||||
selected_line_index = position - cumulative
|
||||
selected_prompt = lines[selected_line_index]
|
||||
break
|
||||
cumulative += len(lines)
|
||||
|
||||
# Label: "List 1 — 4/5 | total: 8"
|
||||
list_number = selected_list_index + 1
|
||||
list_total = len(self._get_lines(raw[selected_list_index]))
|
||||
prompt_display = selected_line_index + 1
|
||||
label = f"List {list_number} \u2014 {prompt_display}/{list_total} | total: {total_all}"
|
||||
|
||||
return {
|
||||
"ui": {"text": [label]},
|
||||
"result": (
|
||||
selected_prompt,
|
||||
list_number,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"SmartPromptController": SmartPromptController,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"SmartPromptController": "Smart Prompt Controller \ud83c\udf9b\ufe0f",
|
||||
}
|
||||
Reference in New Issue
Block a user