GUI for finger v1

This commit is contained in:
ykcheungab
2026-06-18 22:18:19 +08:00
parent f19f092b64
commit 3fd9b839a9
42 changed files with 2326 additions and 1186 deletions
+141
View File
@@ -0,0 +1,141 @@
"""
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")