feat(ml): LoRAs con prefijo de arquitectura (SD15_/SDXL_/FLUX_) + refs actualizadas

Mueve el indicador de arquitectura del SUFIJO al PREFIJO del nombre de cada
LoRA para que el dropdown del LoraLoader muestre de inmediato que LoRA casa con
que checkpoint (evita el shape mismatch SD1.5 vs SDXL que crashea ComfyUI).

- 20 LoRAs renombradas en disco (15 SD15/SDXL en /mnt/2tb, 5 FLUX en ~/ComfyUI),
  mapa de reversion en ~/ComfyUI/models/loras/_rename_map.json.
- Refs actualizadas en builders gamedev-2d, style presets, pipelines, tests y
  docs/capabilities. Defaults hardcodeados (pixel-art, lcm-lora, etc.) apuntan a
  los nombres con prefijo.
- Ejemplos genericos en docstrings normalizados a la convencion de prefijo.
- comfyui_replicate_civitai_oneshot::_norm ignora el token de arquitectura al
  comparar, robusto al reordenado (sufijo civitai vs prefijo instalado).

Refs a repos HuggingFace (nerijs/pixel-art-xl) y checkpoints (juggernaut_xl_v11)
preservados. Verificado: dropdown LoraLoader con prefijos + generacion real
pixel-art OK + tests comfyui verdes (481 ml + 26 pipelines).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-27 16:33:03 +02:00
parent d3d846f748
commit 10dbc510b7
99 changed files with 230 additions and 217 deletions
@@ -41,7 +41,7 @@ def test_golden_kwargs_spreadable_into_builder():
assert "KSampler" in cls
# El LoRA watercolor del preset aparece en el grafo.
loras = [n for n in wf.values() if n["class_type"] == "LoraLoader"]
assert loras and loras[0]["inputs"]["lora_name"] == "watercolor_style_sd15.safetensors"
assert loras and loras[0]["inputs"]["lora_name"] == "SD15_watercolor_style.safetensors"
def test_edge_style_override():
@@ -30,7 +30,7 @@ params:
- name: seed
desc: "Semilla del KSampler. Fija el mismo seed en la familia de tiers del mismo logro para que coincidan en composicion. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'detail_tweaker_sd15.safetensors'). None = sin LoRA. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_detail_tweaker.safetensors'). None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: rembg_model
@@ -169,7 +169,7 @@ def comfyui_build_achievement_badge_workflow(
seed: semilla del KSampler. Fija el mismo seed en la familia de tiers del mismo
logro para que coincidan en composicion. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej.
'detail_tweaker_sd15.safetensors'). None = sin LoRA. keyword-only.
'SD15_detail_tweaker.safetensors'). None = sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
rembg_model: modelo Rembg ('u2net' general, 'isnet-anime' para anime). Solo se
@@ -131,11 +131,11 @@ def test_edge_badge_reflected():
def test_edge_lora_injected():
"""lora -> LoraLoader presente con la fuerza dada."""
wf = comfyui_build_achievement_badge_workflow(
"collector", lora="detail_tweaker_sd15.safetensors", lora_strength=0.8
"collector", lora="SD15_detail_tweaker.safetensors", lora_strength=0.8
)
loras = [n for n in wf.values() if n["class_type"] == "LoraLoader"]
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "detail_tweaker_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_detail_tweaker.safetensors"
assert loras[0]["inputs"]["strength_model"] == pytest.approx(0.8)
@@ -29,7 +29,7 @@ params:
- name: seed
desc: "Semilla del KSampler. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'dark_fantasy_sd15.safetensors'). None = sin LoRA. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_dark_fantasy.safetensors'). None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: upscale_method
@@ -154,7 +154,7 @@ def comfyui_build_asset_variant_workflow(
deformar). 512 por defecto (SD1.5). keyword-only.
seed: semilla del KSampler. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej.
'dark_fantasy_sd15.safetensors'). None = sin LoRA. keyword-only.
'SD15_dark_fantasy.safetensors'). None = sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
upscale_method: metodo del ImageScale ('lanczos', 'bilinear', 'bicubic',
@@ -30,7 +30,7 @@ params:
- name: seed
desc: "Semilla del KSampler (y de la pasada hires). Misma seed + mismo subject -> misma ilustracion. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'detail_tweaker_sd15.safetensors', 'anime_style_xl.safetensors'). None = sin LoRA. Encadena estilo coherente entre cartas. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_detail_tweaker.safetensors', 'SDXL_anime_style.safetensors'). None = sin LoRA. Encadena estilo coherente entre cartas. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: negative
@@ -113,7 +113,7 @@ def comfyui_build_card_art_workflow(
seed: semilla del KSampler (y de la pasada hires). Misma seed + mismo
subject -> misma ilustracion. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej.
'detail_tweaker_sd15.safetensors', 'anime_style_xl.safetensors'). None =
'SD15_detail_tweaker.safetensors', 'SDXL_anime_style.safetensors'). None =
sin LoRA. Encadena estilo coherente entre cartas. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
@@ -94,13 +94,13 @@ def test_edge_card_style_in_prompt():
def test_edge_lora_reflected():
wf = comfyui_build_card_art_workflow(
"a phoenix",
lora="detail_tweaker_sd15.safetensors",
lora="SD15_detail_tweaker.safetensors",
lora_strength=0.9,
hires=False,
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "detail_tweaker_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_detail_tweaker.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.9
# Con lora el KSampler.model ya NO viene del checkpoint directo.
ksampler = _by_class(wf, "KSampler")[0]
@@ -114,11 +114,11 @@ def test_edge_negative_isolates_decal():
def test_edge_lora_reflected():
wf = comfyui_build_decal_overlay_workflow(
"rust stain", lora="grunge_sd15.safetensors", lora_strength=0.8
"rust stain", lora="SD15_grunge.safetensors", lora_strength=0.8
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "grunge_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_grunge.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.8
@@ -132,9 +132,9 @@ def test_error_empty_decal():
def test_determinism():
a = comfyui_build_decal_overlay_workflow(
"blood splatter", on_black=True, lora="grunge_sd15.safetensors", seed=7
"blood splatter", on_black=True, lora="SD15_grunge.safetensors", seed=7
)
b = comfyui_build_decal_overlay_workflow(
"blood splatter", on_black=True, lora="grunge_sd15.safetensors", seed=7
"blood splatter", on_black=True, lora="SD15_grunge.safetensors", seed=7
)
assert a == b
@@ -30,7 +30,7 @@ params:
- name: seed
desc: "Semilla del KSampler. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'detail_tweaker_sd15.safetensors'). None = sin LoRA. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_detail_tweaker.safetensors'). None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: rembg_model
@@ -72,7 +72,7 @@ wf = comfyui_build_dialogue_box_workflow(
# for st in ["fantasy dialogue box", "sci-fi terminal text box, neon glow",
# "visual novel text panel, soft pastel"]:
# wf = comfyui_build_dialogue_box_workflow(st, shape="rounded panel",
# lora="detail_tweaker_sd15.safetensors", seed=42)
# lora="SD15_detail_tweaker.safetensors", seed=42)
# comfyui_submit_workflow(wf) # -> comfyui_wait_result -> comfyui_fetch_output_image
# El motor de juego renderiza el texto de la conversacion sobre el interior plano.
```
@@ -141,7 +141,7 @@ def comfyui_build_dialogue_box_workflow(
recortable luego por el caller/pipeline. keyword-only.
seed: semilla del KSampler. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej.
'detail_tweaker_sd15.safetensors'). None = sin LoRA. keyword-only.
'SD15_detail_tweaker.safetensors'). None = sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
rembg_model: modelo Rembg ('u2net' general, 'isnet-anime' para anime).
@@ -82,12 +82,12 @@ def test_edge_shape_in_prompt():
def test_edge_lora_reflected():
wf = comfyui_build_dialogue_box_workflow(
"fantasy dialogue box",
lora="detail_tweaker_sd15.safetensors",
lora="SD15_detail_tweaker.safetensors",
lora_strength=0.9,
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "detail_tweaker_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_detail_tweaker.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.9
@@ -101,9 +101,9 @@ def test_error_empty_box_style():
def test_determinism():
a = comfyui_build_dialogue_box_workflow(
"stone tablet dialogue box", lora="detail_tweaker_sd15.safetensors", seed=7
"stone tablet dialogue box", lora="SD15_detail_tweaker.safetensors", seed=7
)
b = comfyui_build_dialogue_box_workflow(
"stone tablet dialogue box", lora="detail_tweaker_sd15.safetensors", seed=7
"stone tablet dialogue box", lora="SD15_detail_tweaker.safetensors", seed=7
)
assert a == b
@@ -32,7 +32,7 @@ params:
- name: seed
desc: "Semilla del KSampler (y del sampler del FaceDetailer). Misma seed + mismo character/ref_face -> mismo personaje; variar solo expression mantiene la identidad y cambia la cara. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'anime_lineart_sd15.safetensors'). None = sin LoRA. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_anime_lineart.safetensors'). None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: weight
@@ -119,7 +119,7 @@ def comfyui_build_emote_workflow(
mismo character/ref_face -> mismo personaje; variar solo expression
mantiene la identidad y cambia la cara. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej.
'anime_lineart_sd15.safetensors'). None = sin LoRA. keyword-only.
'SD15_anime_lineart.safetensors'). None = sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
weight: peso del IPAdapter-FaceID (solo si ref_face). 0.85 = parecido alto;
@@ -138,13 +138,13 @@ def test_edge_lora_reflected():
wf = comfyui_build_emote_workflow(
"a dark sorceress",
"smug",
lora="anime_lineart_sd15.safetensors",
lora="SD15_anime_lineart.safetensors",
lora_strength=0.8,
facedetailer=False,
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "anime_lineart_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_anime_lineart.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.8
@@ -176,13 +176,13 @@ def test_determinism():
a = comfyui_build_emote_workflow(
"a knight woman with red hair",
"happy, smiling",
lora="anime_lineart_sd15.safetensors",
lora="SD15_anime_lineart.safetensors",
seed=7,
)
b = comfyui_build_emote_workflow(
"a knight woman with red hair",
"happy, smiling",
lora="anime_lineart_sd15.safetensors",
lora="SD15_anime_lineart.safetensors",
seed=7,
)
assert a == b
@@ -30,7 +30,7 @@ params:
- name: seed
desc: "Semilla del KSampler. Misma seed + misma criatura/style -> misma figura; variar solo `variant` da variantes coherentes. keyword-only."
- name: lora
desc: "LoRA de estilo/criatura opcional en models/loras (ej. 'dark_fantasy_sd15.safetensors', 'monster_design_xl.safetensors'). None = sin LoRA. keyword-only."
desc: "LoRA de estilo/criatura opcional en models/loras (ej. 'SD15_dark_fantasy.safetensors', 'SDXL_monster_design.safetensors'). None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: rembg_model
@@ -147,7 +147,7 @@ def comfyui_build_enemy_creature_workflow(
seed: semilla del KSampler. Misma seed + misma criatura/style -> misma
figura; variar solo `variant` da las variantes coherentes. keyword-only.
lora: LoRA de estilo/criatura opcional en models/loras (ej.
'dark_fantasy_sd15.safetensors', 'monster_design_xl.safetensors'). None =
'SD15_dark_fantasy.safetensors', 'SDXL_monster_design.safetensors'). None =
sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
@@ -87,11 +87,11 @@ def test_edge_style_in_prompt():
def test_edge_lora_reflected():
wf = comfyui_build_enemy_creature_workflow(
"shadow wraith", lora="dark_fantasy_sd15.safetensors", lora_strength=0.9
"shadow wraith", lora="SD15_dark_fantasy.safetensors", lora_strength=0.9
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "dark_fantasy_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_dark_fantasy.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.9
@@ -105,9 +105,9 @@ def test_error_empty_creature():
def test_determinism():
a = comfyui_build_enemy_creature_workflow(
"fire dragon", variant="elite", lora="monster_design_xl.safetensors", seed=7
"fire dragon", variant="elite", lora="SDXL_monster_design.safetensors", seed=7
)
b = comfyui_build_enemy_creature_workflow(
"fire dragon", variant="elite", lora="monster_design_xl.safetensors", seed=7
"fire dragon", variant="elite", lora="SDXL_monster_design.safetensors", seed=7
)
assert a == b
@@ -30,7 +30,7 @@ params:
- name: seed
desc: "Semilla del KSampler. Misma seed + mismo plant/view/style -> misma planta. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'stylized_nature_sd15.safetensors', 'painterly_plants_xl.safetensors'). None = sin LoRA. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_stylized_nature.safetensors', 'SDXL_painterly_plants.safetensors'). None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: rembg_model
@@ -161,7 +161,7 @@ def comfyui_build_foliage_set_workflow(
seed: semilla del KSampler. Misma seed + mismo plant/view/style -> misma
planta. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej.
'stylized_nature_sd15.safetensors', 'painterly_plants_xl.safetensors').
'SD15_stylized_nature.safetensors', 'SDXL_painterly_plants.safetensors').
None = sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
@@ -111,7 +111,7 @@ def test_edge_negative_rejects_manmade_and_buildings():
def test_edge_lora_present():
"""lora -> LoraLoader presente con la fuerza pasada."""
wf = comfyui_build_foliage_set_workflow(
"a cluster of red flowers", lora="stylized_nature_sd15.safetensors",
"a cluster of red flowers", lora="SD15_stylized_nature.safetensors",
lora_strength=0.8,
)
assert "LoraLoader" in _classes(wf)
@@ -33,7 +33,7 @@ params:
- name: seed
desc: "Semilla del KSampler. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'dark_fantasy_sd15.safetensors'). None = sin LoRA. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_dark_fantasy.safetensors'). None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: mode
@@ -279,7 +279,7 @@ def comfyui_build_inpaint_asset_workflow(
los assets del set ya salen a 512). Un int reescala AMBAS a size x size de
forma consistente. Solo aplica al modo vae_encode. keyword-only.
seed: semilla del KSampler. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej. 'dark_fantasy_sd15.safetensors').
lora: LoRA de estilo opcional en models/loras (ej. 'SD15_dark_fantasy.safetensors').
None = sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
@@ -107,7 +107,7 @@ La referencia se sube primero a `input/` del servidor (LoadImage la lee por nomb
registra la carpeta `models/ipadapter`; si los modelos viven en otra ruta (ej.
`/mnt/2tb`), esa clave los mapea. Sin ella `ipadapter_file` sale vacio.
- **faceid usa insightface (`buffalo_l`) + la LoRA FaceID.** El UnifiedLoaderFaceID
carga la LoRA `ip-adapter-faceid-plusv2_sd15_lora.safetensors` (debe estar en
carga la LoRA `SD15_ip-adapter-faceid-plusv2_lora.safetensors` (debe estar en
`models/loras/`). `provider='CPU'` por defecto: insightface en CPU no compite por
los 8GB de VRAM; pon `'CUDA'` solo si tienes onnxruntime-gpu instalado.
- **La referencia debe existir en `input/`.** Es un nombre de archivo, no una ruta:
@@ -5,8 +5,8 @@ lang: py
domain: ml
version: "1.0.0"
purity: pure
signature: "def comfyui_build_isometric_workflow(positive: str, negative: str = \"perspective, vanishing point, blurry, low quality\", *, ckpt_name: str = \"dreamshaper_8.safetensors\", iso_lora: str = \"isometric_game_assets_sd15.safetensors\", lora_strength: float = 0.9, grid_image: str | None = None, controlnet_name: str = \"control_v11p_sd15_canny_fp16.safetensors\", controlnet_strength: float = 0.6, steps: int = 28, cfg: float = 6.0, width: int = 1024, height: int = 1024, seed: int = 0, sampler_name: str = \"dpmpp_2m\", scheduler: str = \"karras\", filename_prefix: str = \"isometric\") -> dict"
description: "Construye el dict (API format) de un workflow ComfyUI isometrico: txt2img + LoRA isometrica (isometric_game_assets_sd15) que impone el angulo 2:1, con ControlNet grid opcional (plantilla de rejilla iso preprocesada) para reforzar el layout. Compone comfyui_build_txt2img_workflow + comfyui_inject_lora (+ comfyui_inject_controlnet si grid_image). Pura, sin red ni I/O. class_types verificados contra /object_info."
signature: "def comfyui_build_isometric_workflow(positive: str, negative: str = \"perspective, vanishing point, blurry, low quality\", *, ckpt_name: str = \"dreamshaper_8.safetensors\", iso_lora: str = \"SD15_isometric_game_assets.safetensors\", lora_strength: float = 0.9, grid_image: str | None = None, controlnet_name: str = \"control_v11p_sd15_canny_fp16.safetensors\", controlnet_strength: float = 0.6, steps: int = 28, cfg: float = 6.0, width: int = 1024, height: int = 1024, seed: int = 0, sampler_name: str = \"dpmpp_2m\", scheduler: str = \"karras\", filename_prefix: str = \"isometric\") -> dict"
description: "Construye el dict (API format) de un workflow ComfyUI isometrico: txt2img + LoRA isometrica (SD15_isometric_game_assets) que impone el angulo 2:1, con ControlNet grid opcional (plantilla de rejilla iso preprocesada) para reforzar el layout. Compone comfyui_build_txt2img_workflow + comfyui_inject_lora (+ comfyui_inject_controlnet si grid_image). Pura, sin red ni I/O. class_types verificados contra /object_info."
tags: [comfyui, ml, gamedev-2d, isometric, tile, workflow, stable-diffusion]
uses_functions: [comfyui_build_txt2img_workflow_py_ml, comfyui_inject_lora_py_ml, comfyui_inject_controlnet_py_ml]
uses_types: []
@@ -22,7 +22,7 @@ params:
- name: ckpt_name
desc: "Checkpoint base. Default 'dreamshaper_8.safetensors' (SD1.5 holgado, casa con la LoRA iso SD1.5). keyword-only."
- name: iso_lora
desc: "LoRA isometrica en models/loras. Default 'isometric_game_assets_sd15.safetensors'. keyword-only."
desc: "LoRA isometrica en models/loras. Default 'SD15_isometric_game_assets.safetensors'. keyword-only."
- name: lora_strength
desc: "Fuerza de la LoRA iso sobre model y clip (recomendado 0.9). keyword-only."
- name: grid_image
@@ -86,7 +86,7 @@ el prompt se desvíe del ángulo y quieras forzar la rejilla. Para una textura i
## Gotchas
- **Requiere la LoRA `isometric_game_assets_sd15.safetensors` en models/loras**
- **Requiere la LoRA `SD15_isometric_game_assets.safetensors` en models/loras**
(ya presente). Es SD1.5: usa un checkpoint SD1.5 (`dreamshaper_8`).
- **`comfyui_inject_controlnet` NO preprocesa**: `grid_image` debe ser una imagen
de control ya en line-art/canny limpio. Si partes de un render, preprocésala
@@ -5,7 +5,7 @@ La proyeccion isometrica 2:1 no necesita node nuevo: es composicion pura (report
1. LoRA isometrica (via principal): impone el angulo 2:1 y el look de tile. Se
inyecta con comfyui_inject_lora sobre la base txt2img. LoRA instalada:
isometric_game_assets_sd15.safetensors (SD1.5, holgada en 8GB).
SD15_isometric_game_assets.safetensors (SD1.5, holgada en 8GB).
2. ControlNet grid (refuerzo opcional): una plantilla de rejilla isometrica
(rombos 2:1) ya preprocesada (canny/lineart) fuerza el layout. Se inyecta con
comfyui_inject_controlnet (legacy ControlNetApply; basta para un grid guia).
@@ -35,7 +35,7 @@ def comfyui_build_isometric_workflow(
negative: str = "perspective, vanishing point, blurry, low quality",
*,
ckpt_name: str = "dreamshaper_8.safetensors",
iso_lora: str = "isometric_game_assets_sd15.safetensors",
iso_lora: str = "SD15_isometric_game_assets.safetensors",
lora_strength: float = 0.9,
grid_image: str | None = None,
controlnet_name: str = "control_v11p_sd15_canny_fp16.safetensors",
@@ -59,7 +59,7 @@ def comfyui_build_isometric_workflow(
ckpt_name: checkpoint base (default 'dreamshaper_8.safetensors', SD1.5
holgado y casa con la LoRA iso SD1.5). keyword-only.
iso_lora: LoRA isometrica en models/loras
(default 'isometric_game_assets_sd15.safetensors').
(default 'SD15_isometric_game_assets.safetensors').
lora_strength: fuerza de la LoRA iso sobre model y clip (recomendado 0.9).
grid_image: nombre de una plantilla de rejilla isometrica ya preprocesada
(line-art/canny) en input/ del servidor. Si se pasa, inyecta un
@@ -15,7 +15,7 @@ def test_golden_iso_lora_no_controlnet():
wf = comfyui_build_isometric_workflow("isometric house, game asset")
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "isometric_game_assets_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_isometric_game_assets.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.9
# Sin grid_image -> sin ControlNet.
assert len(_by_class(wf, "ControlNetApply")) == 0
@@ -28,7 +28,7 @@ params:
- name: seed
desc: "Semilla del KSampler. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'detail_tweaker_sd15.safetensors', 'isometric_game_assets_sd15.safetensors'). None = sin LoRA. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_detail_tweaker.safetensors', 'SD15_isometric_game_assets.safetensors'). None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: rembg_model
@@ -69,7 +69,7 @@ wf = comfyui_build_item_icon_workflow(
# Set coherente: misma firma de estilo para cada item del inventario.
# for it in ["iron sword", "gold ring", "ancient book", "wooden shield"]:
# wf = comfyui_build_item_icon_workflow(it, style="hand-painted RPG icon",
# lora="detail_tweaker_sd15.safetensors", seed=42)
# lora="SD15_detail_tweaker.safetensors", seed=42)
# comfyui_submit_workflow(wf) # -> comfyui_wait_result -> comfyui_fetch_output_image
# Contact-sheet del set: montar los PNG resultantes con comfyui_build_grid.
```
@@ -121,7 +121,7 @@ def comfyui_build_item_icon_workflow(
luego por el caller/pipeline. keyword-only.
seed: semilla del KSampler. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej.
'detail_tweaker_sd15.safetensors', 'isometric_game_assets_sd15.safetensors').
'SD15_detail_tweaker.safetensors', 'SD15_isometric_game_assets.safetensors').
None = sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
@@ -73,11 +73,11 @@ def test_edge_style_in_prompt():
def test_edge_lora_reflected():
wf = comfyui_build_item_icon_workflow(
"magic shield", lora="detail_tweaker_sd15.safetensors", lora_strength=0.9
"magic shield", lora="SD15_detail_tweaker.safetensors", lora_strength=0.9
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "detail_tweaker_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_detail_tweaker.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.9
@@ -90,6 +90,6 @@ def test_error_empty_item():
def test_determinism():
a = comfyui_build_item_icon_workflow("emerald gem", lora="detail_tweaker_sd15.safetensors", seed=7)
b = comfyui_build_item_icon_workflow("emerald gem", lora="detail_tweaker_sd15.safetensors", seed=7)
a = comfyui_build_item_icon_workflow("emerald gem", lora="SD15_detail_tweaker.safetensors", seed=7)
b = comfyui_build_item_icon_workflow("emerald gem", lora="SD15_detail_tweaker.safetensors", seed=7)
assert a == b
@@ -39,7 +39,7 @@ params:
- name: seed
desc: "Semilla del KSampler. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'dark_fantasy_sd15.safetensors'). None = sin LoRA. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_dark_fantasy.safetensors'). None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: negative
@@ -169,7 +169,7 @@ def comfyui_build_outpaint_asset_workflow(
costura. Default 0 (el feathering del pad suele bastar). Se clampa a [0, 64]
(limite de VAEEncodeForInpaint). keyword-only.
seed: semilla del KSampler. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej. 'dark_fantasy_sd15.safetensors').
lora: LoRA de estilo opcional en models/loras (ej. 'SD15_dark_fantasy.safetensors').
None = sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
@@ -123,11 +123,11 @@ def test_edge_negative_isolates_single_particle():
def test_edge_lora_reflected():
wf = comfyui_build_particle_texture_workflow(
"glowing flare", lora="vfx_sd15.safetensors", lora_strength=0.8
"glowing flare", lora="SD15_vfx.safetensors", lora_strength=0.8
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "vfx_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_vfx.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.8
@@ -141,9 +141,9 @@ def test_error_empty_particle():
def test_determinism():
a = comfyui_build_particle_texture_workflow(
"spark", soft=False, lora="vfx_sd15.safetensors", seed=7
"spark", soft=False, lora="SD15_vfx.safetensors", seed=7
)
b = comfyui_build_particle_texture_workflow(
"spark", soft=False, lora="vfx_sd15.safetensors", seed=7
"spark", soft=False, lora="SD15_vfx.safetensors", seed=7
)
assert a == b
@@ -5,8 +5,8 @@ lang: py
domain: ml
version: "1.0.0"
purity: pure
signature: "def comfyui_build_pixelart_workflow(positive: str, negative: str = \"blurry, jpeg artifacts, gradient, smooth shading, anti-aliasing\", *, ckpt_name: str = \"juggernaut_xl_v11.safetensors\", pixel_lora: str = \"pixel-art-xl.safetensors\", lora_strength: float = 1.2, use_lcm: bool = True, lcm_lora: str = \"lcm-lora-sdxl.safetensors\", lcm_strength: float = 1.0, steps: int | None = None, cfg: float | None = None, width: int = 1024, height: int = 1024, seed: int = 0, sampler_name: str | None = None, scheduler: str | None = None, filename_prefix: str = \"pixelart\") -> dict"
description: "Construye el dict (API format) del workflow ComfyUI de pixel-art Fase 1: SDXL base + LoRA pixel-art-xl (nerijs), opcionalmente con LCM-LoRA para 8 steps. Compone comfyui_build_txt2img_workflow + comfyui_inject_multi_lora. El pixel-perfect (Fase 2) lo hace comfyui_pixelize_image, no este workflow. Pura, sin red ni I/O. class_types verificados contra /object_info (8GB lowvram)."
signature: "def comfyui_build_pixelart_workflow(positive: str, negative: str = \"blurry, jpeg artifacts, gradient, smooth shading, anti-aliasing\", *, ckpt_name: str = \"juggernaut_xl_v11.safetensors\", pixel_lora: str = \"SDXL_pixel-art.safetensors\", lora_strength: float = 1.2, use_lcm: bool = True, lcm_lora: str = \"SDXL_lcm-lora.safetensors\", lcm_strength: float = 1.0, steps: int | None = None, cfg: float | None = None, width: int = 1024, height: int = 1024, seed: int = 0, sampler_name: str | None = None, scheduler: str | None = None, filename_prefix: str = \"pixelart\") -> dict"
description: "Construye el dict (API format) del workflow ComfyUI de pixel-art Fase 1: SDXL base + LoRA SDXL_pixel-art (nerijs), opcionalmente con LCM-LoRA para 8 steps. Compone comfyui_build_txt2img_workflow + comfyui_inject_multi_lora. El pixel-perfect (Fase 2) lo hace comfyui_pixelize_image, no este workflow. Pura, sin red ni I/O. class_types verificados contra /object_info (8GB lowvram)."
tags: [comfyui, ml, gamedev-2d, pixelart, workflow, stable-diffusion, sdxl]
uses_functions: [comfyui_build_txt2img_workflow_py_ml, comfyui_inject_multi_lora_py_ml]
uses_types: []
@@ -20,15 +20,15 @@ params:
- name: negative
desc: "Prompt negativo. Por defecto evita blur/gradientes/anti-alias que estropean el look pixel."
- name: ckpt_name
desc: "Checkpoint SDXL base. Default 'juggernaut_xl_v11.safetensors' (el SDXL instalado; pixel-art-xl es LoRA SDXL). keyword-only."
desc: "Checkpoint SDXL base. Default 'juggernaut_xl_v11.safetensors' (el SDXL instalado; SDXL_pixel-art es LoRA SDXL). keyword-only."
- name: pixel_lora
desc: "Archivo de la LoRA de estilo pixel-art en models/loras. Default 'pixel-art-xl.safetensors'. keyword-only."
desc: "Archivo de la LoRA de estilo pixel-art en models/loras. Default 'SDXL_pixel-art.safetensors'. keyword-only."
- name: lora_strength
desc: "Fuerza de pixel-art-xl sobre model y clip (recomendado 1.2). Se clampa a [0.0, 2.0]. keyword-only."
desc: "Fuerza de SDXL_pixel-art sobre model y clip (recomendado 1.2). Se clampa a [0.0, 2.0]. keyword-only."
- name: use_lcm
desc: "Si True encadena LCM-LoRA SDXL y usa defaults rapidos (8 steps, cfg 1.5, sampler 'lcm', scheduler 'sgm_uniform'); si False usa defaults SDXL normales (25 steps, cfg 7, 'euler'/'normal'). keyword-only."
- name: lcm_lora
desc: "Archivo de la LCM-LoRA SDXL en models/loras (solo si use_lcm). Default 'lcm-lora-sdxl.safetensors'. keyword-only."
desc: "Archivo de la LCM-LoRA SDXL en models/loras (solo si use_lcm). Default 'SDXL_lcm-lora.safetensors'. keyword-only."
- name: lcm_strength
desc: "Fuerza de la LCM-LoRA sobre model y clip (recomendado 1.0). keyword-only."
- name: steps
@@ -47,9 +47,9 @@ params:
desc: "Scheduler del KSampler. None = default del modo ('sgm_uniform' con LCM, 'normal' sin). keyword-only."
- name: filename_prefix
desc: "Prefijo del PNG que SaveImage escribe en output/. keyword-only."
output: "dict en API format listo para comfyui_submit_workflow: CheckpointLoaderSimple + 1 LoraLoader (pixel-art-xl) o 2 (+ lcm-lora-sdxl si use_lcm) + KSampler con params del modo + SaveImage."
output: "dict en API format listo para comfyui_submit_workflow: CheckpointLoaderSimple + 1 LoraLoader (SDXL_pixel-art) o 2 (+ SDXL_lcm-lora si use_lcm) + KSampler con params del modo + SaveImage."
tested: true
tests: ["golden use_lcm=True: 2 LoraLoader (pixel-art-xl@1.2, lcm@1.0) + KSampler steps 8/cfg 1.5/sampler lcm/sgm_uniform", "edge use_lcm=False: 1 LoraLoader + KSampler steps 25/cfg 7/euler/normal", "edge overrides steps/cfg + clamp lora_strength a 2.0", "error positive vacio -> ValueError", "determinismo"]
tests: ["golden use_lcm=True: 2 LoraLoader (SDXL_pixel-art@1.2, lcm@1.0) + KSampler steps 8/cfg 1.5/sampler lcm/sgm_uniform", "edge use_lcm=False: 1 LoraLoader + KSampler steps 25/cfg 7/euler/normal", "edge overrides steps/cfg + clamp lora_strength a 2.0", "error positive vacio -> ValueError", "determinismo"]
test_file_path: "python/functions/ml/comfyui_build_pixelart_workflow_test.py"
file_path: "python/functions/ml/comfyui_build_pixelart_workflow.py"
---
@@ -61,7 +61,7 @@ import sys, os
sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions"))
from ml.comfyui_build_pixelart_workflow import comfyui_build_pixelart_workflow
# Fase 1: generar el crudo pixel-art (SDXL + pixel-art-xl + LCM, 8 steps).
# Fase 1: generar el crudo pixel-art (SDXL + SDXL_pixel-art + LCM, 8 steps).
wf = comfyui_build_pixelart_workflow(
"isometric tiny house, pixel, 32x32 style, vibrant colors",
use_lcm=True,
@@ -87,9 +87,9 @@ Para tilesets, genera cada tile por separado y ensambla con `comfyui_build_grid`
no lo pegues en la UI.
- **No produce pixel-perfect por sí solo**: deja pixeles irregulares y cientos de
colores. El pixel-perfect es post-proceso (`comfyui_pixelize_image`, CPU/PIL).
- `use_lcm=True` requiere `lcm-lora-sdxl.safetensors` en models/loras y el sampler
- `use_lcm=True` requiere `SDXL_lcm-lora.safetensors` en models/loras y el sampler
`lcm`; ambos verificados presentes. Da iteración rápida (8 steps) en 8GB.
- `ckpt_name` debe ser un checkpoint SDXL (pixel-art-xl es LoRA SDXL). Default
- `ckpt_name` debe ser un checkpoint SDXL (SDXL_pixel-art es LoRA SDXL). Default
`juggernaut_xl_v11` (no existe `sd_xl_base_1.0` instalado). SDXL en 8GB corre con
`--lowvram`; la Fase 2 es CPU y no toca VRAM.
- Función pura: no valida contra el server. Si una LoRA/checkpoint falta, el HTTP
@@ -1,19 +1,19 @@
"""Construye un workflow ComfyUI de pixel-art (SDXL + LoRA pixel-art-xl) en API format.
"""Construye un workflow ComfyUI de pixel-art (SDXL + LoRA SDXL_pixel-art) en API format.
Fase 1 del pipeline pixel-art (ver report 0135): genera el *look* pixel-art con
SDXL base + la LoRA `pixel-art-xl` (nerijs), opcionalmente acelerada con la
SDXL base + la LoRA `SDXL_pixel-art` (nerijs), opcionalmente acelerada con la
LCM-LoRA SDXL para iterar en 8 steps. El resultado todavia tiene pixeles de
tamano irregular y cientos de colores: el pixel-perfect (Fase 2) lo hace
`comfyui_pixelize_image` (downscale nearest + cuantizacion), NO este workflow.
Compone funciones existentes del registry, no reescribe el grafo:
- comfyui_build_txt2img_workflow -> base SDXL txt2img
- comfyui_inject_multi_lora -> encadena pixel-art-xl (+ lcm-lora-sdxl)
- comfyui_inject_multi_lora -> encadena SDXL_pixel-art (+ SDXL_lcm-lora)
class_types/params verificados contra /object_info del servidor (8GB lowvram):
CheckpointLoaderSimple, LoraLoader, CLIPTextEncode, EmptyLatentImage,
KSampler (sampler 'lcm', scheduler 'sgm_uniform' presentes), VAEDecode, SaveImage.
LoRAs presentes en models/loras: pixel-art-xl.safetensors, lcm-lora-sdxl.safetensors.
LoRAs presentes en models/loras: SDXL_pixel-art.safetensors, SDXL_lcm-lora.safetensors.
Funcion pura: sin red, sin I/O. Determinista para los mismos argumentos.
"""
@@ -34,10 +34,10 @@ def comfyui_build_pixelart_workflow(
negative: str = "blurry, jpeg artifacts, gradient, smooth shading, anti-aliasing",
*,
ckpt_name: str = "juggernaut_xl_v11.safetensors",
pixel_lora: str = "pixel-art-xl.safetensors",
pixel_lora: str = "SDXL_pixel-art.safetensors",
lora_strength: float = 1.2,
use_lcm: bool = True,
lcm_lora: str = "lcm-lora-sdxl.safetensors",
lcm_lora: str = "SDXL_lcm-lora.safetensors",
lcm_strength: float = 1.0,
steps: int | None = None,
cfg: float | None = None,
@@ -56,9 +56,9 @@ def comfyui_build_pixelart_workflow(
negative: prompt negativo (por defecto evita blur/gradientes/anti-alias,
que estropean el look pixel).
ckpt_name: checkpoint SDXL base (default 'juggernaut_xl_v11.safetensors',
el SDXL instalado; pixel-art-xl es una LoRA SDXL). keyword-only.
el SDXL instalado; SDXL_pixel-art es una LoRA SDXL). keyword-only.
pixel_lora: archivo de la LoRA de estilo pixel-art en models/loras.
lora_strength: fuerza de pixel-art-xl sobre model y clip (recomendado 1.2).
lora_strength: fuerza de SDXL_pixel-art sobre model y clip (recomendado 1.2).
Se clampa a [0.0, 2.0].
use_lcm: si True encadena la LCM-LoRA SDXL y usa los defaults rapidos
(8 steps, cfg 1.5, sampler 'lcm', scheduler 'sgm_uniform'); si False
@@ -74,8 +74,8 @@ def comfyui_build_pixelart_workflow(
Returns:
dict en API format listo para comfyui_submit_workflow, con el
CheckpointLoaderSimple, 1 LoraLoader (pixel-art-xl) o 2 (pixel-art-xl +
lcm-lora-sdxl si use_lcm), KSampler con los params del modo y SaveImage.
CheckpointLoaderSimple, 1 LoraLoader (SDXL_pixel-art) o 2 (SDXL_pixel-art +
SDXL_lcm-lora si use_lcm), KSampler con los params del modo y SaveImage.
Raises:
ValueError: si positive esta vacio.
@@ -18,12 +18,12 @@ def _ksampler(wf):
def test_golden_lcm_two_loras():
wf = comfyui_build_pixelart_workflow("isometric house, pixel, 32x32 style", use_lcm=True)
cls = _classes(wf)
# Dos LoraLoader: pixel-art-xl + lcm-lora-sdxl.
# Dos LoraLoader: SDXL_pixel-art + SDXL_lcm-lora.
loras = [n for n in wf.values() if n["class_type"] == "LoraLoader"]
assert len(loras) == 2
names = {n["inputs"]["lora_name"] for n in loras}
assert names == {"pixel-art-xl.safetensors", "lcm-lora-sdxl.safetensors"}
px = next(n for n in loras if n["inputs"]["lora_name"] == "pixel-art-xl.safetensors")
assert names == {"SDXL_pixel-art.safetensors", "SDXL_lcm-lora.safetensors"}
px = next(n for n in loras if n["inputs"]["lora_name"] == "SDXL_pixel-art.safetensors")
assert px["inputs"]["strength_model"] == 1.2
# KSampler con defaults LCM.
ks = _ksampler(wf)["inputs"]
@@ -36,7 +36,7 @@ def test_edge_no_lcm_single_lora():
wf = comfyui_build_pixelart_workflow("a pixel sword", use_lcm=False)
loras = [n for n in wf.values() if n["class_type"] == "LoraLoader"]
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "pixel-art-xl.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SDXL_pixel-art.safetensors"
ks = _ksampler(wf)["inputs"]
assert ks["steps"] == 25 and ks["cfg"] == 7.0
assert ks["sampler_name"] == "euler" and ks["scheduler"] == "normal"
@@ -50,7 +50,7 @@ def test_edge_overrides_and_clamp():
assert ks["steps"] == 12 and ks["cfg"] == 2.0
px = next(
n for n in wf.values()
if n["class_type"] == "LoraLoader" and n["inputs"]["lora_name"] == "pixel-art-xl.safetensors"
if n["class_type"] == "LoraLoader" and n["inputs"]["lora_name"] == "SDXL_pixel-art.safetensors"
)
assert px["inputs"]["strength_model"] == 2.0 # clamp a [0,2]
@@ -30,7 +30,7 @@ params:
- name: seed
desc: "Semilla del KSampler (y del sampler del FaceDetailer). Misma seed + mismo character/ref_face -> mismo retrato. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'anime_lineart_sd15.safetensors'). None = sin LoRA. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_anime_lineart.safetensors'). None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: weight
@@ -105,7 +105,7 @@ def comfyui_build_portrait_avatar_workflow(
seed: semilla del KSampler (y del sampler del FaceDetailer). Misma seed +
mismo character/ref_face -> mismo retrato. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej.
'anime_lineart_sd15.safetensors'). None = sin LoRA. keyword-only.
'SD15_anime_lineart.safetensors'). None = sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
weight: peso del IPAdapter-FaceID (solo si ref_face). 0.85 = parecido alto;
@@ -107,13 +107,13 @@ def test_edge_size_reflected_square():
def test_edge_lora_reflected():
wf = comfyui_build_portrait_avatar_workflow(
"a dark sorceress",
lora="anime_lineart_sd15.safetensors",
lora="SD15_anime_lineart.safetensors",
lora_strength=0.8,
facedetailer=False,
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "anime_lineart_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_anime_lineart.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.8
@@ -135,9 +135,9 @@ def test_error_empty_character():
def test_determinism():
a = comfyui_build_portrait_avatar_workflow(
"a young knight woman with red hair", lora="anime_lineart_sd15.safetensors", seed=7
"a young knight woman with red hair", lora="SD15_anime_lineart.safetensors", seed=7
)
b = comfyui_build_portrait_avatar_workflow(
"a young knight woman with red hair", lora="anime_lineart_sd15.safetensors", seed=7
"a young knight woman with red hair", lora="SD15_anime_lineart.safetensors", seed=7
)
assert a == b
@@ -32,7 +32,7 @@ params:
- name: seed
desc: "Semilla del KSampler. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'detail_tweaker_sd15.safetensors'). None = sin LoRA. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_detail_tweaker.safetensors'). None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: rembg_model
@@ -155,7 +155,7 @@ def comfyui_build_projectile_workflow(
keyword-only.
seed: semilla del KSampler. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej.
'detail_tweaker_sd15.safetensors'). None = sin LoRA. keyword-only.
'SD15_detail_tweaker.safetensors'). None = sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
rembg_model: modelo Rembg ('u2net' general, 'isnet-anime' para anime). Solo
@@ -119,11 +119,11 @@ def test_edge_style_reflected():
def test_edge_lora_reflected():
wf = comfyui_build_projectile_workflow(
"arrow", lora="detail_tweaker_sd15.safetensors", lora_strength=0.9
"arrow", lora="SD15_detail_tweaker.safetensors", lora_strength=0.9
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "detail_tweaker_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_detail_tweaker.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.9
@@ -148,9 +148,9 @@ def test_error_empty_projectile():
def test_determinism():
a = comfyui_build_projectile_workflow(
"fireball", direction="right", glow=True, lora="detail_tweaker_sd15.safetensors", seed=7
"fireball", direction="right", glow=True, lora="SD15_detail_tweaker.safetensors", seed=7
)
b = comfyui_build_projectile_workflow(
"fireball", direction="right", glow=True, lora="detail_tweaker_sd15.safetensors", seed=7
"fireball", direction="right", glow=True, lora="SD15_detail_tweaker.safetensors", seed=7
)
assert a == b
@@ -28,7 +28,7 @@ params:
- name: seed
desc: "Semilla del KSampler. Misma seed + mismo prop/style -> mismo objeto. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'isometric_game_assets_sd15.safetensors', 'stylized_props_xl.safetensors'). None = sin LoRA. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_isometric_game_assets.safetensors', 'SDXL_stylized_props.safetensors'). None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: rembg_model
@@ -146,7 +146,7 @@ def comfyui_build_prop_object_workflow(
seed: semilla del KSampler. Misma seed + mismo prop/style -> mismo objeto.
keyword-only.
lora: LoRA de estilo opcional en models/loras (ej.
'isometric_game_assets_sd15.safetensors', 'stylized_props_xl.safetensors').
'SD15_isometric_game_assets.safetensors', 'SDXL_stylized_props.safetensors').
None = sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
@@ -102,12 +102,12 @@ def test_edge_world_object_not_inventory_icon():
def test_edge_lora_reflected():
wf = comfyui_build_prop_object_workflow(
"ornate vase",
lora="isometric_game_assets_sd15.safetensors",
lora="SD15_isometric_game_assets.safetensors",
lora_strength=0.9,
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "isometric_game_assets_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_isometric_game_assets.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.9
@@ -127,9 +127,9 @@ def test_error_empty_prop():
def test_determinism():
a = comfyui_build_prop_object_workflow(
"wooden barrel", lora="stylized_props_xl.safetensors", seed=7
"wooden barrel", lora="SDXL_stylized_props.safetensors", seed=7
)
b = comfyui_build_prop_object_workflow(
"wooden barrel", lora="stylized_props_xl.safetensors", seed=7
"wooden barrel", lora="SDXL_stylized_props.safetensors", seed=7
)
assert a == b
@@ -97,7 +97,7 @@ def test_edge_lora_injected_when_requested():
wf_no = comfyui_build_rune_glyph_workflow("ward symbol")
assert "LoraLoader" not in _classes(wf_no)
wf_lora = comfyui_build_rune_glyph_workflow(
"ward symbol", lora="arcane_style_sd15.safetensors", lora_strength=0.8
"ward symbol", lora="SD15_arcane_style.safetensors", lora_strength=0.8
)
assert "LoraLoader" in _classes(wf_lora)
ll = next(n for n in wf_lora.values() if n["class_type"] == "LoraLoader")
@@ -54,7 +54,7 @@ def test_edge_no_circular_vae():
def test_edge_material_lora_before_tile():
wf = comfyui_build_seamless_tile_workflow(
"stone", material_lora="detail_tweaker_sd15.safetensors", lora_strength=0.7
"stone", material_lora="SD15_detail_tweaker.safetensors", lora_strength=0.7
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
@@ -32,7 +32,7 @@ params:
- name: seed
desc: "Semilla del KSampler. Fija el mismo seed en el par unlocked/locked del mismo talento para que coincidan. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'detail_tweaker_sd15.safetensors'). None = sin LoRA. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_detail_tweaker.safetensors'). None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: rembg_model
@@ -172,7 +172,7 @@ def comfyui_build_skill_tree_node_workflow(
seed: semilla del KSampler. Fija el mismo seed en el par unlocked/locked del
mismo talento para que coincidan. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej.
'detail_tweaker_sd15.safetensors'). None = sin LoRA. keyword-only.
'SD15_detail_tweaker.safetensors'). None = sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
rembg_model: modelo Rembg ('u2net' general, 'isnet-anime' para anime). Solo se
@@ -121,11 +121,11 @@ def test_edge_skill_reflected():
def test_edge_lora_injected():
"""lora -> LoraLoader presente con la fuerza dada."""
wf = comfyui_build_skill_tree_node_workflow(
"whirlwind", lora="detail_tweaker_sd15.safetensors", lora_strength=0.8
"whirlwind", lora="SD15_detail_tweaker.safetensors", lora_strength=0.8
)
loras = [n for n in wf.values() if n["class_type"] == "LoraLoader"]
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "detail_tweaker_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_detail_tweaker.safetensors"
assert loras[0]["inputs"]["strength_model"] == pytest.approx(0.8)
@@ -30,7 +30,7 @@ params:
- name: seed
desc: "Semilla del KSampler (y de la pasada hires). Misma seed + misma scene -> misma ilustracion. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'concept_art_sd15.safetensors', 'cinematic_xl.safetensors'). None = sin LoRA. Encadena la identidad visual del juego. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_concept_art.safetensors', 'SDXL_cinematic.safetensors'). None = sin LoRA. Encadena la identidad visual del juego. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: negative
@@ -114,7 +114,7 @@ def comfyui_build_splash_art_workflow(
seed: semilla del KSampler (y de la pasada hires). Misma seed + misma scene
-> misma ilustracion. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej.
'concept_art_sd15.safetensors', 'cinematic_xl.safetensors'). None = sin
'SD15_concept_art.safetensors', 'SDXL_cinematic.safetensors'). None = sin
LoRA. Encadena la identidad visual del juego. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
@@ -97,13 +97,13 @@ def test_edge_mood_in_prompt():
def test_edge_lora_reflected():
wf = comfyui_build_splash_art_workflow(
"a knight raising a banner on a hill",
lora="concept_art_sd15.safetensors",
lora="SD15_concept_art.safetensors",
lora_strength=0.9,
hires=False,
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "concept_art_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_concept_art.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.9
# Con lora el KSampler.model ya NO viene del checkpoint directo.
ksampler = _by_class(wf, "KSampler")[0]
@@ -31,7 +31,7 @@ params:
- name: seed
desc: "Semilla del KSampler. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'dark_fantasy_sd15.safetensors'). None = sin LoRA. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_dark_fantasy.safetensors'). None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: preprocess
@@ -200,7 +200,7 @@ def comfyui_build_sprite_from_sketch_workflow(
size: lado en px del sprite generado (EmptyLatentImage size x size). El preprocesador
normaliza el boceto a esta resolucion. 512 por defecto (SD1.5). keyword-only.
seed: semilla del KSampler. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej. 'dark_fantasy_sd15.safetensors').
lora: LoRA de estilo opcional en models/loras (ej. 'SD15_dark_fantasy.safetensors').
None = sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only.
preprocess: si True (default), inserta el preprocesador del control_type entre el
@@ -66,11 +66,11 @@ def test_edge_opaque_no_rembg():
def test_edge_char_lora():
wf = comfyui_build_sprite_sheet_workflow(
"a hero", char_lora="anime_style_box_sd15.safetensors", lora_strength=0.8
"a hero", char_lora="SD15_anime_style_box.safetensors", lora_strength=0.8
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "anime_style_box_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_anime_style_box.safetensors"
def test_error_empty_subject():
@@ -28,7 +28,7 @@ params:
- name: seed
desc: "Semilla del KSampler. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'detail_tweaker_sd15.safetensors'). None = sin LoRA. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_detail_tweaker.safetensors'). None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: rembg_model
@@ -69,7 +69,7 @@ wf = comfyui_build_status_effect_icon_workflow(
# Barra de estados coherente: misma firma de estilo para cada efecto.
# for fx in ["burning", "frozen", "shield", "regeneration", "stun", "speed boost"]:
# wf = comfyui_build_status_effect_icon_workflow(fx, ui_style="game status icon, bold symbol, flat",
# lora="detail_tweaker_sd15.safetensors", seed=42)
# lora="SD15_detail_tweaker.safetensors", seed=42)
# comfyui_submit_workflow(wf) # -> comfyui_wait_result -> comfyui_fetch_output_image
# Atlas de estados: montar los PNG resultantes con comfyui_build_grid.
```
@@ -141,7 +141,7 @@ def comfyui_build_status_effect_icon_workflow(
recortable luego por el caller/pipeline. keyword-only.
seed: semilla del KSampler. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej.
'detail_tweaker_sd15.safetensors'). None = sin LoRA. keyword-only.
'SD15_detail_tweaker.safetensors'). None = sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
rembg_model: modelo Rembg ('u2net' general, 'isnet-anime' para anime).
@@ -95,11 +95,11 @@ def test_edge_effect_reflected():
def test_edge_lora_injected():
"""lora -> LoraLoader presente con la fuerza dada."""
wf = comfyui_build_status_effect_icon_workflow(
"speed boost", lora="detail_tweaker_sd15.safetensors", lora_strength=0.8
"speed boost", lora="SD15_detail_tweaker.safetensors", lora_strength=0.8
)
loras = [n for n in wf.values() if n["class_type"] == "LoraLoader"]
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "detail_tweaker_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_detail_tweaker.safetensors"
assert loras[0]["inputs"]["strength_model"] == pytest.approx(0.8)
@@ -30,7 +30,7 @@ params:
- name: seed
desc: "Semilla del KSampler. Misma seed + misma structure/view/style -> mismo edificio. keyword-only."
- name: lora
desc: "LoRA de estilo/isometrica opcional en models/loras (ej. 'isometric_game_assets_sd15.safetensors', 'stylized_buildings_xl.safetensors'). Para escenarios iso coherentes la LoRA iso fija el angulo 2:1. None = sin LoRA. keyword-only."
desc: "LoRA de estilo/isometrica opcional en models/loras (ej. 'SD15_isometric_game_assets.safetensors', 'SDXL_stylized_buildings.safetensors'). Para escenarios iso coherentes la LoRA iso fija el angulo 2:1. None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: rembg_model
@@ -106,7 +106,7 @@ cada una y monta los PNG con `comfyui_build_grid`.
etéreas/translúcidas que quieras conservar, pon `transparent=False` y recorta aparte.
- **`view` fija la perspectiva del mapa**: `isometric` (default) para mapas iso 2:1;
`side`/`front` para plataformas; `top-down` para cenital; `3/4` para vista de ¾. Para
iso estricto, añade la LoRA iso (`lora="isometric_game_assets_sd15.safetensors"`), que
iso estricto, añade la LoRA iso (`lora="SD15_isometric_game_assets.safetensors"`), que
fija mejor el ángulo 2:1 que solo el prompt.
- **Coherencia del set = mismos parámetros**: si cambias `view`/`style`/`checkpoint`/`lora`/
`seed` entre edificios, el escenario deja de combinar. Fija esos y varía solo `structure`.
@@ -149,7 +149,7 @@ def comfyui_build_structure_workflow(
seed: semilla del KSampler. Misma seed + misma structure/view/style -> mismo
edificio. keyword-only.
lora: LoRA de estilo/isometrica opcional en models/loras (ej.
'isometric_game_assets_sd15.safetensors', 'stylized_buildings_xl.safetensors').
'SD15_isometric_game_assets.safetensors', 'SDXL_stylized_buildings.safetensors').
Para escenarios isometricos coherentes, la LoRA iso ayuda a fijar el angulo
2:1. None = sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
@@ -123,12 +123,12 @@ def test_edge_building_not_small_prop():
def test_edge_lora_reflected():
wf = comfyui_build_structure_workflow(
"wizard tower",
lora="isometric_game_assets_sd15.safetensors",
lora="SD15_isometric_game_assets.safetensors",
lora_strength=0.9,
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "isometric_game_assets_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_isometric_game_assets.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.9
@@ -148,9 +148,9 @@ def test_error_empty_structure():
def test_determinism():
a = comfyui_build_structure_workflow(
"wizard tower", lora="stylized_buildings_xl.safetensors", seed=7
"wizard tower", lora="SDXL_stylized_buildings.safetensors", seed=7
)
b = comfyui_build_structure_workflow(
"wizard tower", lora="stylized_buildings_xl.safetensors", seed=7
"wizard tower", lora="SDXL_stylized_buildings.safetensors", seed=7
)
assert a == b
@@ -121,11 +121,11 @@ def test_edge_negative_allows_text():
def test_edge_lora_injected():
"""lora -> LoraLoader presente con la fuerza dada."""
wf = comfyui_build_title_lettering_workflow(
"EMPIRE", lora="logo_style_sd15.safetensors", lora_strength=0.7
"EMPIRE", lora="SD15_logo_style.safetensors", lora_strength=0.7
)
loras = [n for n in wf.values() if n["class_type"] == "LoraLoader"]
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "logo_style_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_logo_style.safetensors"
assert loras[0]["inputs"]["strength_model"] == pytest.approx(0.7)
@@ -30,7 +30,7 @@ params:
- name: seed
desc: "Semilla del KSampler. Misma seed + misma subject/style -> misma figura; variar solo `direction` da las vistas coherentes de movimiento. keyword-only."
- name: lora
desc: "LoRA de estilo/top-down opcional en models/loras (ej. 'topdown_rpg_sd15.safetensors'). None = sin LoRA. keyword-only."
desc: "LoRA de estilo/top-down opcional en models/loras (ej. 'SD15_topdown_rpg.safetensors'). None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: rembg_model
@@ -154,7 +154,7 @@ def comfyui_build_topdown_sprite_workflow(
figura; variar solo `direction` da las vistas coherentes de movimiento.
keyword-only.
lora: LoRA de estilo/top-down opcional en models/loras (ej.
'topdown_rpg_sd15.safetensors'). None = sin LoRA. keyword-only.
'SD15_topdown_rpg.safetensors'). None = sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
rembg_model: modelo Rembg ('u2net' general, 'isnet-anime' para anime). Solo
@@ -111,11 +111,11 @@ def test_edge_style_in_prompt():
def test_edge_lora_reflected():
wf = comfyui_build_topdown_sprite_workflow(
"a knight character", lora="topdown_rpg_sd15.safetensors", lora_strength=0.9
"a knight character", lora="SD15_topdown_rpg.safetensors", lora_strength=0.9
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "topdown_rpg_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_topdown_rpg.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.9
@@ -129,9 +129,9 @@ def test_error_empty_subject():
def test_determinism():
a = comfyui_build_topdown_sprite_workflow(
"a knight character", direction="east", lora="topdown_rpg_sd15.safetensors", seed=5
"a knight character", direction="east", lora="SD15_topdown_rpg.safetensors", seed=5
)
b = comfyui_build_topdown_sprite_workflow(
"a knight character", direction="east", lora="topdown_rpg_sd15.safetensors", seed=5
"a knight character", direction="east", lora="SD15_topdown_rpg.safetensors", seed=5
)
assert a == b
@@ -30,7 +30,7 @@ params:
- name: seed
desc: "Semilla del KSampler. Misma seed + mismo hazard/view/style -> misma trampa. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'pixel_art_sd15.safetensors', 'stylized_game_assets_xl.safetensors'). None = sin LoRA. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_pixel_art.safetensors', 'SDXL_stylized_game_assets.safetensors'). None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: rembg_model
@@ -151,7 +151,7 @@ def comfyui_build_trap_hazard_workflow(
seed: semilla del KSampler. Misma seed + mismo hazard/view/style -> misma trampa.
keyword-only.
lora: LoRA de estilo opcional en models/loras (ej.
'pixel_art_sd15.safetensors', 'stylized_game_assets_xl.safetensors'). None =
'SD15_pixel_art.safetensors', 'SDXL_stylized_game_assets.safetensors'). None =
sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
@@ -118,11 +118,11 @@ def test_edge_negative_rejects_living_character():
def test_edge_lora_injected():
wf = comfyui_build_trap_hazard_workflow(
"bear trap", lora="pixel_art_sd15.safetensors", lora_strength=0.8
"bear trap", lora="SD15_pixel_art.safetensors", lora_strength=0.8
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "pixel_art_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_pixel_art.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.8
@@ -28,7 +28,7 @@ params:
- name: seed
desc: "Semilla del KSampler. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'detail_tweaker_sd15.safetensors'). None = sin LoRA. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_detail_tweaker.safetensors'). None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: rembg_model
@@ -69,7 +69,7 @@ wf = comfyui_build_ui_hud_workflow(
# HUD coherente: misma firma de estilo para cada pieza de la interfaz.
# for el in ["wooden button", "ornate frame", "mana orb", "menu cursor"]:
# wf = comfyui_build_ui_hud_workflow(el, ui_style="fantasy game UI",
# lora="detail_tweaker_sd15.safetensors", seed=42)
# lora="SD15_detail_tweaker.safetensors", seed=42)
# comfyui_submit_workflow(wf) # -> comfyui_wait_result -> comfyui_fetch_output_image
# Atlas del HUD: montar los PNG resultantes con comfyui_build_grid.
```
@@ -129,7 +129,7 @@ def comfyui_build_ui_hud_workflow(
recortable luego por el caller/pipeline. keyword-only.
seed: semilla del KSampler. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej.
'detail_tweaker_sd15.safetensors'). None = sin LoRA. keyword-only.
'SD15_detail_tweaker.safetensors'). None = sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
rembg_model: modelo Rembg ('u2net' general, 'isnet-anime' para anime).
@@ -74,11 +74,11 @@ def test_edge_ui_style_in_prompt():
def test_edge_lora_reflected():
wf = comfyui_build_ui_hud_workflow(
"magic icon", lora="detail_tweaker_sd15.safetensors", lora_strength=0.9
"magic icon", lora="SD15_detail_tweaker.safetensors", lora_strength=0.9
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "detail_tweaker_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_detail_tweaker.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.9
@@ -91,6 +91,6 @@ def test_error_empty_element():
def test_determinism():
a = comfyui_build_ui_hud_workflow("menu cursor", lora="detail_tweaker_sd15.safetensors", seed=7)
b = comfyui_build_ui_hud_workflow("menu cursor", lora="detail_tweaker_sd15.safetensors", seed=7)
a = comfyui_build_ui_hud_workflow("menu cursor", lora="SD15_detail_tweaker.safetensors", seed=7)
b = comfyui_build_ui_hud_workflow("menu cursor", lora="SD15_detail_tweaker.safetensors", seed=7)
assert a == b
@@ -30,7 +30,7 @@ params:
- name: seed
desc: "Semilla del KSampler. Misma seed + mismo vehicle/view/style -> mismo vehiculo. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'vehicle_concept_sd15.safetensors', 'mecha_xl.safetensors'). None = sin LoRA. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_vehicle_concept.safetensors', 'SDXL_mecha.safetensors'). None = sin LoRA. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: rembg_model
@@ -153,7 +153,7 @@ def comfyui_build_vehicle_mount_workflow(
seed: semilla del KSampler. Misma seed + mismo vehicle/view/style -> mismo
vehiculo. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej.
'vehicle_concept_sd15.safetensors', 'mecha_xl.safetensors'). None = sin
'SD15_vehicle_concept.safetensors', 'SDXL_mecha.safetensors'). None = sin
LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
@@ -98,11 +98,11 @@ def test_edge_style_in_prompt():
def test_edge_lora_reflected():
wf = comfyui_build_vehicle_mount_workflow(
"mecha walker", lora="mecha_xl.safetensors", lora_strength=0.9
"mecha walker", lora="SDXL_mecha.safetensors", lora_strength=0.9
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "mecha_xl.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SDXL_mecha.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.9
@@ -116,9 +116,9 @@ def test_error_empty_vehicle():
def test_determinism():
a = comfyui_build_vehicle_mount_workflow(
"griffon mount", view="iso", lora="vehicle_concept_sd15.safetensors", seed=7
"griffon mount", view="iso", lora="SD15_vehicle_concept.safetensors", seed=7
)
b = comfyui_build_vehicle_mount_workflow(
"griffon mount", view="iso", lora="vehicle_concept_sd15.safetensors", seed=7
"griffon mount", view="iso", lora="SD15_vehicle_concept.safetensors", seed=7
)
assert a == b
@@ -47,7 +47,7 @@ def test_edge_open_loop():
def test_edge_lora_fx():
wf = comfyui_build_vfx_spritesheet_workflow(
"explosion", lora="detail_tweaker_sd15.safetensors", lora_strength=1.0
"explosion", lora="SD15_detail_tweaker.safetensors", lora_strength=1.0
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
@@ -119,11 +119,11 @@ def test_edge_negative_isolates_layer():
def test_edge_lora_reflected():
wf = comfyui_build_weather_overlay_workflow(
"falling snow", lora="vfx_sd15.safetensors", lora_strength=0.8
"falling snow", lora="SD15_vfx.safetensors", lora_strength=0.8
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "vfx_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_vfx.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.8
@@ -137,9 +137,9 @@ def test_error_empty_weather():
def test_determinism():
a = comfyui_build_weather_overlay_workflow(
"heavy rain", on_black=True, lora="vfx_sd15.safetensors", seed=7
"heavy rain", on_black=True, lora="SD15_vfx.safetensors", seed=7
)
b = comfyui_build_weather_overlay_workflow(
"heavy rain", on_black=True, lora="vfx_sd15.safetensors", seed=7
"heavy rain", on_black=True, lora="SD15_vfx.safetensors", seed=7
)
assert a == b
@@ -30,7 +30,7 @@ params:
- name: seed
desc: "Semilla del KSampler (y de la pasada hires). Misma seed + misma region -> mismo mapa. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'cartography_sd15.safetensors', 'old_map_xl.safetensors'). None = sin LoRA. Encadena la identidad visual del mapa del juego. keyword-only."
desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_cartography.safetensors', 'SDXL_old_map.safetensors'). None = sin LoRA. Encadena la identidad visual del mapa del juego. keyword-only."
- name: lora_strength
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
- name: negative
@@ -116,7 +116,7 @@ def comfyui_build_world_map_workflow(
seed: semilla del KSampler (y de la pasada hires). Misma seed + misma region
-> mismo mapa. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej. 'cartography_sd15.safe-
tensors', 'old_map_xl.safetensors'). None = sin LoRA. Encadena la
tensors', 'SDXL_old_map.safetensors'). None = sin LoRA. Encadena la
identidad visual del mapa del juego. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
@@ -101,13 +101,13 @@ def test_edge_map_style_in_prompt():
def test_edge_lora_reflected():
wf = comfyui_build_world_map_workflow(
"an archipelago of volcanic islands",
lora="cartography_sd15.safetensors",
lora="SD15_cartography.safetensors",
lora_strength=0.9,
hires=False,
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "cartography_sd15.safetensors"
assert loras[0]["inputs"]["lora_name"] == "SD15_cartography.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.9
# Con lora el KSampler.model ya NO viene del checkpoint directo.
ksampler = _by_class(wf, "KSampler")[0]
@@ -48,8 +48,8 @@ base = comfyui_build_txt2img_workflow("dreamshaper_8.safetensors", "a hero, 3d r
mixed = comfyui_compose_capabilities(
base,
loras=[
{"name": "3d_render_redmond_sd15.safetensors", "strength_model": 0.9},
{"name": "detail_tweaker_sd15.safetensors", "strength_model": 0.5},
{"name": "SD15_3d_render_redmond.safetensors", "strength_model": 0.9},
{"name": "SD15_detail_tweaker.safetensors", "strength_model": 0.5},
],
facedetailer={"denoise": 0.45},
# controlnet=..., ipadapter=..., hires=... -> None = desactivadas
@@ -195,8 +195,8 @@ if __name__ == "__main__":
mixed = comfyui_compose_capabilities(
base,
loras=[
{"name": "3d_render_redmond_sd15.safetensors", "strength_model": 0.9},
{"name": "detail_tweaker_sd15.safetensors", "strength_model": 0.5},
{"name": "SD15_3d_render_redmond.safetensors", "strength_model": 0.9},
{"name": "SD15_detail_tweaker.safetensors", "strength_model": 0.5},
],
facedetailer={"denoise": 0.45},
)
@@ -19,7 +19,7 @@ params:
desc: "Identificador del estilo: 'gameboy', 'ghibli' o 'pixel-art-retro'. Insensible a mayusculas y a '_' vs '-'. Si es None o cadena vacia devuelve el catalogo {names, count} en vez de una receta (discovery)."
output: "Si name es None/'': dict {names: list[str], count: int} con el catalogo. Si name es valido: copia profunda de la receta del estilo con campos {name, subject_prefix, subject_suffix, style, negative, checkpoint, lora, lora_strength, size, transparent, post, notes}. Es una COPIA — mutarla no afecta al registro interno."
tested: true
tests: ["golden: los 3 presets (gameboy/ghibli/pixel-art-retro) devuelven recetas con todos los campos requeridos, checkpoint .safetensors, size>0", "golden gameboy: lora None + post pixelize paleta game-boy 4 colores", "golden pixel-art-retro: lora pixel-art-xl SDXL + checkpoint juggernaut + size>=768 + post pixelize", "golden ghibli: degrada a watercolor_style_sd15 + sin post pixelize", "edge name None/'' -> catalogo de 3 nombres", "edge insensible a mayusculas y '_'/'-'", "edge devuelve copia profunda (mutar no afecta), error name desconocido -> ValueError listando disponibles"]
tests: ["golden: los 3 presets (gameboy/ghibli/pixel-art-retro) devuelven recetas con todos los campos requeridos, checkpoint .safetensors, size>0", "golden gameboy: lora None + post pixelize paleta game-boy 4 colores", "golden pixel-art-retro: lora SDXL_pixel-art SDXL + checkpoint juggernaut + size>=768 + post pixelize", "golden ghibli: degrada a SD15_watercolor_style + sin post pixelize", "edge name None/'' -> catalogo de 3 nombres", "edge insensible a mayusculas y '_'/'-'", "edge devuelve copia profunda (mutar no afecta), error name desconocido -> ValueError listando disponibles"]
test_file_path: "python/functions/ml/comfyui_get_gamedev_style_preset_test.py"
file_path: "python/functions/ml/comfyui_get_gamedev_style_preset.py"
---
@@ -64,11 +64,11 @@ para descubrir los estilos disponibles.
`comfyui_submit_workflow`/`comfyui_wait_result`/`comfyui_fetch_output_image` +
`comfyui_pixelize_image`.
- **El checkpoint y el LoRA deben casar de base**: `pixel-art-retro` usa el LoRA SDXL
`pixel-art-xl` -> exige `checkpoint` SDXL (`juggernaut_xl_v11`) y `size` 768. Aplicar un
`SDXL_pixel-art` -> exige `checkpoint` SDXL (`juggernaut_xl_v11`) y `size` 768. Aplicar un
LoRA SDXL sobre un checkpoint SD1.5 da basura. Si anades un estilo con LoRA nuevo,
descargalo a `models/loras` y verifica su base antes.
- **ghibli no usa un LoRA Ghibli dedicado**: no hay ninguno instalado y no se descargo
ninguno gated/de pago. Degrada a `watercolor_style_sd15` (gratis, ya instalado) +
ninguno gated/de pago. Degrada a `SD15_watercolor_style` (gratis, ya instalado) +
prompt Ghibli para el look pintado/acuarela. Un LoRA Ghibli especifico de Civitai
mejoraria el parecido facial — pendiente humano.
- **gameboy se resuelve por POST, no por LoRA**: sin LoRA; el look DMG de 4 tonos verde lo
@@ -76,8 +76,8 @@ para descubrir los estilos disponibles.
aplicar el post (`preset["post"]["pixelize"]`) o solo vera un sprite monocromo-ish sin
la paleta sellada.
- **Modelos verificados en el servidor** (8GB lowvram, modelos en `/mnt/2tb`): si cambias
de PC valida que `dreamshaper_8`, `juggernaut_xl_v11`, `pixel-art-xl` y
`watercolor_style_sd15` existan (`GET /object_info/CheckpointLoaderSimple` y `/LoraLoader`).
de PC valida que `dreamshaper_8`, `juggernaut_xl_v11`, `SDXL_pixel-art` y
`SD15_watercolor_style` existan (`GET /object_info/CheckpointLoaderSimple` y `/LoraLoader`).
## Capability growth log
@@ -45,7 +45,7 @@ import copy
# Modelos verificados presentes en el servidor (8GB lowvram, modelos en /mnt/2tb):
# checkpoints: dreamshaper_8 (SD1.5), juggernaut_xl_v11 (SDXL)
# loras: pixel-art-xl (SDXL), watercolor_style_sd15 (SD1.5), ...
# loras: SDXL_pixel-art (SDXL), SD15_watercolor_style (SD1.5), ...
# Si se anade un estilo con un LoRA nuevo, descargar antes a models/loras y verificar
# que el `checkpoint` casa con su base (un LoRA SDXL exige checkpoint SDXL).
_PRESETS: dict[str, dict] = {
@@ -82,7 +82,7 @@ _PRESETS: dict[str, dict] = {
},
# Studio Ghibli: anime pintado a mano, colores suaves, fondos acuarela. No hay un LoRA
# Ghibli dedicado instalado (no se descargo ninguno gated/de pago); el look pintado se
# logra con watercolor_style_sd15 (LoRA gratis ya instalado) a fuerza media + prompt
# logra con SD15_watercolor_style (LoRA gratis ya instalado) a fuerza media + prompt
# Ghibli. Sin post-proceso (es ilustracion pintada, no pixelart).
"ghibli": {
"name": "ghibli",
@@ -91,20 +91,20 @@ _PRESETS: dict[str, dict] = {
"style": "Studio Ghibli style, hand-painted anime, soft colors, watercolor background, whimsical, gentle, detailed illustration",
"negative": "photo, photorealistic, 3d render, harsh shadows, pixel art, lowres, deformed, text, watermark, signature",
"checkpoint": "dreamshaper_8.safetensors",
"lora": "watercolor_style_sd15.safetensors",
"lora": "SD15_watercolor_style.safetensors",
"lora_strength": 0.7,
"size": 512,
"transparent": False,
"post": {},
"notes": (
"No hay LoRA Ghibli dedicado instalado y no se descargo ninguno gated/de pago "
"(error-path: degradar a LoRA gratis + prompt). watercolor_style_sd15 (gratis, ya "
"(error-path: degradar a LoRA gratis + prompt). SD15_watercolor_style (gratis, ya "
"instalado) a strength 0.7 + prompt Ghibli da el look pintado/acuarela. Un LoRA "
"Ghibli especifico de Civitai mejoraria el parecido facial — pendiente humano si "
"se quiere. transparent=False para conservar el fondo acuarela."
),
},
# Pixel-art retro 16-bit (SNES/Genesis): reutiliza el LoRA pixel-art-xl ya instalado
# Pixel-art retro 16-bit (SNES/Genesis): reutiliza el LoRA SDXL_pixel-art ya instalado
# (es SDXL -> exige checkpoint SDXL juggernaut_xl_v11 y resolucion mayor). El post
# pixelize a 16 colores sella los pixeles duros (el LoRA da el estilo, el post el grid).
"pixel-art-retro": {
@@ -114,7 +114,7 @@ _PRESETS: dict[str, dict] = {
"style": "16-bit pixel art, SNES JRPG sprite, retro game, limited palette, clean outline, flat colors",
"negative": "blurry, smooth, photorealistic, 3d render, realistic, antialiasing, soft, gradient, noise",
"checkpoint": "juggernaut_xl_v11.safetensors",
"lora": "pixel-art-xl.safetensors",
"lora": "SDXL_pixel-art.safetensors",
"lora_strength": 1.0,
"size": 768,
"transparent": False,
@@ -128,8 +128,8 @@ _PRESETS: dict[str, dict] = {
}
},
"notes": (
"Reutiliza pixel-art-xl.safetensors (SDXL, ya instalado) -> requiere checkpoint "
"SDXL juggernaut_xl_v11 y size 768 (a 512 SDXL+pixel-art-xl pierde calidad). El "
"Reutiliza SDXL_pixel-art.safetensors (SDXL, ya instalado) -> requiere checkpoint "
"SDXL juggernaut_xl_v11 y size 768 (a 512 SDXL+SDXL_pixel-art pierde calidad). El "
"post pixelize a 16 colores (paleta auto MEDIANCUT) da el grid duro 16-bit. OOM en "
"8GB -> bajar size a 512 (NO matar procesos). El LoRA da el estilo; el post el grid."
),
@@ -176,7 +176,7 @@ _PRESETS: dict[str, dict] = {
"transparent": True,
"post": {},
"notes": (
"Sin LoRA a propósito: 3d_render_redmond_sd15 (instalado) empuja a render "
"Sin LoRA a propósito: SD15_3d_render_redmond (instalado) empuja a render "
"fotorrealista, lo OPUESTO a low-poly. El look faceteado/flat lo da el prompt "
"(low poly, faceted, flat shading, PS1) sobre dreamshaper_8 (SD1.5). Sin post: el "
"flat shading es limpio de por sí, no necesita pixelize. transparent=True porque un "
@@ -185,7 +185,7 @@ _PRESETS: dict[str, dict] = {
},
# Cartoon cel-shaded: dibujos animados / anime con sombreado plano por celdas, líneas
# negras gruesas, colores saturados y planos (look toon/Borderlands/Zelda Wind Waker).
# Usa anime_style_box_sd15 (LoRA gratis ya instalado) a fuerza media + prompt cel-shaded.
# Usa SD15_anime_style_box (LoRA gratis ya instalado) a fuerza media + prompt cel-shaded.
# Sin post (es ilustración vectorial limpia, no pixelart).
"cartoon-cel-shaded": {
"name": "cartoon-cel-shaded",
@@ -194,13 +194,13 @@ _PRESETS: dict[str, dict] = {
"style": "cartoon cel-shaded art, bold black outlines, flat color fills, hard cel shadows, vibrant saturated colors, clean vector look, anime toon shading, comic style",
"negative": "photorealistic, soft shading, gradient, realistic texture, painterly, blurry, noisy, pixel art, lowres, grainy, deformed, text, watermark, signature",
"checkpoint": "dreamshaper_8.safetensors",
"lora": "anime_style_box_sd15.safetensors",
"lora": "SD15_anime_style_box.safetensors",
"lora_strength": 0.7,
"size": 512,
"transparent": True,
"post": {},
"notes": (
"anime_style_box_sd15.safetensors (gratis, ya instalado en /mnt/2tb) a strength 0.7 "
"SD15_anime_style_box.safetensors (gratis, ya instalado en /mnt/2tb) a strength 0.7 "
"empuja el toon/anime; el prompt sella el cel-shading (outlines negros gruesos, "
"sombras duras por celdas, colores planos saturados). SD1.5 (dreamshaper_8). Sin "
"post: el look vectorial limpio no necesita pixelize. transparent=True para recortar "
@@ -48,8 +48,8 @@ def test_golden_gameboy_recipe():
def test_golden_pixel_retro_uses_sdxl_lora():
r = comfyui_get_gamedev_style_preset("pixel-art-retro")
# Reutiliza pixel-art-xl (SDXL) -> checkpoint SDXL + size mayor + pixelize.
assert r["lora"] == "pixel-art-xl.safetensors"
# Reutiliza SDXL_pixel-art (SDXL) -> checkpoint SDXL + size mayor + pixelize.
assert r["lora"] == "SDXL_pixel-art.safetensors"
assert r["checkpoint"] == "juggernaut_xl_v11.safetensors"
assert r["size"] >= 768
assert "pixelize" in r["post"]
@@ -58,7 +58,7 @@ def test_golden_pixel_retro_uses_sdxl_lora():
def test_golden_ghibli_degrades_to_watercolor_lora():
r = comfyui_get_gamedev_style_preset("ghibli")
# Sin LoRA Ghibli dedicado -> watercolor instalado + prompt; sin post pixelize.
assert r["lora"] == "watercolor_style_sd15.safetensors"
assert r["lora"] == "SD15_watercolor_style.safetensors"
assert r["post"] == {}
assert "ghibli" in r["style"].lower()
@@ -60,7 +60,7 @@ from ml.comfyui_inject_multi_lora import comfyui_inject_multi_lora
from ml.comfyui_inject_ipadapter import comfyui_inject_ipadapter
base = comfyui_build_txt2img_workflow("dreamshaper_8.safetensors", "portrait of a knight")
wf = comfyui_inject_multi_lora(base, [{"name": "detail_tweaker_sd15.safetensors", "strength_model": 0.5}])
wf = comfyui_inject_multi_lora(base, [{"name": "SD15_detail_tweaker.safetensors", "strength_model": 0.5}])
wf = comfyui_inject_ipadapter(wf, "hero_face.png", mode="faceid", weight=0.9)
# KSampler.model viene de IPAdapterFaceID(model=ultimo LoraLoader, image=hero_face)
```
@@ -36,8 +36,8 @@ from ml.comfyui_inject_multi_lora import comfyui_inject_multi_lora
base = comfyui_build_txt2img_workflow("dreamshaper_8.safetensors", "a robot, 3D Render Style")
wf = comfyui_inject_multi_lora(base, [
{"name": "3d_render_redmond_sd15.safetensors", "strength_model": 0.9},
{"name": "detail_tweaker_sd15.safetensors", "strength_model": 0.5, "strength_clip": 0.5},
{"name": "SD15_3d_render_redmond.safetensors", "strength_model": 0.9},
{"name": "SD15_detail_tweaker.safetensors", "strength_model": 0.5, "strength_clip": 0.5},
])
# Cadena: checkpoint -> 3d_render (0.9) -> detail_tweaker (0.5/0.5) -> KSampler/CLIPTextEncode
```
@@ -79,8 +79,8 @@ if __name__ == "__main__":
wf = comfyui_inject_multi_lora(
base,
[
{"name": "3d_render_redmond_sd15.safetensors", "strength_model": 0.9},
{"name": "detail_tweaker_sd15.safetensors", "strength_model": 0.5, "strength_clip": 0.5},
{"name": "SD15_3d_render_redmond.safetensors", "strength_model": 0.9},
{"name": "SD15_detail_tweaker.safetensors", "strength_model": 0.5, "strength_clip": 0.5},
],
)
print(json.dumps(wf, indent=2))
@@ -43,7 +43,7 @@ import sys, os
sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions"))
from ml.comfyui_pixelize_image import comfyui_pixelize_image
# Crudo SDXL+pixel-art-xl 1024x1024 -> pixelart 16 colores, grid de 128
# Crudo SDXL+SDXL_pixel-art 1024x1024 -> pixelart 16 colores, grid de 128
res = comfyui_pixelize_image(
os.path.expanduser("~/ComfyUI/output/pixelart_00001_.png"),
"/tmp/hero_pixel.png",
@@ -58,7 +58,7 @@ comfyui_pixelize_image("/tmp/hero_pixel.png", "/tmp/hero_gb.png",
## Cuando usarla
Fase 2 del pipeline pixelart: tras generar el crudo (SDXL + LoRA `pixel-art-xl`),
Fase 2 del pipeline pixelart: tras generar el crudo (SDXL + LoRA `SDXL_pixel-art`),
para colapsar el grid borroso a pixeles duros y limitar la paleta. Tambien sirve
para "pixelizar" cualquier imagen (sprite, render, foto) a estetica retro sin
tocar la GPU. Para llevar el resultado a Godot con filtro Nearest:
@@ -5,7 +5,7 @@ verdad: downscale nearest-neighbor por factor (colapsa cada bloque borroso a un
pixel duro) -> cuantizacion a N colores o a una paleta fija (NES / Game Boy /
PICO-8) -> opcional re-upscale nearest conservando los pixeles duros.
Es la Fase 2 del pipeline pixelart (la Fase 1, generar con SDXL + pixel-art-xl
Es la Fase 2 del pipeline pixelart (la Fase 1, generar con SDXL + SDXL_pixel-art
LoRA, vive en otra funcion). Determinista y CPU-only: el nucleo `_pixelize_pil`
es puro (PIL), no toca la GPU ni la red. La funcion publica es impura solo por la
lectura/escritura de disco (mismo patron que comfyui_build_grid).
@@ -40,8 +40,8 @@ def test_solo_loras_encadena():
out = comfyui_compose_capabilities(
base,
loras=[
{"name": "3d_render_redmond_sd15.safetensors", "strength_model": 0.9},
{"name": "detail_tweaker_sd15.safetensors", "strength_model": 0.5},
{"name": "SD15_3d_render_redmond.safetensors", "strength_model": 0.9},
{"name": "SD15_detail_tweaker.safetensors", "strength_model": 0.5},
],
)
assert_api_format(out)
@@ -54,7 +54,7 @@ def test_solo_loras_encadena():
def test_loras_mas_facedetailer():
out = comfyui_compose_capabilities(
_base(),
loras=[{"name": "detail_tweaker_sd15.safetensors", "strength_model": 0.5}],
loras=[{"name": "SD15_detail_tweaker.safetensors", "strength_model": 0.5}],
facedetailer={"denoise": 0.45},
)
assert_api_format(out)
@@ -65,8 +65,8 @@ res = comfyui_generate_mixed_oneshot(
checkpoint="dreamshaper_8.safetensors",
capabilities={
"loras": [
{"name": "3d_render_redmond_sd15.safetensors", "strength_model": 0.9},
{"name": "detail_tweaker_sd15.safetensors", "strength_model": 0.5},
{"name": "SD15_3d_render_redmond.safetensors", "strength_model": 0.9},
{"name": "SD15_detail_tweaker.safetensors", "strength_model": 0.5},
],
"facedetailer": {"denoise": 0.45},
# "ipadapter": {"ref_image": "face.png", "mode": "faceid"}, # activar/desactivar
@@ -210,8 +210,8 @@ if __name__ == "__main__":
checkpoint="dreamshaper_8.safetensors",
capabilities={
"loras": [
{"name": "3d_render_redmond_sd15.safetensors", "strength_model": 0.9},
{"name": "detail_tweaker_sd15.safetensors", "strength_model": 0.5},
{"name": "SD15_3d_render_redmond.safetensors", "strength_model": 0.9},
{"name": "SD15_detail_tweaker.safetensors", "strength_model": 0.5},
],
"facedetailer": {"denoise": 0.45},
},
@@ -66,10 +66,10 @@ def test_build_only_preset_drives_workflow():
def test_build_only_lora_preset_injects_lora():
# cartoon-cel-shaded usa anime_style_box_sd15 -> el workflow debe inyectar el LoRA
# cartoon-cel-shaded usa SD15_anime_style_box -> el workflow debe inyectar el LoRA
built = styled_asset_build_only("enemy_creature", "goblin", "cartoon-cel-shaded", seed=1)
blob = json.dumps(built["workflow"])
assert "anime_style_box_sd15.safetensors" in blob
assert "SD15_anime_style_box.safetensors" in blob
assert any(n.get("class_type") == "LoraLoader" for n in built["workflow"].values())
@@ -114,9 +114,22 @@ def _server_models(server):
def _norm(name):
"""Normaliza un nombre de modelo para comparar (sin ext, sin separadores)."""
"""Normaliza un nombre de modelo para comparar (sin ext, sin separadores).
Ignora el token de arquitectura (SD15/SDXL/XL/FLUX) lo lleve como prefijo
(convencion nueva: `SDXL_detail_tweaker`) o como sufijo (recetas civitai:
`Detail-Tweaker XL`), para que el match sea robusto al reordenado del token.
"""
base = os.path.splitext(str(name))[0].lower()
return re.sub(r"[^a-z0-9]", "", base)
s = re.sub(r"[^a-z0-9]", "", base)
for tok in ("sdxl", "sd15", "flux"):
if s.startswith(tok):
s = s[len(tok):]
if s.endswith(tok):
s = s[: -len(tok)]
if s.endswith("xl"): # 'xl' suelto como sufijo de arquitectura
s = s[:-2]
return s
def _find_installed(name, installed):
@@ -45,9 +45,9 @@ def test_pick_checkpoint_familia_y_exacto():
def test_find_installed_match_normalizado():
installed = ["detail_tweaker_xl.safetensors", "watercolor_style_sd15.safetensors"]
installed = ["SDXL_detail_tweaker.safetensors", "SD15_watercolor_style.safetensors"]
# match normalizado (ignora separadores/ext/case)
assert _find_installed("Detail-Tweaker XL", installed) == "detail_tweaker_xl.safetensors"
assert _find_installed("Detail-Tweaker XL", installed) == "SDXL_detail_tweaker.safetensors"
# no instalado -> None
assert _find_installed("LoRA Inexistente 9000", installed) is None