Update docs and screenshots
CI / backend (pull_request) Canceled after 0s
CI / shell (pull_request) Canceled after 0s
CI / frontend (pull_request) Canceled after 0s
CI / arm64-smoke (pull_request) Canceled after 0s
CI / backend (push) Canceled after 0s
CI / shell (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / arm64-smoke (push) Canceled after 0s

This commit is contained in:
2026-07-25 10:26:29 +02:00
parent d6cda77aaf
commit 1f110866f5
170 changed files with 24657 additions and 3 deletions
+91
View File
@@ -0,0 +1,91 @@
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import UTC, datetime
def clamp_channel(value: int) -> int:
return max(0, min(255, int(value)))
@dataclass(slots=True)
class FrameLayer:
name: str
priority: int
values_by_universe: dict[int, dict[int, int]]
precedence_map_by_universe: dict[int, dict[int, str]] = field(default_factory=dict)
created_at: datetime = field(default_factory=lambda: datetime.now(UTC))
@property
def values(self) -> dict[int, int]:
return self.values_by_universe.get(1, {})
@property
def precedence_map(self) -> dict[int, str]:
return self.precedence_map_by_universe.get(1, {})
@classmethod
def from_channel_values(
cls,
name: str,
priority: int,
values: dict[int, int],
precedence_map: dict[int, str] | None = None,
*,
universe: int = 1,
) -> "FrameLayer":
return cls(
name=name,
priority=priority,
values_by_universe={universe: values},
precedence_map_by_universe={universe: precedence_map or {}},
)
@dataclass(slots=True)
class DmxFrame:
universe: int = 1
values: list[int] = field(default_factory=lambda: [0] * 512)
source_map: list[str] = field(default_factory=lambda: ["idle"] * 512)
def set_channel(self, channel: int, value: int, source: str = "manual") -> None:
if not 1 <= channel <= 512:
raise ValueError("Channel must be in range 1..512")
self.values[channel - 1] = clamp_channel(value)
self.source_map[channel - 1] = source
def copy(self) -> DmxFrame:
return DmxFrame(self.universe, self.values.copy(), self.source_map.copy())
def merge_layers(layers: list[FrameLayer], blackout: bool = False) -> dict[int, DmxFrame]:
universes = sorted(
{
int(universe)
for layer in layers
for universe in layer.values_by_universe
}
) or [1]
if blackout:
return {universe: DmxFrame(universe=universe) for universe in universes}
ordered = sorted(layers, key=lambda layer: (layer.priority, layer.created_at))
frames = {universe: DmxFrame(universe=universe) for universe in universes}
for universe, frame in frames.items():
for channel in range(1, 513):
channel_candidates: list[tuple[FrameLayer, int]] = []
for layer in ordered:
values = layer.values_by_universe.get(universe, {})
if channel in values:
channel_candidates.append((layer, clamp_channel(values[channel])))
if not channel_candidates:
continue
precedence_map = channel_candidates[-1][0].precedence_map_by_universe.get(universe, {})
precedence = precedence_map.get(channel, "ltp").lower()
if precedence == "htp":
chosen_layer, chosen_value = max(channel_candidates, key=lambda pair: pair[1])
else:
chosen_layer, chosen_value = channel_candidates[-1]
frame.set_channel(channel, chosen_value, chosen_layer.name)
return frames