Add model-filtered preset picker and per-model latent channel count
- EmptyLatentAspectPreset gains a "model" widget; web/aspect_ratio_filter.js filters the "preset" combo to that model client-side, parsed from each preset's label so presets.py stays the single source of truth. - Fix latent channel count: SD1.5/SDXL use 4-channel latents, but Flux/HiDream/Krea/Qwen-Image use 16-channel latents (same family as ComfyUI's EmptySD3LatentImage) — was previously hardcoded to 4 for all models, breaking/corrupting generations on the newer models. - Drop Ideogram and ERNIE from presets.py: both are hosted API models with no local LATENT/diffusion pipeline in ComfyUI, so listing them implied false compatibility.
This commit is contained in:
@@ -3,6 +3,7 @@ node.zip
|
||||
# Claude Code / AI assistant local state
|
||||
.claude/
|
||||
.serena/
|
||||
.tokensave/
|
||||
CLAUDE.md
|
||||
CLAUDE.local.md
|
||||
|
||||
|
||||
@@ -6,3 +6,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME = "latent"
|
||||
|
||||
WEB_DIRECTORY = "web"
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "WEB_DIRECTORY"]
|
||||
@@ -13,11 +13,31 @@ class EmptyLatentAspectPreset:
|
||||
f"{w}x{h} - {lbl} - {model}": (w, h)
|
||||
for model, lbl, w, h in PRESETS
|
||||
}
|
||||
# Unique models in first-appearance order; the "model" widget below is purely a
|
||||
# client-side filter (see web/aspect_ratio_filter.js) for the "preset" dropdown,
|
||||
# which already encodes the model in its label — parsed there, not duplicated.
|
||||
MODELS = list(dict.fromkeys(model for model, _, _, _ in PRESETS))
|
||||
|
||||
# Latent channel count per model family. SD1.5/SDXL use the legacy 4-channel VAE;
|
||||
# Flux/HiDream/Qwen-Image/Krea use a 16-channel VAE (same family as ComfyUI's own
|
||||
# EmptySD3LatentImage) — feeding a 4-channel latent into those samplers fails or
|
||||
# silently produces garbage.
|
||||
LATENT_CHANNELS = {
|
||||
"SD15": 4,
|
||||
"SDXL": 4,
|
||||
"HiDream": 16,
|
||||
"Flux.1": 16,
|
||||
"Flux.2": 16,
|
||||
"Krea": 16,
|
||||
"Qwen-Image": 16,
|
||||
}
|
||||
DEFAULT_LATENT_CHANNELS = 4
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"model": (cls.MODELS,),
|
||||
"preset": (list(cls.PRESET_MAP.keys()),),
|
||||
"batch_size": ("INT", {"default": 1, "min": 1})
|
||||
}
|
||||
@@ -28,7 +48,7 @@ class EmptyLatentAspectPreset:
|
||||
FUNCTION = "generate"
|
||||
CATEGORY = "latent" # moved into ComfyUI's built-in "latent" category
|
||||
|
||||
def generate(self, preset: str, batch_size: int):
|
||||
def generate(self, model: str, preset: str, batch_size: int):
|
||||
if preset not in self.PRESET_MAP:
|
||||
raise ValueError(f"Unknown preset: {preset}")
|
||||
w, h = self.PRESET_MAP[preset]
|
||||
@@ -36,7 +56,8 @@ class EmptyLatentAspectPreset:
|
||||
_validate_dim(w)
|
||||
_validate_dim(h)
|
||||
|
||||
latent = torch.zeros([batch_size, 4, h // 8, w // 8], dtype=torch.float32)
|
||||
channels = self.LATENT_CHANNELS.get(model, self.DEFAULT_LATENT_CHANNELS)
|
||||
latent = torch.zeros([batch_size, channels, h // 8, w // 8], dtype=torch.float32)
|
||||
return ({"samples": latent}, w, h)
|
||||
|
||||
|
||||
|
||||
+2
-17
@@ -67,21 +67,6 @@ PRESETS = [
|
||||
("Qwen-Image", "3:4 Portrait", 1136, 1472),
|
||||
("Qwen-Image", "9:16 Portrait", 928, 1664),
|
||||
|
||||
# --- Ideogram 4.0 (broad native AR support incl. panoramic) ---
|
||||
("Ideogram", "1:1 Square", 1024, 1024),
|
||||
("Ideogram", "3:2 Landscape", 1216, 832),
|
||||
("Ideogram", "4:3 Landscape", 1152, 896),
|
||||
("Ideogram", "16:9 Landscape", 1344, 768),
|
||||
("Ideogram", "3:1 Landscape", 1728, 576),
|
||||
("Ideogram", "2:3 Portrait", 832, 1216),
|
||||
("Ideogram", "3:4 Portrait", 896, 1152),
|
||||
("Ideogram", "9:16 Portrait", 768, 1344),
|
||||
("Ideogram", "1:3 Portrait", 576, 1728),
|
||||
|
||||
# --- ERNIE (Baidu ERNIE-ViLG / iRAG, native 1024) ---
|
||||
("Ernie", "1:1 Square", 1024, 1024),
|
||||
("Ernie", "4:3 Landscape", 1152, 896),
|
||||
("Ernie", "16:9 Landscape", 1344, 768),
|
||||
("Ernie", "3:4 Portrait", 896, 1152),
|
||||
("Ernie", "9:16 Portrait", 768, 1344),
|
||||
# Ideogram 4.0 and ERNIE are hosted API models (no local LATENT/diffusion sampling
|
||||
# in ComfyUI) — deliberately excluded so this node never implies false compatibility.
|
||||
]
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
|
||||
// Filters the "preset" combo of the "CAS Empty Latent Aspect Ratio Preset" node down to
|
||||
// entries matching the selected "model" widget. The model is parsed from each preset's
|
||||
// label ("WxH - label - model"), so presets.py stays the single source of truth.
|
||||
app.registerExtension({
|
||||
name: "ComfyUI.AspectRatioPresets.ModelFilter",
|
||||
beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== "CAS Empty Latent Aspect Ratio Preset") return;
|
||||
|
||||
const allPresets = nodeData.input.required.preset[0];
|
||||
const presetsByModel = (model) =>
|
||||
allPresets.filter((p) => p.endsWith(` - ${model}`));
|
||||
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
onNodeCreated?.apply(this, arguments);
|
||||
|
||||
const modelWidget = this.widgets.find((w) => w.name === "model");
|
||||
const presetWidget = this.widgets.find((w) => w.name === "preset");
|
||||
if (!modelWidget || !presetWidget) return;
|
||||
|
||||
const applyFilter = () => {
|
||||
const filtered = presetsByModel(modelWidget.value);
|
||||
presetWidget.options.values = filtered;
|
||||
if (!filtered.includes(presetWidget.value)) {
|
||||
presetWidget.value = filtered[0];
|
||||
}
|
||||
};
|
||||
|
||||
const origCallback = modelWidget.callback;
|
||||
modelWidget.callback = (...args) => {
|
||||
origCallback?.apply(modelWidget, args);
|
||||
applyFilter();
|
||||
};
|
||||
|
||||
applyFilter();
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user