0eefb7cfcd
Calidad por ESTILO en vez de por tipo: un dev fija el look del juego una vez y todos los assets salen coherentes. Diseño (A) datos puros + helper, no pipeline monolítico (issue 0087, crecer por composición). - comfyui_get_gamedev_style_preset(name=None): recetas curadas o catálogo. gameboy (sin LoRA, post pixelize paleta game-boy 4 tonos), ghibli (degrada a watercolor_style_sd15 gratis instalado, sin LoRA Ghibli gated), pixel-art-retro (reutiliza pixel-art-xl SDXL + juggernaut + post pixelize 16 colores). Extensible. - comfyui_apply_style_preset(preset, subject): traduce a kwargs **spread-ables para cualquier builder de sujeto + size/transparent/post. Pura, no muta. - 16 tests offline verdes. Validado e2e GPU: mismo 'knight character' en 3 estilos visiblemente distintos (4 vs 78552 vs 16 colores). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
136 lines
5.2 KiB
Python
136 lines
5.2 KiB
Python
"""comfyui_apply_style_preset — traduce un style preset gamedev a los kwargs de un builder.
|
|
|
|
Toma una receta de estilo (de `comfyui_get_gamedev_style_preset`) y un `subject` del usuario
|
|
y produce, de forma PURA, lo que un builder de sujeto del grupo `gamedev-2d` necesita:
|
|
|
|
- el `subject` combinado con el prefijo/sufijo del estilo,
|
|
- los kwargs comunes a todos los builders de sujeto (`style`, `checkpoint`, `lora`,
|
|
`lora_strength`, `negative`) listos para hacer `**spread`,
|
|
- la resolucion y el recorte recomendados (`size`, `transparent`),
|
|
- y la spec de post-proceso (`post`, p.ej. pixelize) que el caller aplica al PNG resultante.
|
|
|
|
Asi el mismo estilo se aplica a CUALQUIER builder de sujeto (item_icon, enemy_creature,
|
|
prop_object, structure, ...) sin acoplar este helper a sus firmas, y el preset elige el
|
|
checkpoint/lora coherentes ANTES de construir el grafo.
|
|
|
|
Patron de uso:
|
|
|
|
preset = comfyui_get_gamedev_style_preset("gameboy")
|
|
ap = comfyui_apply_style_preset(preset, "knight character")
|
|
wf = comfyui_build_enemy_creature_workflow(
|
|
ap["subject"], size=ap["size"], transparent=ap["transparent"], **ap["builder_kwargs"]
|
|
)
|
|
# tras submit/wait/fetch, si ap["post"].get("pixelize"):
|
|
# comfyui_pixelize_image(raw_png, dst_png, **ap["post"]["pixelize"])
|
|
|
|
Funcion pura: sin red, sin I/O. No muta el preset de entrada (copia lo que devuelve).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
|
|
# Claves obligatorias de una receta valida (las que produce comfyui_get_gamedev_style_preset).
|
|
_REQUIRED = (
|
|
"name",
|
|
"subject_prefix",
|
|
"subject_suffix",
|
|
"style",
|
|
"negative",
|
|
"checkpoint",
|
|
"lora",
|
|
"lora_strength",
|
|
"size",
|
|
"transparent",
|
|
"post",
|
|
)
|
|
|
|
|
|
def _merge_negative(a: str, b: str) -> str:
|
|
"""Une dos negativos por comas sin duplicar terminos ni dejar comas sueltas."""
|
|
seen: list[str] = []
|
|
for chunk in (a or "", b or ""):
|
|
for term in chunk.split(","):
|
|
t = term.strip()
|
|
if t and t.lower() not in {s.lower() for s in seen}:
|
|
seen.append(t)
|
|
return ", ".join(seen)
|
|
|
|
|
|
def comfyui_apply_style_preset(
|
|
preset: dict,
|
|
subject: str,
|
|
*,
|
|
style: str | None = None,
|
|
negative: str | None = None,
|
|
) -> dict:
|
|
"""Aplica un style preset a un subject y devuelve los kwargs listos para un builder.
|
|
|
|
Args:
|
|
preset: receta de estilo (dict de comfyui_get_gamedev_style_preset). Debe traer
|
|
los campos del preset; se valida que esten presentes. No se muta.
|
|
subject: lo que el usuario quiere generar (ej. "knight character", "health potion").
|
|
Se combina con el prefijo/sufijo del estilo. No puede estar vacio.
|
|
style: si se pasa, sustituye al `style` del preset (override puntual). None usa el
|
|
del preset. keyword-only.
|
|
negative: negativo extra del caller; se MERGEA con el negativo del estilo (no lo
|
|
reemplaza). None = solo el del estilo. keyword-only.
|
|
|
|
Returns:
|
|
dict con:
|
|
- "name" (str): nombre del estilo aplicado.
|
|
- "subject" (str): subject combinado con prefijo/sufijo del estilo.
|
|
- "builder_kwargs" (dict): {style, checkpoint, lora, lora_strength, negative} —
|
|
los kwargs comunes a los builders de sujeto, para hacer **spread.
|
|
- "size" (int): resolucion recomendada por el estilo.
|
|
- "transparent" (bool): recorte a alpha recomendado por el estilo.
|
|
- "post" (dict): post-proceso CPU a aplicar al PNG ({"pixelize": {...}} o {}).
|
|
|
|
Raises:
|
|
ValueError: si subject esta vacio o el preset no trae los campos requeridos.
|
|
"""
|
|
if not subject or not subject.strip():
|
|
raise ValueError("comfyui_apply_style_preset: 'subject' no puede estar vacio")
|
|
if not isinstance(preset, dict):
|
|
raise ValueError("comfyui_apply_style_preset: 'preset' debe ser un dict")
|
|
missing = [k for k in _REQUIRED if k not in preset]
|
|
if missing:
|
|
raise ValueError(
|
|
f"comfyui_apply_style_preset: preset incompleto, faltan campos {missing}. "
|
|
"Usa comfyui_get_gamedev_style_preset para obtener una receta valida."
|
|
)
|
|
|
|
subject_full = (
|
|
f"{preset['subject_prefix']}{subject.strip()}{preset['subject_suffix']}"
|
|
).strip().strip(",").strip()
|
|
|
|
style_final = style if style is not None else preset["style"]
|
|
neg_final = _merge_negative(preset["negative"], negative or "")
|
|
|
|
return {
|
|
"name": preset["name"],
|
|
"subject": subject_full,
|
|
"builder_kwargs": {
|
|
"style": style_final,
|
|
"checkpoint": preset["checkpoint"],
|
|
"lora": preset["lora"],
|
|
"lora_strength": preset["lora_strength"],
|
|
"negative": neg_final,
|
|
},
|
|
"size": preset["size"],
|
|
"transparent": preset["transparent"],
|
|
"post": copy.deepcopy(preset["post"]),
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
from ml.comfyui_get_gamedev_style_preset import comfyui_get_gamedev_style_preset
|
|
|
|
p = comfyui_get_gamedev_style_preset("pixel-art-retro")
|
|
ap = comfyui_apply_style_preset(p, "knight character")
|
|
print(json.dumps(ap, indent=2, ensure_ascii=False))
|