Files
IAMCCS-IAMCCS-nodes/iamccs_ltx2_lora_stack_segmented6.py

289 lines
9.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# iamccs_ltx2_lora_stack_segmented6.py
# ===============================================================
# Segmented LoRA stacks for workflows with 3 segments × 2 stages.
# Outputs either 6 LoRA stacks or 6 MODELs (MODEL only, no CLIP).
# ===============================================================
import logging
from typing import Any, Dict, Optional
import comfy.sd
import comfy.utils
import folder_paths
from .iamccs_ltx2_lora_stack import SuppressLTX2MissingKeysFilter, standardize_ltx2_lora_keys
def _load_lora_state_dict(name: str, cache: Dict[str, Any]) -> Optional[dict]:
if not name or name == "no":
return None
if name in cache:
return cache[name]
path = folder_paths.get_full_path_or_raise("loras", name)
sd = comfy.utils.load_torch_file(path, safe_load=True)
sd = standardize_ltx2_lora_keys(sd)
cache[name] = sd
return sd
def _append_lora(stack: list, name: str, strength: float, cache: Dict[str, Any]) -> None:
if not name or name == "no":
return
s = float(strength)
if s == 0.0:
return
sd = _load_lora_state_dict(name, cache)
if not sd:
return
stack.append({"name": name, "strength": s, "state_dict": sd})
def _build_segment_stage_stack(
*,
fixed_lora: str,
fixed_strength: float,
var_lora1: str,
var1_strength: float,
var_lora2: str,
var2_strength: float,
cache: Dict[str, Any],
) -> list:
stack: list = []
_append_lora(stack, fixed_lora, fixed_strength, cache)
_append_lora(stack, var_lora1, var1_strength, cache)
_append_lora(stack, var_lora2, var2_strength, cache)
return stack
def _apply_lora_stack_to_model(model, lora_stack: list):
if not lora_stack:
return model
model_out = model
for entry in lora_stack:
sd = entry["state_dict"]
strength = float(entry["strength"])
model_out, _ = comfy.sd.load_lora_for_models(model_out, None, sd, strength, 0)
return model_out
class IAMCCS_LTX2_LoRAStackSegmented6:
"""Builds 6 LORA stacks: 3 segments × 2 stages (MODEL-only workflows)."""
@classmethod
def INPUT_TYPES(cls):
lora_list = folder_paths.get_filename_list("loras") + ["no"]
required: Dict[str, Any] = {
"fixed_lora": (lora_list, {"default": "no"}),
}
# 3 segments (0..2), each has 2 stages and 2 variable loras
for seg in range(3):
required[f"seg{seg}_var_lora1"] = (lora_list, {"default": "no"})
required[f"seg{seg}_var_lora2"] = (lora_list, {"default": "no"})
# fixed strength per stage
required[f"seg{seg}_fixed_strength_stage1"] = (
"FLOAT",
{"default": 0.0, "min": -5.0, "max": 5.0, "step": 0.01},
)
required[f"seg{seg}_fixed_strength_stage2"] = (
"FLOAT",
{"default": 0.0, "min": -5.0, "max": 5.0, "step": 0.01},
)
# var strengths per stage
for i in (1, 2):
required[f"seg{seg}_var{i}_strength_stage1"] = (
"FLOAT",
{"default": 0.0, "min": -5.0, "max": 5.0, "step": 0.01},
)
required[f"seg{seg}_var{i}_strength_stage2"] = (
"FLOAT",
{"default": 0.0, "min": -5.0, "max": 5.0, "step": 0.01},
)
return {"required": required}
RETURN_TYPES = ("LORA", "LORA", "LORA", "LORA", "LORA", "LORA")
RETURN_NAMES = (
"seg0_stage1_lora",
"seg0_stage2_lora",
"seg1_stage1_lora",
"seg1_stage2_lora",
"seg2_stage1_lora",
"seg2_stage2_lora",
)
FUNCTION = "build"
CATEGORY = "IAMCCS/LoRA"
def build(self, fixed_lora: str, **kwargs):
cache: Dict[str, Any] = {}
out: list[list] = []
for seg in range(3):
var1 = str(kwargs.get(f"seg{seg}_var_lora1") or "no")
var2 = str(kwargs.get(f"seg{seg}_var_lora2") or "no")
fixed_s1 = kwargs.get(f"seg{seg}_fixed_strength_stage1", 0.0)
fixed_s2 = kwargs.get(f"seg{seg}_fixed_strength_stage2", 0.0)
v1s1 = kwargs.get(f"seg{seg}_var1_strength_stage1", 0.0)
v1s2 = kwargs.get(f"seg{seg}_var1_strength_stage2", 0.0)
v2s1 = kwargs.get(f"seg{seg}_var2_strength_stage1", 0.0)
v2s2 = kwargs.get(f"seg{seg}_var2_strength_stage2", 0.0)
out.append(
_build_segment_stage_stack(
fixed_lora=fixed_lora,
fixed_strength=fixed_s1,
var_lora1=var1,
var1_strength=v1s1,
var_lora2=var2,
var2_strength=v2s1,
cache=cache,
)
)
out.append(
_build_segment_stage_stack(
fixed_lora=fixed_lora,
fixed_strength=fixed_s2,
var_lora1=var1,
var1_strength=v1s2,
var_lora2=var2,
var2_strength=v2s2,
cache=cache,
)
)
# Logging summary (compact)
total = sum(len(s) for s in out)
if total == 0:
logging.warning("[IAMCCS_LTX2_LoRAStackSegmented6] ⚠ No LoRA selected")
else:
logging.info(f"[IAMCCS_LTX2_LoRAStackSegmented6] ✅ Built 6 stacks ({total} active entries)")
return tuple(out)
class IAMCCS_LTX2_ModelWithLoRA_Segmented6:
"""Applies 6 stacks (3 segments × 2 stages) to a base MODEL and outputs 6 MODELs."""
@classmethod
def INPUT_TYPES(cls):
# Mirror config of stack node, but include base model
lora_list = folder_paths.get_filename_list("loras") + ["no"]
required: Dict[str, Any] = {
"model": ("MODEL",),
"fixed_lora": (lora_list, {"default": "no"}),
}
for seg in range(3):
required[f"seg{seg}_var_lora1"] = (lora_list, {"default": "no"})
required[f"seg{seg}_var_lora2"] = (lora_list, {"default": "no"})
required[f"seg{seg}_fixed_strength_stage1"] = (
"FLOAT",
{"default": 0.0, "min": -5.0, "max": 5.0, "step": 0.01},
)
required[f"seg{seg}_fixed_strength_stage2"] = (
"FLOAT",
{"default": 0.0, "min": -5.0, "max": 5.0, "step": 0.01},
)
for i in (1, 2):
required[f"seg{seg}_var{i}_strength_stage1"] = (
"FLOAT",
{"default": 0.0, "min": -5.0, "max": 5.0, "step": 0.01},
)
required[f"seg{seg}_var{i}_strength_stage2"] = (
"FLOAT",
{"default": 0.0, "min": -5.0, "max": 5.0, "step": 0.01},
)
return {"required": required}
RETURN_TYPES = ("MODEL", "MODEL", "MODEL", "MODEL", "MODEL", "MODEL")
RETURN_NAMES = (
"seg0_stage1_model",
"seg0_stage2_model",
"seg1_stage1_model",
"seg1_stage2_model",
"seg2_stage1_model",
"seg2_stage2_model",
)
FUNCTION = "apply_segmented"
CATEGORY = "IAMCCS/LoRA"
def apply_segmented(self, model, fixed_lora: str, **kwargs):
cache: Dict[str, Any] = {}
# Build all 6 stacks
stacks: list[list] = []
for seg in range(3):
var1 = str(kwargs.get(f"seg{seg}_var_lora1") or "no")
var2 = str(kwargs.get(f"seg{seg}_var_lora2") or "no")
fixed_s1 = kwargs.get(f"seg{seg}_fixed_strength_stage1", 0.0)
fixed_s2 = kwargs.get(f"seg{seg}_fixed_strength_stage2", 0.0)
v1s1 = kwargs.get(f"seg{seg}_var1_strength_stage1", 0.0)
v1s2 = kwargs.get(f"seg{seg}_var1_strength_stage2", 0.0)
v2s1 = kwargs.get(f"seg{seg}_var2_strength_stage1", 0.0)
v2s2 = kwargs.get(f"seg{seg}_var2_strength_stage2", 0.0)
stacks.append(
_build_segment_stage_stack(
fixed_lora=fixed_lora,
fixed_strength=fixed_s1,
var_lora1=var1,
var1_strength=v1s1,
var_lora2=var2,
var2_strength=v2s1,
cache=cache,
)
)
stacks.append(
_build_segment_stage_stack(
fixed_lora=fixed_lora,
fixed_strength=fixed_s2,
var_lora1=var1,
var1_strength=v1s2,
var_lora2=var2,
var2_strength=v2s2,
cache=cache,
)
)
# Apply with log suppression
logger = logging.getLogger()
missing_keys_filter = SuppressLTX2MissingKeysFilter()
logger.addFilter(missing_keys_filter)
try:
models = []
for idx, stack in enumerate(stacks):
out_model = _apply_lora_stack_to_model(model, stack)
models.append(out_model)
if stack:
names = ", ".join(f"{e['name']}({e['strength']})" for e in stack)
logging.info(f"[IAMCCS_LTX2_ModelWithLoRA_Segmented6] segStage[{idx}] -> {names}")
return tuple(models)
finally:
logger.removeFilter(missing_keys_filter)
NODE_CLASS_MAPPINGS = {
"IAMCCS_LTX2_LoRAStackSegmented6": IAMCCS_LTX2_LoRAStackSegmented6,
"IAMCCS_LTX2_ModelWithLoRA_Segmented6": IAMCCS_LTX2_ModelWithLoRA_Segmented6,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"IAMCCS_LTX2_LoRAStackSegmented6": "LoRA Stack (LTX-2, segmented: 3 seg × 2 stages)",
"IAMCCS_LTX2_ModelWithLoRA_Segmented6": "Apply LoRA to MODEL (LTX-2, segmented: 3 seg × 2 stages)",
}