""" Finger dashboard layout — edit this file to define the panel hierarchy and sizes. Structure: ┌─ toolbar ─────────────────────────────────────────────┐ ├─ TOP (stretch: top_stretch) ──────────────────────────┤ │ ┌ mesh ─┬─ arrows ─┬─ charts ─┐ (left → right) │ │ └───────┴───────────┴─────────┘ │ ├─ LOG (stretch: log_stretch) ──────────────────────────┤ └───────────────────────────────────────────────────────┘ Each panel uses explicit width_px and height_px. top_stretch / log_stretch control how extra vertical space is shared on resize. Edit TOP_ROW.slots to reorder or resize panels. Edit PANEL_SETUP to change top vs log proportions. """ from __future__ import annotations from dataclasses import dataclass from typing import Literal PanelId = Literal["mesh", "arrows", "charts", "log"] @dataclass(frozen=True) class PanelSlot: """One panel in the dashboard.""" id: PanelId title: str width_px: int height_px: int stretch: int = 0 enabled: bool = True @dataclass(frozen=True) class PanelRow: """Horizontal row of panels (the top visual strip).""" slots: tuple[PanelSlot, ...] collapsible: bool = False @dataclass(frozen=True) class PanelSetup: """Full panel hierarchy: top row + bottom log.""" top_row: PanelRow log_panel: PanelSlot top_stretch: int = 5 log_stretch: int = 1 # --- Top strip: mesh | arrows | charts (edit order and sizes here) --- TOP_ROW = PanelRow( slots=( PanelSlot("mesh", "3D mesh", width_px=500, height_px=500), PanelSlot("arrows", "UV Unrolled", width_px=500, height_px=500), PanelSlot("charts", "Force charts", width_px=500, height_px=500), ), ) LOG_PANEL = PanelSlot("log", "Log", width_px=0, height_px=100) PANEL_SETUP = PanelSetup( top_row=TOP_ROW, log_panel=LOG_PANEL, top_stretch=5, log_stretch=1, ) @dataclass(frozen=True) class DashboardLayout: """Window chrome, chart styling, and panel hierarchy.""" panel_setup: PanelSetup = PANEL_SETUP window_title: str = "Orisys Finger Dashboard" window_width: int = 1500 window_height: int = 600 root_margin_px: int = 6 root_spacing_px: int = 6 toolbar_spacing_px: int = 8 plot_buffer_points: int = 120 plot_sample_rate_hz: float = 30.0 chart_title_font_pt: int = 11 chart_axis_label_font_pt: int = 9 chart_tick_font_pt: int = 9 y_axis_min_span: float = 20000.0 y_range_smooth_alpha: float = 0.5 curve_color: str = "#00E5FF" # UV arrow panel: min flow magnitude to draw an arrow (display only; not dd02-ov.json contact.flow_threshold) arrow_flow_threshold: float = 1.0 timer_interval_ms: int = 10 def slot(self, panel_id: PanelId) -> PanelSlot | None: if self.panel_setup.log_panel.id == panel_id: return self.panel_setup.log_panel for slot in self.panel_setup.top_row.slots: if slot.id == panel_id: return slot return None def top_slot(self, panel_id: PanelId) -> PanelSlot: for slot in self.panel_setup.top_row.slots: if slot.id == panel_id: return slot raise KeyError(f"panel {panel_id!r} not in top row") @property def mesh_panel_title(self) -> str: return self.top_slot("mesh").title @property def arrows_panel_title(self) -> str: return self.top_slot("arrows").title LAYOUT = DashboardLayout() def top_row_splitter_sizes(layout: DashboardLayout = LAYOUT) -> tuple[int, ...]: """Horizontal splitter widths from panel setup.""" return tuple(slot.width_px for slot in layout.panel_setup.top_row.slots if slot.enabled) def top_row_slot_index(panel_id: PanelId, layout: DashboardLayout = LAYOUT) -> int: """Index of a panel in the horizontal top splitter.""" for i, slot in enumerate(layout.panel_setup.top_row.slots): if slot.id == panel_id: return i raise KeyError(f"panel {panel_id!r} not in top row")