"""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))