feat(ui): improve device session management, update data directory paths, and add session clearing functionality.
This commit is contained in:
@@ -24,6 +24,19 @@ Rectangle {
|
||||
property int selectedTabIndex: 0
|
||||
property int selectedSideActionIndex: -1 // nothing active on startup
|
||||
|
||||
Connections {
|
||||
target: deviceController
|
||||
function onParsedDataReady(result) {
|
||||
if (result.success && result.csv_path) {
|
||||
// Load the freshly read CSV into the player
|
||||
streamController.loadFile(result.csv_path)
|
||||
|
||||
// Automatically switch to the "Wafer Map Tab" (index 3)
|
||||
root.selectedTabIndex = 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Main Two-Column Layout =====
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
@@ -60,9 +73,19 @@ Rectangle {
|
||||
onClicked: {
|
||||
root.selectedSideActionIndex = index
|
||||
root.selectedTabIndex = 0 // always jump to Status tab
|
||||
if (index === 0) deviceController.detectWafer()
|
||||
else if (index === 1) deviceController.readMemoryAsync()
|
||||
else if (index === 2) deviceController.openCsvFile()
|
||||
if (index === 0) {
|
||||
streamController.setMode("review")
|
||||
streamController.stopStream()
|
||||
deviceController.detectWafer()
|
||||
}
|
||||
else if (index === 1) {
|
||||
streamController.setMode("review")
|
||||
streamController.stopStream()
|
||||
deviceController.readMemoryAsync()
|
||||
}
|
||||
else if (index === 2) {
|
||||
deviceController.openCsvFile()
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
@@ -182,6 +205,7 @@ Rectangle {
|
||||
color: Theme.tabBarBackground
|
||||
border.color: Theme.workspaceBorder
|
||||
border.width: Theme.borderThin
|
||||
radius: Theme.radiusMd
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -47,11 +47,7 @@ ColumnLayout {
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 86
|
||||
color: {
|
||||
if (deviceController.connectionStatus === "Connected") return Theme.statusSuccessColor + "22"
|
||||
if (deviceController.connectionStatus === "Disconnected") return Theme.statusErrorColor + "22"
|
||||
return Theme.cardBackground
|
||||
}
|
||||
color: Theme.cardBackground
|
||||
border.color: {
|
||||
if (deviceController.connectionStatus === "Connected") return Theme.statusSuccessColor
|
||||
if (deviceController.connectionStatus === "Disconnected") return Theme.statusErrorColor
|
||||
@@ -187,13 +183,91 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
// --- Activity Log ---
|
||||
GroupBox {
|
||||
title: "Activity Log"
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: Theme.cardBackground
|
||||
border.color: Theme.cardBorder
|
||||
radius: Theme.radiusMd
|
||||
clip: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
// Header
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
color: Theme.subtleSectionBackground
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.panelPadding
|
||||
anchors.rightMargin: Theme.panelPadding
|
||||
|
||||
Label {
|
||||
text: "ACTIVITY LOG"
|
||||
color: Theme.bodyColor
|
||||
font.pixelSize: 10
|
||||
font.letterSpacing: 1.8
|
||||
font.weight: Font.Medium
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "REFRESH SESSION"
|
||||
font.pixelSize: 10
|
||||
implicitHeight: 24
|
||||
hoverEnabled: true
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Theme.buttonNeutralHover : "transparent"
|
||||
radius: Theme.radiusSm
|
||||
}
|
||||
contentItem: Text {
|
||||
text: parent.text
|
||||
color: Theme.bodyColor
|
||||
font: parent.font
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
onClicked: deviceController.clearSession()
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "CLEAR LOG"
|
||||
font.pixelSize: 10
|
||||
implicitHeight: 24
|
||||
hoverEnabled: true
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Theme.buttonNeutralHover : "transparent"
|
||||
radius: Theme.radiusSm
|
||||
}
|
||||
contentItem: Text {
|
||||
text: parent.text
|
||||
color: Theme.bodyColor
|
||||
font: parent.font
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
onClicked: deviceController.clearActivityLog()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 1
|
||||
color: Theme.cardBorder
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.margins: Theme.panelPadding
|
||||
clip: true
|
||||
|
||||
TextArea {
|
||||
@@ -204,9 +278,9 @@ ColumnLayout {
|
||||
font.family: "monospace"
|
||||
font.pixelSize: 12
|
||||
wrapMode: TextArea.WordWrap
|
||||
leftPadding: 4
|
||||
rightPadding: 4
|
||||
color: Theme.bodyColor
|
||||
background: null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,20 @@ Item {
|
||||
}
|
||||
}
|
||||
onCurrentIndexChanged:
|
||||
streamController.setMode(currentIndex === 0 ? "live" : "review")
|
||||
if (currentIndex === 0){
|
||||
// Entering Live mode, only work when wafer detected/connected
|
||||
streamController.setMode("live")
|
||||
if (deviceController.connectionStatus === "Connected") {
|
||||
var fc = ""
|
||||
if (deviceController.lastWaferInfo && deviceController.lastWaferInfo.length > 0) {
|
||||
fc = deviceController.lastWaferInfo[0]
|
||||
}
|
||||
streamController.startStream(deviceController.selectedPort, fc)
|
||||
}
|
||||
} else {
|
||||
// Entering Review Mode (automatically calls stopStream in backend)
|
||||
streamController.setMode("review")
|
||||
}
|
||||
}
|
||||
|
||||
// Live source info: WiFi icon + port · serial
|
||||
|
||||
@@ -15,6 +15,8 @@ Item {
|
||||
sensors: streamController.sensorLayout // [{label,x,y}]
|
||||
values: streamController.sensorValues // [float]
|
||||
bands: streamController.sensorBands // ["in_range"|"high"|"low"]
|
||||
shape: streamController.waferShape
|
||||
size: streamController.waferSize
|
||||
target: streamController.target
|
||||
margin: streamController.margin
|
||||
blend: root.blend
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import logging
|
||||
|
||||
from PySide6.QtQml import QQmlApplicationEngine
|
||||
from PySide6.QtQuickControls2 import QQuickStyle
|
||||
@@ -15,6 +16,10 @@ from pygui.backend.visualization.wafer_map_item import WaferMapItem
|
||||
|
||||
# ===== Application Entry Point =====
|
||||
def main() -> int:
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
|
||||
)
|
||||
# ===== UI Style Setup =====
|
||||
# Use a non-native controls style so our custom QML button backgrounds are supported.
|
||||
QQuickStyle.setStyle("Basic")
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
"""QML-exposed controller for wafer device communication.
|
||||
|
||||
Bridges DeviceService (serialcomm) to QML via @Slot methods and signals.
|
||||
All hardware operations run synchronously on the main thread (matching
|
||||
C# Form1.cs per-operation open/close pattern). For long operations
|
||||
(erase ~15s, read up to 120s) the caller should show a loading state.
|
||||
"""
|
||||
"""QML-exposed controller for wafer device communication."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -67,9 +61,8 @@ class DeviceController(QObject):
|
||||
self._operation_in_progress = False
|
||||
self._activity_log: list[str] = getattr(settings, 'activity_log', [])
|
||||
self._raw_bytes: Optional[bytes] = None
|
||||
# Default save dir lives next to the settings file (~/Documents/isc_data/csv)
|
||||
from pathlib import Path
|
||||
self._save_data_dir: str = getattr(settings, 'save_data_dir', str(Path.home() / "Documents" / "isc_data" / "csv"))
|
||||
self._save_data_dir: str = getattr(settings, 'save_data_dir', "") or str(Path(self._data_dir) / "csv")
|
||||
self._last_wafer_info: dict[str, Any] = getattr(settings, 'last_wafer_info', {})
|
||||
self._last_csv_path: str = getattr(settings, 'last_csv_path', "")
|
||||
self._selected_port: str = getattr(settings, 'selected_port', "")
|
||||
@@ -209,6 +202,28 @@ class DeviceController(QObject):
|
||||
self._activity_log = self._activity_log[-200:]
|
||||
self.activityLogUpdated.emit("\n".join(self._activity_log))
|
||||
|
||||
@Slot()
|
||||
def clearActivityLog(self) -> None:
|
||||
"""Clear the activity log."""
|
||||
self._activity_log.clear()
|
||||
self.activityLogUpdated.emit("")
|
||||
self._save_status()
|
||||
|
||||
@Slot()
|
||||
def clearSession(self) -> None:
|
||||
"""Clear the current session state, allowing a clean detect."""
|
||||
self._connection_status = "Disconnected"
|
||||
self._selected_port = ""
|
||||
self._last_wafer_info = {}
|
||||
self._data_row_count = 0
|
||||
self._data_col_count = 0
|
||||
self._raw_bytes = None
|
||||
self._operation_in_progress = False
|
||||
self.detectResult.emit(None)
|
||||
self.portsUpdated.emit(self.availablePorts)
|
||||
self._append_log("Session refreshed/cleared")
|
||||
self._save_status()
|
||||
|
||||
def _set_operation_progress(self, in_progress: bool) -> None:
|
||||
"""Set operation progress state and notify QML.
|
||||
|
||||
@@ -424,12 +439,10 @@ class DeviceController(QObject):
|
||||
return
|
||||
|
||||
if not self._save_data_dir:
|
||||
self._append_log("No save data directory set")
|
||||
self.parsedDataReady.emit({
|
||||
"success": False,
|
||||
"error": "No save data directory set",
|
||||
})
|
||||
return
|
||||
from pathlib import Path
|
||||
self._save_data_dir = str(Path(self._data_dir) / "csv")
|
||||
self._append_log(f"Auto-set save directory to: {self._save_data_dir}")
|
||||
self._save_status()
|
||||
|
||||
fc = family_code or (
|
||||
self._last_wafer_info.get("familyCode", "")
|
||||
@@ -509,7 +522,7 @@ class DeviceController(QObject):
|
||||
self._settings.last_csv_path = self._last_csv_path
|
||||
|
||||
# Save to disk
|
||||
LocalSettings.save_settings(str(LocalSettings._settings_path(self._data_dir)), self._settings)
|
||||
LocalSettings.save_settings(self._data_dir, self._settings)
|
||||
|
||||
# ---- Helpers ----
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ class LocalSettingsModel(QObject):
|
||||
QStandardPaths.DocumentsLocation
|
||||
)
|
||||
base_dir = Path(documents_dir) if documents_dir else (Path.home() / "Documents")
|
||||
return base_dir / "isc_data"
|
||||
return base_dir / "ISC_DATA"
|
||||
|
||||
def _new_defaults(self) -> dict[str, Any]:
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user