MOTORES: pastillas configurables y destino (salida o capa)

Las pestanas de capa de la barra superior no valian: mezclaban navegar por el
panel con elegir sobre que se trabaja, y sacaban del panel de directo. Se
quitan y todo pasa a MOTORES, con dos decisiones separadas:

  DONDE  -> fila 'Donde se ve': SALIDA o una capa del mapping (+ crea una).
  QUE    -> las pastillas: combinaciones guardadas por el usuario.

Pinchar una pastilla la manda al destino elegido: con SALIDA cambia el motor
global, con una capa cambia SOLO esa capa y el resto sigue igual. Con una capa
elegida sus ajustes salen en MOTORES (la tarjeta de propiedades se mueve, no
se duplica: dos formularios para lo mismo se desincronizan a la primera).

Las pastillas son configurables: motor + preset + ajustes y su nombre, con
'Configurar pastillas'. Persisten en data/slots.json (gitignorado, es de cada
instalacion). De fabrica vienen los cinco motores de siempre, asi que quien
actualice se encuentra lo mismo que tenia. Hydra, Mezcla y projectM nativo
solo pueden ir a la salida, y al intentar mandarlos a una capa se dice.

Comprobado en Chromium por CDP, no con --dump-dom: ese fotografia el DOM al
cargar, antes de que llegue el estado por socket, y daba falsos "esta vacio".
Verificado que disparar sobre una capa deja el motor global intacto, que la
tarjeta de propiedades va y vuelve de su sitio, y que las pastillas nuevas se
guardan en disco.

De paso, dos fallos que aparecieron al probarlo:
- Restaurar la vista guardada corria al principio del fichero y llamaba a
  codigo que usa variables declaradas con let mas abajo: ReferenceError que
  se llevaba por delante el resto del script (socket incluido) y dejaba el
  panel en blanco. Ahora se restaura al final.
- #card-destino no estaba en la lista de 'order' del CSS, asi que caia a 0 y
  se colaba encima de las pastillas pese a ir despues en el HTML.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hacklab 2026-08-02 16:47:01 +02:00
parent f017246f0e
commit 61543e1987
8 changed files with 602 additions and 146 deletions

View file

@ -79,6 +79,19 @@ socketio = SocketIO(app, async_mode="threading", cors_allowed_origins=None)
HYDRA_SKETCHES = load_json(DATA / "hydra-sketches.json", {})
SHADERS = load_json(DATA / "shaders.json", {})
# Pastillas: combinaciones guardadas (motor + preset + ajustes) que el panel
# dispara de un toque sobre el destino elegido (la salida o una capa del
# mapping). Las de fabrica son los cinco motores de siempre, para que quien
# actualice se encuentre lo mismo que tenia.
SLOTS_FABRICA = [
{"id": "sl-projectm", "nombre": "projectM", "tipo": "projectm-nativo"},
{"id": "sl-butter", "nombre": "Butter", "tipo": "butter",
"biblioteca": "butter", "preset": ""},
{"id": "sl-hydra", "nombre": "Hydra", "tipo": "hydra"},
{"id": "sl-shaders", "nombre": "Shaders", "tipo": "shader", "shader": ""},
{"id": "sl-mezcla", "nombre": "Mezcla", "tipo": "mixer"},
]
DEFAULTS = CFG.get("defaults", {})
ENGINES = ("projectm", "butterchurn", "hydra", "shaders", "mixer")
@ -187,6 +200,9 @@ state = {
# apagado para que el proyector salga limpio, sin marcos
# de colores ni circulos en las esquinas.
edit=False),
# Pastillas del panel: persisten en data/slots.json
"slots": (load_json(DATA / "slots.json", None)
if (DATA / "slots.json").is_file() else None) or list(SLOTS_FABRICA),
"meta": {
"butterchurnPresets": [],
"audioDevices": [],
@ -743,6 +759,34 @@ def capas_saneadas(lista, clave_geo):
return limpio
def slots_saneadas(lista):
"""Filtra lo que no tenga forma de pastilla y limita cuantas se aceptan:
igual que con el mapping, cualquiera en la red podria llenar el disco."""
limpio = []
for it in lista[:24]:
if isinstance(it, dict) and isinstance(it.get("id"), str) \
and isinstance(it.get("tipo"), str):
it["nombre"] = str(it.get("nombre", ""))[:12]
limpio.append(it)
return limpio
@socketio.on("set_slots")
def on_set_slots(data):
"""Guarda las pastillas del panel (data/slots.json) y las difunde."""
lista = (data or {}).get("slots")
if not isinstance(lista, list):
return
with lock:
state["slots"] = slots_saneadas(lista)
try:
with open(DATA / "slots.json", "w", encoding="utf-8") as fh:
json.dump(state["slots"], fh, ensure_ascii=False, indent=2)
except OSError as exc:
notify("error", f"No se pudieron guardar las pastillas: {exc}")
broadcast()
@socketio.on("set_mapping")
def on_set_mapping(data):
"""Projection mapping: activar/editar y guardar las superficies.