Files
RFP_Infinity-Vis/app/core/pattern_compat.py

126 lines
4.8 KiB
Python

from __future__ import annotations
from app.core.types import PatternParameters, clamp
SCAN_ANGLES: tuple[int, ...] = (0, 45, 90, 135, 180, 225, 270, 315)
_LEGACY_SCAN_IDS = {
"row_chase",
"row_scan",
"column_chase",
"column_scan",
"diagonal_scan",
}
_DEFAULT_SCAN_PARAMS = PatternParameters()
def nearest_scan_angle(value: float) -> int:
angle = int(round(float(value))) % 360
return min(SCAN_ANGLES, key=lambda candidate: min((candidate - angle) % 360, (angle - candidate) % 360))
def coerce_bool(value: object, default: bool = False) -> bool:
if isinstance(value, bool):
return value
if isinstance(value, (int, float)):
return bool(value)
if isinstance(value, str):
normalized = value.strip().lower()
if normalized in {"1", "true", "yes", "on"}:
return True
if normalized in {"0", "false", "no", "off"}:
return False
return default
def _flip_angle_horizontal(angle: int) -> int:
return (180 - angle) % 360
def _flip_angle_vertical(angle: int) -> int:
return (-angle) % 360
def normalize_pattern_request(
pattern_id: str,
params: PatternParameters | None = None,
rows: int | None = None,
cols: int | None = None,
) -> tuple[str, PatternParameters | None]:
if pattern_id == "pixel_sparkle":
if params is None:
return "strobe", None
payload = params.to_dict()
payload["strobe_mode"] = "random_pixels"
return "strobe", PatternParameters.from_dict(payload)
if pattern_id == "random_blocks":
return "sparkle", params
if pattern_id == "scan" and params is not None:
payload = params.to_dict()
angle = nearest_scan_angle(getattr(params, "angle", payload.get("angle", 0.0)))
if coerce_bool(getattr(params, "flip_horizontal", False)):
angle = _flip_angle_horizontal(angle)
if coerce_bool(getattr(params, "flip_vertical", False)):
angle = _flip_angle_vertical(angle)
payload["angle"] = angle
payload.pop("band_thickness", None)
payload.pop("flip_horizontal", None)
payload.pop("flip_vertical", None)
return "scan", PatternParameters.from_dict(payload)
if pattern_id not in _LEGACY_SCAN_IDS:
return pattern_id, params
payload = params.to_dict() if params is not None else {}
if params is not None:
payload["flip_horizontal"] = getattr(params, "flip_horizontal", payload.get("flip_horizontal", False))
payload["flip_vertical"] = getattr(params, "flip_vertical", payload.get("flip_vertical", False))
payload["band_thickness"] = getattr(params, "band_thickness", payload.get("band_thickness", params.on_width))
direction = str(payload.get("direction", "left_to_right"))
diagonal_mode = str(payload.get("diagonal_scan_mode", "line"))
def preferred_value(key: str, legacy_value):
current_value = payload.get(key, getattr(_DEFAULT_SCAN_PARAMS, key))
default_value = getattr(_DEFAULT_SCAN_PARAMS, key)
return legacy_value if current_value == default_value else current_value
if pattern_id in {"row_chase", "row_scan"}:
angle = 90 if direction != "bottom_to_top" else 270
on_width = float(preferred_value("on_width", payload.get("block_size", 1.0)))
off_width = float(preferred_value("off_width", 0.0))
scan_style = str(preferred_value("scan_style", "line"))
elif pattern_id in {"column_chase", "column_scan"}:
angle = 0 if direction != "right_to_left" else 180
on_width = float(preferred_value("on_width", payload.get("block_size", 1.0)))
off_width = float(preferred_value("off_width", 0.0))
scan_style = str(preferred_value("scan_style", "line"))
else:
angle = {
"left_to_right": 315,
"right_to_left": 135,
"top_to_bottom": 315,
"bottom_to_top": 135,
}.get(direction, 315)
scan_style = "bands" if diagonal_mode == "bands" else "line"
scan_style = str(preferred_value("scan_style", scan_style))
on_width = float(preferred_value("on_width", 2.0 if scan_style == "bands" else 0.5))
off_width = float(preferred_value("off_width", 1.5 if scan_style == "bands" else 0.0))
angle = nearest_scan_angle(preferred_value("angle", angle))
if coerce_bool(preferred_value("flip_horizontal", False)):
angle = _flip_angle_horizontal(angle)
if coerce_bool(preferred_value("flip_vertical", False)):
angle = _flip_angle_vertical(angle)
payload["angle"] = angle
payload["scan_style"] = str(scan_style)
payload["on_width"] = clamp(float(on_width), 0.1, 2.0)
payload["off_width"] = clamp(float(off_width), 0.0, 2.0)
payload.pop("band_thickness", None)
payload.pop("flip_horizontal", None)
payload.pop("flip_vertical", None)
return "scan", PatternParameters.from_dict(payload)