639 lines
24 KiB
Python
639 lines
24 KiB
Python
"""
|
|
LLS / ModelLoader
|
|
=================
|
|
功能域:模型加载与管理(对应功能分类总览第 1 节)
|
|
|
|
CATEGORY = "LLS/Model Loader"
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
from typing import Iterable
|
|
|
|
try:
|
|
import folder_paths
|
|
except Exception as exc:
|
|
folder_paths = None
|
|
_FOLDER_PATHS_ERR = exc
|
|
else:
|
|
_FOLDER_PATHS_ERR = None
|
|
|
|
try:
|
|
import comfy.sd as comfy_sd
|
|
except Exception as exc:
|
|
comfy_sd = None
|
|
_COMFY_SD_ERR = exc
|
|
else:
|
|
_COMFY_SD_ERR = None
|
|
|
|
try:
|
|
comfy_core_nodes = importlib.import_module("nodes")
|
|
except Exception as exc:
|
|
comfy_core_nodes = None
|
|
_CORE_NODES_ERR = exc
|
|
else:
|
|
_CORE_NODES_ERR = None
|
|
|
|
from ..model_profiles.registry import resolve_model_profile
|
|
from ..utils.model_info import (
|
|
MODEL_FAMILY_CHOICES,
|
|
canonicalize_family,
|
|
get_family_defaults,
|
|
info_to_json,
|
|
infer_family_from_name,
|
|
is_flux2_family,
|
|
is_flux_family,
|
|
is_sdxl_family,
|
|
tag_lls_object,
|
|
)
|
|
|
|
|
|
AUTO_PLACEHOLDER = "(auto)"
|
|
NO_MODELS_PLACEHOLDER = "(no models found)"
|
|
NO_VAE_PLACEHOLDER = "(no vae found)"
|
|
NO_TEXT_ENCODER_PLACEHOLDER = "(no text encoders found)"
|
|
LOAD_MODE_CHOICES = ["simple", "advanced"]
|
|
VAE_SOURCE_CHOICES = ["auto", "embedded", "external", "none"]
|
|
TEXT_ENCODER_SOURCE_CHOICES = ["auto", "embedded", "external", "manual"]
|
|
UNIVERSAL_VAE_SOURCE_CHOICES = ["auto", "embedded", "external"]
|
|
UNIVERSAL_TEXT_ENCODER_SOURCE_CHOICES = ["auto", "embedded", "external"]
|
|
|
|
_FLUX_CLIP_L_PATTERNS = ("clip_l.safetensors", "clip_l", "clip-l")
|
|
_FLUX_T5_PATTERNS = ("t5xxl_fp8_e4m3fn.safetensors", "t5xxl_fp16.safetensors", "t5xxl", "t5")
|
|
_SDXL_CLIP_L_PATTERNS = ("clip_l.safetensors", "clip_l", "clip-l")
|
|
_SDXL_CLIP_G_PATTERNS = ("clip_g.safetensors", "clip_g", "clip-g")
|
|
_FLUX_VAE_PATTERNS = ("ae.safetensors", "ae", "vae")
|
|
_FLUX2_QWEN_PATTERNS = ("qwen_3_8b.safetensors", "qwen_3_8b_fp8mixed.safetensors", "qwen_3_8b")
|
|
_FLUX2_VAE_PATTERNS = ("flux2-vae.safetensors", "flux2-vae", "flux2_vae")
|
|
|
|
|
|
def _build_capability_tags(model_name: str, family: str) -> dict[str, object]:
|
|
profile = resolve_model_profile(
|
|
model=None,
|
|
model_info={
|
|
"checkpoint_name": model_name,
|
|
"model_name": model_name,
|
|
"family": family,
|
|
},
|
|
checkpoint_name=model_name,
|
|
family=family,
|
|
)
|
|
return {
|
|
"profile_id": profile["profile_id"],
|
|
"backend_type": profile["backend_type"],
|
|
"sampler_strategy": profile["sampler_strategy"],
|
|
"loader_strategy": profile["loader_strategy"],
|
|
"model_role": profile["role"],
|
|
"supports_inpaint_native": profile["supports_inpaint_native"],
|
|
"supports_image_edit_native": profile["supports_image_edit_native"],
|
|
"preferred_edit_backend": profile["preferred_edit_backend"],
|
|
}
|
|
|
|
|
|
def _get_filename_list(category: str) -> list[str]:
|
|
if folder_paths is None:
|
|
return []
|
|
try:
|
|
return list(folder_paths.get_filename_list(category))
|
|
except Exception:
|
|
return []
|
|
|
|
|
|
def _with_placeholder(names: list[str], placeholder: str) -> list[str]:
|
|
cleaned = [name for name in names if name]
|
|
if not cleaned:
|
|
return [AUTO_PLACEHOLDER, placeholder]
|
|
return [AUTO_PLACEHOLDER] + cleaned
|
|
|
|
|
|
def _get_model_name_choices() -> list[str]:
|
|
checkpoints = _get_filename_list("checkpoints")
|
|
diffusion_models = [f"diffusion_models/{name}" for name in _get_filename_list("diffusion_models")]
|
|
names = list(dict.fromkeys(checkpoints + diffusion_models))
|
|
return names if names else [NO_MODELS_PLACEHOLDER]
|
|
|
|
|
|
def _get_vae_choices() -> list[str]:
|
|
if comfy_core_nodes is not None:
|
|
vae_loader = getattr(comfy_core_nodes, "VAELoader", None)
|
|
if vae_loader is not None and hasattr(vae_loader, "vae_list"):
|
|
try:
|
|
return _with_placeholder(list(vae_loader.vae_list(vae_loader)), NO_VAE_PLACEHOLDER)
|
|
except Exception:
|
|
pass
|
|
return _with_placeholder(_get_filename_list("vae"), NO_VAE_PLACEHOLDER)
|
|
|
|
|
|
def _get_text_encoder_choices() -> list[str]:
|
|
return _with_placeholder(_get_filename_list("text_encoders"), NO_TEXT_ENCODER_PLACEHOLDER)
|
|
|
|
|
|
def _normalize_choice(value: str | None, placeholder: str) -> str | None:
|
|
if value in (None, "", AUTO_PLACEHOLDER, placeholder):
|
|
return None
|
|
return value
|
|
|
|
|
|
def _get_full_path(category: str, name: str) -> str | None:
|
|
if folder_paths is None:
|
|
return None
|
|
try:
|
|
return folder_paths.get_full_path_or_raise(category, name)
|
|
except AttributeError:
|
|
return folder_paths.get_full_path(category, name)
|
|
except FileNotFoundError:
|
|
return None
|
|
|
|
|
|
def _resolve_model_path(model_name: str, family: str) -> tuple[str, str]:
|
|
if model_name == NO_MODELS_PLACEHOLDER:
|
|
raise RuntimeError(
|
|
"[LLS] No models were found. Put checkpoints in ComfyUI/models/checkpoints/ "
|
|
"or FLUX diffusion models in ComfyUI/models/diffusion_models/."
|
|
)
|
|
|
|
if model_name.startswith("diffusion_models/"):
|
|
short_name = model_name.split("/", 1)[1]
|
|
model_path = _get_full_path("diffusion_models", short_name)
|
|
if model_path is None:
|
|
raise RuntimeError(
|
|
f"[LLS] Missing FLUX main model '{short_name}'. "
|
|
"Place it in ComfyUI/models/diffusion_models/."
|
|
)
|
|
return ("diffusion_models", model_path)
|
|
|
|
checkpoint_path = _get_full_path("checkpoints", model_name)
|
|
if checkpoint_path is not None:
|
|
return ("checkpoints", checkpoint_path)
|
|
|
|
if is_flux_family(family):
|
|
diffusion_path = _get_full_path("diffusion_models", model_name)
|
|
if diffusion_path is not None:
|
|
return ("diffusion_models", diffusion_path)
|
|
raise RuntimeError(
|
|
f"[LLS] Missing FLUX main model '{model_name}'. "
|
|
"Place it in ComfyUI/models/diffusion_models/."
|
|
)
|
|
|
|
raise RuntimeError(
|
|
f"[LLS] Checkpoint '{model_name}' was not found. "
|
|
"Place it in ComfyUI/models/checkpoints/."
|
|
)
|
|
|
|
|
|
def _load_checkpoint_bundle(model_path: str):
|
|
try:
|
|
return comfy_sd.load_checkpoint_guess_config(
|
|
model_path,
|
|
output_vae=True,
|
|
output_clip=True,
|
|
embedding_directory=folder_paths.get_folder_paths("embeddings"),
|
|
)
|
|
except Exception as exc:
|
|
raise RuntimeError(f"[LLS] Failed to load checkpoint '{model_path}': {exc}") from exc
|
|
|
|
|
|
def _load_model(model_source: str, model_path: str):
|
|
if model_source == "checkpoints":
|
|
model, text_encoder, vae = _load_checkpoint_bundle(model_path)[:3]
|
|
return model, text_encoder, vae
|
|
|
|
try:
|
|
model = comfy_sd.load_diffusion_model(model_path)
|
|
except Exception as exc:
|
|
raise RuntimeError(f"[LLS] Failed to load diffusion model '{model_path}': {exc}") from exc
|
|
return model, None, None
|
|
|
|
|
|
def _load_external_vae(vae_name: str):
|
|
if comfy_core_nodes is None:
|
|
raise RuntimeError(
|
|
"[LLS] ComfyUI core VAELoader is unavailable, so external VAE loading cannot be used."
|
|
) from _CORE_NODES_ERR
|
|
|
|
vae_loader_cls = getattr(comfy_core_nodes, "VAELoader", None)
|
|
if vae_loader_cls is None:
|
|
raise RuntimeError("[LLS] ComfyUI core VAELoader class was not found.")
|
|
return vae_loader_cls().load_vae(vae_name)[0]
|
|
|
|
|
|
def _load_external_text_encoder(family: str, name1: str, name2: str | None):
|
|
if comfy_core_nodes is None:
|
|
raise RuntimeError(
|
|
"[LLS] ComfyUI core CLIP loaders are unavailable, so external text encoder loading cannot be used."
|
|
) from _CORE_NODES_ERR
|
|
|
|
if family == "SD1.5":
|
|
clip_loader_cls = getattr(comfy_core_nodes, "CLIPLoader", None)
|
|
if clip_loader_cls is None:
|
|
raise RuntimeError("[LLS] ComfyUI core CLIPLoader class was not found.")
|
|
return clip_loader_cls().load_clip(name1, type="stable_diffusion")[0]
|
|
|
|
if is_flux2_family(family):
|
|
clip_loader_cls = getattr(comfy_core_nodes, "CLIPLoader", None)
|
|
if clip_loader_cls is None:
|
|
raise RuntimeError("[LLS] ComfyUI core CLIPLoader class was not found.")
|
|
return clip_loader_cls().load_clip(name1, type="flux2")[0]
|
|
|
|
dual_loader_cls = getattr(comfy_core_nodes, "DualCLIPLoader", None)
|
|
if dual_loader_cls is None:
|
|
raise RuntimeError("[LLS] ComfyUI core DualCLIPLoader class was not found.")
|
|
if is_sdxl_family(family):
|
|
return dual_loader_cls().load_clip(name1, name2, type="sdxl")[0]
|
|
return dual_loader_cls().load_clip(name1, name2, type="flux")[0]
|
|
|
|
|
|
def _match_file(names: Iterable[str], patterns: Iterable[str]) -> str | None:
|
|
lowered = {name.lower(): name for name in names}
|
|
for pattern in patterns:
|
|
for candidate_lower, candidate in lowered.items():
|
|
if pattern.lower() == candidate_lower or pattern.lower() in candidate_lower:
|
|
return candidate
|
|
return None
|
|
|
|
|
|
def _resolve_external_text_encoder_names(
|
|
family: str,
|
|
name1: str | None,
|
|
name2: str | None,
|
|
) -> tuple[str | None, str | None]:
|
|
available = _get_filename_list("text_encoders")
|
|
|
|
if family == "SD1.5":
|
|
if name1:
|
|
return name1, None
|
|
return (_match_file(available, _SDXL_CLIP_L_PATTERNS) or available[0] if available else None, None)
|
|
|
|
if is_flux2_family(family):
|
|
resolved_1 = name1 or _match_file(available, _FLUX2_QWEN_PATTERNS)
|
|
return resolved_1, None
|
|
|
|
if is_sdxl_family(family):
|
|
resolved_1 = name1 or _match_file(available, _SDXL_CLIP_L_PATTERNS)
|
|
resolved_2 = name2 or _match_file(available, _SDXL_CLIP_G_PATTERNS)
|
|
return resolved_1, resolved_2
|
|
|
|
resolved_1 = name1 or _match_file(available, _FLUX_CLIP_L_PATTERNS)
|
|
resolved_2 = name2 or _match_file(available, _FLUX_T5_PATTERNS)
|
|
return resolved_1, resolved_2
|
|
|
|
|
|
def _resolve_external_vae_name(family: str, name: str | None) -> str | None:
|
|
if name:
|
|
return name
|
|
available = _get_filename_list("vae")
|
|
if is_flux2_family(family):
|
|
return _match_file(available, _FLUX2_VAE_PATTERNS)
|
|
if is_flux_family(family):
|
|
return _match_file(available, _FLUX_VAE_PATTERNS)
|
|
return available[0] if available else None
|
|
|
|
|
|
def _missing_text_encoder_error(family: str, missing_role: str) -> RuntimeError:
|
|
if family == "SD1.5":
|
|
return RuntimeError(
|
|
"[LLS] Missing SD1.5 text encoder. Provide external_text_encoder_1 or use a checkpoint with embedded CLIP."
|
|
)
|
|
if is_sdxl_family(family):
|
|
filename = "clip_l.safetensors" if missing_role == "clip_l" else "clip_g.safetensors"
|
|
return RuntimeError(
|
|
f"[LLS] Missing SDXL text encoder {filename}. "
|
|
"Place it in ComfyUI/models/text_encoders/."
|
|
)
|
|
if is_flux2_family(family):
|
|
return RuntimeError(
|
|
"[LLS] Missing Flux2/Klein text encoder qwen_3_8b.safetensors "
|
|
"or qwen_3_8b_fp8mixed.safetensors. Place one of them in ComfyUI/models/text_encoders/."
|
|
)
|
|
filename = "clip_l.safetensors" if missing_role == "clip_l" else "t5xxl_fp8_e4m3fn.safetensors"
|
|
fallback = (
|
|
"[LLS] Missing FLUX text encoder t5xxl_fp8_e4m3fn.safetensors or t5xxl_fp16.safetensors. "
|
|
"Place one of them in ComfyUI/models/text_encoders/."
|
|
if missing_role == "t5xxl"
|
|
else "[LLS] Missing FLUX text encoder clip_l.safetensors. "
|
|
"Place it in ComfyUI/models/text_encoders/."
|
|
)
|
|
return RuntimeError(fallback)
|
|
|
|
|
|
def _resolve_text_encoder(
|
|
family: str,
|
|
source: str,
|
|
embedded_text_encoder,
|
|
external_name_1: str | None,
|
|
external_name_2: str | None,
|
|
) -> tuple[object | None, str, str | None, str | None]:
|
|
if source == "manual":
|
|
return None, "manual", external_name_1, external_name_2
|
|
|
|
if source == "embedded":
|
|
if embedded_text_encoder is None:
|
|
raise RuntimeError(
|
|
f"[LLS] {family} was set to use embedded text encoders, but the selected model does not contain them."
|
|
)
|
|
return embedded_text_encoder, "embedded", None, None
|
|
|
|
resolved_1, resolved_2 = _resolve_external_text_encoder_names(family, external_name_1, external_name_2)
|
|
|
|
if source == "auto" and embedded_text_encoder is not None:
|
|
return embedded_text_encoder, "embedded", None, None
|
|
|
|
if family == "SD1.5":
|
|
if not resolved_1:
|
|
if source == "auto" and embedded_text_encoder is not None:
|
|
return embedded_text_encoder, "embedded", None, None
|
|
raise _missing_text_encoder_error(family, "clip")
|
|
return _load_external_text_encoder(family, resolved_1, None), "external", resolved_1, None
|
|
|
|
if is_flux2_family(family):
|
|
if not resolved_1:
|
|
raise _missing_text_encoder_error(family, "qwen_3_8b")
|
|
return _load_external_text_encoder(family, resolved_1, None), "external", resolved_1, None
|
|
|
|
required_role_1 = "clip_l"
|
|
required_role_2 = "clip_g" if is_sdxl_family(family) else "t5xxl"
|
|
if not resolved_1:
|
|
raise _missing_text_encoder_error(family, required_role_1)
|
|
if not resolved_2:
|
|
raise _missing_text_encoder_error(family, required_role_2)
|
|
return (
|
|
_load_external_text_encoder(family, resolved_1, resolved_2),
|
|
"external",
|
|
resolved_1,
|
|
resolved_2,
|
|
)
|
|
|
|
|
|
def _resolve_vae(
|
|
family: str,
|
|
source: str,
|
|
embedded_vae,
|
|
external_vae_name: str | None,
|
|
) -> tuple[object | None, str, str | None]:
|
|
if source == "none":
|
|
if is_flux_family(family):
|
|
raise RuntimeError(
|
|
"[LLS] Missing FLUX AE/VAE ae.safetensors. Place it in ComfyUI/models/vae/."
|
|
)
|
|
return None, "none", None
|
|
|
|
if source == "embedded":
|
|
if embedded_vae is None:
|
|
raise RuntimeError(
|
|
f"[LLS] {family} was set to use an embedded VAE, but the selected model does not contain one."
|
|
)
|
|
return embedded_vae, "embedded", None
|
|
|
|
resolved_vae_name = _resolve_external_vae_name(family, external_vae_name)
|
|
|
|
if source == "auto" and embedded_vae is not None:
|
|
return embedded_vae, "embedded", None
|
|
|
|
if resolved_vae_name:
|
|
return _load_external_vae(resolved_vae_name), "external", resolved_vae_name
|
|
|
|
if is_flux2_family(family):
|
|
raise RuntimeError(
|
|
"[LLS] Missing Flux2/Klein VAE flux2-vae.safetensors. Place it in ComfyUI/models/vae/."
|
|
)
|
|
|
|
if is_flux_family(family):
|
|
raise RuntimeError(
|
|
"[LLS] Missing FLUX AE/VAE ae.safetensors. Place it in ComfyUI/models/vae/."
|
|
)
|
|
return None, "none", None
|
|
|
|
|
|
def _resolve_loader_family(model_name: str, model_family: str) -> str:
|
|
if model_family == "Auto":
|
|
return infer_family_from_name(model_name, "SD1.5")
|
|
return canonicalize_family(model_family)
|
|
|
|
|
|
def _load_tagged_resources(
|
|
*,
|
|
model_name: str,
|
|
model_family: str,
|
|
load_mode: str,
|
|
vae_source: str,
|
|
text_encoder_source: str,
|
|
vae_name: str,
|
|
text_encoder_name_1: str,
|
|
text_encoder_name_2: str,
|
|
):
|
|
if folder_paths is None:
|
|
raise RuntimeError(
|
|
"[LLS] folder_paths is not available. Make sure this node runs inside ComfyUI."
|
|
) from _FOLDER_PATHS_ERR
|
|
if comfy_sd is None:
|
|
raise RuntimeError(
|
|
"[LLS] comfy.sd is not available. Make sure this node runs inside ComfyUI."
|
|
) from _COMFY_SD_ERR
|
|
|
|
family = _resolve_loader_family(model_name, model_family)
|
|
model_source, model_path = _resolve_model_path(model_name, family)
|
|
model, embedded_text_encoder, embedded_vae = _load_model(model_source, model_path)
|
|
if model is None:
|
|
raise RuntimeError(f"[LLS] The selected model '{model_name}' did not produce a valid diffusion model.")
|
|
|
|
external_vae_name = _normalize_choice(vae_name, NO_VAE_PLACEHOLDER)
|
|
external_text_encoder_1 = _normalize_choice(text_encoder_name_1, NO_TEXT_ENCODER_PLACEHOLDER)
|
|
external_text_encoder_2 = _normalize_choice(text_encoder_name_2, NO_TEXT_ENCODER_PLACEHOLDER)
|
|
|
|
text_encoder, resolved_text_encoder_source, resolved_te_1, resolved_te_2 = _resolve_text_encoder(
|
|
family=family,
|
|
source=text_encoder_source,
|
|
embedded_text_encoder=embedded_text_encoder,
|
|
external_name_1=external_text_encoder_1,
|
|
external_name_2=external_text_encoder_2,
|
|
)
|
|
vae, resolved_vae_source, resolved_vae_name = _resolve_vae(
|
|
family=family,
|
|
source=vae_source,
|
|
embedded_vae=embedded_vae,
|
|
external_vae_name=external_vae_name,
|
|
)
|
|
|
|
defaults = get_family_defaults(family)
|
|
capability_tags = _build_capability_tags(model_name, family)
|
|
resolved_text_encoder_name = resolved_te_1 or ("embedded" if text_encoder is not None else None)
|
|
resolved_text_encoder_name_1 = resolved_te_1 or ("embedded" if text_encoder is not None and not resolved_te_2 else None)
|
|
resolved_text_encoder_name_2 = resolved_te_2
|
|
if resolved_te_1 and resolved_te_2:
|
|
resolved_text_encoder_name = ", ".join([resolved_te_1, resolved_te_2])
|
|
resolved_vae_label = resolved_vae_name or ("embedded" if vae is not None else None)
|
|
|
|
tag_lls_object(
|
|
model,
|
|
family=family,
|
|
model_name=model_name,
|
|
checkpoint_name=model_name,
|
|
load_mode=load_mode,
|
|
**capability_tags,
|
|
)
|
|
tag_lls_object(
|
|
text_encoder,
|
|
family=family,
|
|
model_name=model_name,
|
|
checkpoint_name=model_name,
|
|
text_encoder_type=defaults["text_encoder_type"],
|
|
text_encoder_source=resolved_text_encoder_source,
|
|
text_encoder_name=resolved_text_encoder_name,
|
|
text_encoder_name_1=resolved_text_encoder_name_1,
|
|
text_encoder_name_2=resolved_text_encoder_name_2,
|
|
**capability_tags,
|
|
)
|
|
tag_lls_object(
|
|
vae,
|
|
family=family,
|
|
model_name=model_name,
|
|
checkpoint_name=model_name,
|
|
vae_name=resolved_vae_label,
|
|
vae_source=resolved_vae_source,
|
|
**capability_tags,
|
|
)
|
|
|
|
model_info = {
|
|
"model_family": family,
|
|
"checkpoint_name": model_name,
|
|
"model_name": model_name,
|
|
"load_mode": load_mode,
|
|
"model_source": model_source,
|
|
"profile_id": capability_tags["profile_id"],
|
|
"backend_type": capability_tags["backend_type"],
|
|
"sampler_strategy": capability_tags["sampler_strategy"],
|
|
"loader_strategy": capability_tags["loader_strategy"],
|
|
"model_role": capability_tags["model_role"],
|
|
"supports_inpaint_native": capability_tags["supports_inpaint_native"],
|
|
"supports_image_edit_native": capability_tags["supports_image_edit_native"],
|
|
"preferred_edit_backend": capability_tags["preferred_edit_backend"],
|
|
"text_encoder_type": defaults["text_encoder_type"],
|
|
"text_encoder_source": resolved_text_encoder_source,
|
|
"text_encoder_name": resolved_text_encoder_name or "",
|
|
"text_encoder_name_1": resolved_text_encoder_name_1 or "",
|
|
"text_encoder_name_2": resolved_text_encoder_name_2 or "",
|
|
"vae_name": resolved_vae_label or "",
|
|
"vae_source": resolved_vae_source,
|
|
}
|
|
return model, text_encoder, vae, model_info
|
|
|
|
|
|
class LLSSimpleCheckpointLoader:
|
|
"""
|
|
统一封装的基础模型加载器。
|
|
|
|
输出结构固定为 model / clip / vae / text_encoder。
|
|
- `clip` 保留旧工作流需要的原生 CLIP 端口类型和位置。
|
|
- `text_encoder` 是同一个原生 CLIP 对象的兼容别名。
|
|
- 轻量级模型信息写入原生对象 `_lls_*` 属性,不再额外输出额外上下文端口。
|
|
"""
|
|
|
|
CATEGORY = "LLS/Model Loader"
|
|
FUNCTION = "load_checkpoint"
|
|
RETURN_TYPES = ("MODEL", "CLIP", "VAE", "CLIP")
|
|
RETURN_NAMES = ("model", "clip", "vae", "text_encoder")
|
|
DESCRIPTION = (
|
|
"Load SD1.5, SDXL, SDXL Turbo, and FLUX families with family-aware resource dispatch. "
|
|
"Supports FLUX checkpoint-all-in-one mode and separated diffusion/text-encoder/VAE mode. "
|
|
"Preserves the legacy CLIP output for old workflows and writes lightweight metadata "
|
|
"into native MODEL/CLIP/VAE objects."
|
|
)
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"ckpt_name": (_get_model_name_choices(),),
|
|
"model_family": (MODEL_FAMILY_CHOICES, {"default": "Auto"}),
|
|
"load_mode": (LOAD_MODE_CHOICES, {"default": "simple"}),
|
|
"vae_source": (VAE_SOURCE_CHOICES, {"default": "auto"}),
|
|
"text_encoder_source": (TEXT_ENCODER_SOURCE_CHOICES, {"default": "auto"}),
|
|
"external_vae_name": (_get_vae_choices(), {"default": AUTO_PLACEHOLDER}),
|
|
"external_text_encoder_1": (_get_text_encoder_choices(), {"default": AUTO_PLACEHOLDER}),
|
|
"external_text_encoder_2": (_get_text_encoder_choices(), {"default": AUTO_PLACEHOLDER}),
|
|
},
|
|
}
|
|
|
|
def load_checkpoint(
|
|
self,
|
|
ckpt_name: str,
|
|
model_family: str,
|
|
load_mode: str,
|
|
vae_source: str,
|
|
text_encoder_source: str,
|
|
external_vae_name: str,
|
|
external_text_encoder_1: str,
|
|
external_text_encoder_2: str,
|
|
):
|
|
model, text_encoder, vae, _model_info = _load_tagged_resources(
|
|
model_name=ckpt_name,
|
|
model_family=model_family,
|
|
load_mode=load_mode,
|
|
vae_source=vae_source,
|
|
text_encoder_source=text_encoder_source,
|
|
vae_name=external_vae_name,
|
|
text_encoder_name_1=external_text_encoder_1,
|
|
text_encoder_name_2=external_text_encoder_2,
|
|
)
|
|
return (model, text_encoder, vae, text_encoder)
|
|
|
|
|
|
class LLSUniversalModelLoader:
|
|
CATEGORY = "LLS/Model Loader"
|
|
FUNCTION = "load"
|
|
RETURN_TYPES = ("MODEL", "CLIP", "VAE", "STRING")
|
|
RETURN_NAMES = ("model", "text_encoder", "vae", "model_info")
|
|
DESCRIPTION = (
|
|
"Unified model loader for SD1.5, SDXL, and FLUX families. "
|
|
"Internally resolves single or dual text encoders but always exposes one text_encoder output."
|
|
)
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"model_name": (_get_model_name_choices(),),
|
|
"model_family": (MODEL_FAMILY_CHOICES, {"default": "Auto"}),
|
|
"load_mode": (LOAD_MODE_CHOICES, {"default": "simple"}),
|
|
"vae_source": (UNIVERSAL_VAE_SOURCE_CHOICES, {"default": "auto"}),
|
|
"text_encoder_source": (UNIVERSAL_TEXT_ENCODER_SOURCE_CHOICES, {"default": "auto"}),
|
|
"text_encoder_1": (_get_text_encoder_choices(), {"default": AUTO_PLACEHOLDER}),
|
|
"text_encoder_2": (_get_text_encoder_choices(), {"default": AUTO_PLACEHOLDER}),
|
|
"vae_name": (_get_vae_choices(), {"default": AUTO_PLACEHOLDER}),
|
|
},
|
|
}
|
|
|
|
def load(
|
|
self,
|
|
model_name: str,
|
|
model_family: str,
|
|
load_mode: str,
|
|
vae_source: str,
|
|
text_encoder_source: str,
|
|
text_encoder_1: str,
|
|
text_encoder_2: str,
|
|
vae_name: str,
|
|
):
|
|
model, text_encoder, vae, model_info = _load_tagged_resources(
|
|
model_name=model_name,
|
|
model_family=model_family,
|
|
load_mode=load_mode,
|
|
vae_source=vae_source,
|
|
text_encoder_source=text_encoder_source,
|
|
vae_name=vae_name,
|
|
text_encoder_name_1=text_encoder_1,
|
|
text_encoder_name_2=text_encoder_2,
|
|
)
|
|
return model, text_encoder, vae, info_to_json(model_info)
|
|
|
|
|
|
NODE_CLASS_MAPPINGS: dict[str, type] = {
|
|
"LLSSimpleCheckpointLoader": LLSSimpleCheckpointLoader,
|
|
"LLSUniversalModelLoader": LLSUniversalModelLoader,
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS: dict[str, str] = {
|
|
"LLSSimpleCheckpointLoader": "LLS Simple Checkpoint Loader",
|
|
"LLSUniversalModelLoader": "LLS Universal Model Loader",
|
|
}
|