feat(license): wafer license gate, runtime warning, and first-launch wizard

- Gate READ/ERASE/DEBUG behind wafer license check; log hint to load .bin
- waferRuntimeExceeded() warns when onboard runtime > 350s
- LicenseModel: remove-license (soft hide, .bin stays on disk) via
  removed_licenses.txt; re-loading un-hides
- FirstLaunchWizard: 2-step popup (license + save dir) shown on first run
- Per-tab file memory in HomePage (map/graph/data don't bleed state)
- Merge test_device_controller_license into test_device_controller
This commit is contained in:
jack
2026-07-12 20:02:25 -07:00
parent 6003bde84d
commit 67158ed7ab
15 changed files with 699 additions and 54 deletions
@@ -378,12 +378,7 @@ class WaferMapItem(QQuickPaintedItem):
return idx
return -1
# TODO P6.1: build batch export on top of this single-frame export
# THINKING: export_image() already does the hard part (grabToImage → PNG);
# batch export is just a loop over file_browser.files that loads each CSV
# through the existing wafer-map pipeline and calls this per file into a
# chosen output dir, plus an optional summary CSV of (file, min/max/mean).
# No new rendering code needed — see docs/pending/alpha-release-polish-plan.md §6.1.
@Slot(str, result=bool)
@Slot(str, str, result=bool)
def export_image(self, file_path: str, extra: str = "") -> bool:
@@ -520,13 +515,6 @@ class WaferMapItem(QQuickPaintedItem):
r = max(math.hypot(s.x, s.y) for s in self._sensors)
return r * 1.05
# TODO P6.2: reuse this for the edge-to-center delta metric
# THINKING: radial_metrics.py (new) needs the same "which sensors are near
# the edge vs. near the center" bucketing this function already computes
# for ring-line drawing — do NOT re-derive ring boundaries separately, this
# already handles round vs. square wafer shapes correctly (see family_spec.py
# square-wafer work). Outermost group ≈ edge sensors, innermost ≈ center.
# See docs/pending/alpha-release-polish-plan.md §6.2.
def _sensor_ring_radii_mm(self) -> list[float]:
"""Distinct radial distances of sensor groups, sorted ascending, plus the outer boundary."""
if not self._sensors: