Restructure into src/ layout under pygui package

Move all application source under src/pygui/ and rewire imports,
build config, and QML module path to match.
- Relocate backend/, serialcomm/, and the ISC QML module into
  src/pygui/; convert main.py into pygui/__main__.py with a main()
  entry point (run via `python -m pygui` or the new `isc` script)
- Rewrite absolute imports: backend.* -> pygui.backend.*,
  serialcomm.* -> pygui.serialcomm.* (source + tests)
- Move app icons (isc.ico/icns) into packaging/
- Update README and ISC.qmlproject to the new paths
This commit is contained in:
jack
2026-06-03 11:41:45 -07:00
parent af170666e8
commit 9779baa468
34 changed files with 130 additions and 36 deletions
+8 -2
View File
@@ -5,6 +5,9 @@ __pycache__/
*.pyd *.pyd
*.so *.so
*.a *.a
*.egg-info/
.pytest_cache/
.ruff_cache/
# Virtual environment # Virtual environment
.venv/ .venv/
@@ -14,10 +17,11 @@ env/
# IDE # IDE
.vscode/ .vscode/
.idea/ .idea/
.qtcreator/
*.swp *.swp
*.swo *.swo
*~ *~
docs
# OS # OS
.DS_Store .DS_Store
Thumbs.db Thumbs.db
@@ -34,4 +38,6 @@ build/
dist/ dist/
*.spec *.spec
*.icns *.icns
*.ico *.ico
# Superpowers brainstorming visual companion
.superpowers/
+16
View File
@@ -0,0 +1,16 @@
import type QmlProjectModel 1.0
QmlProject {
name: "ISC"
version: "1.0"
mainFile: "src/pygui/ISC/Main.qml"
dependencies {
module: "QtQuick"
minimumVersion: "6.0.0"
}
importPaths: [
"src/pygui"
]
}
+19 -11
View File
@@ -16,34 +16,42 @@ python3 -m venv .venv
source .venv/bin/activate source .venv/bin/activate
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install -r requirements.txt pip install -r requirements.txt
pip install -e . # install the `pygui` package (src/ layout) in editable mode
``` ```
## Run ## Run
```bash ```bash
source .venv/bin/activate source .venv/bin/activate
python main.py python -m pygui # or run the `isc` console script
``` ```
This launches the Qt window and loads the `ISC` QML module from `ISC/Main.qml`. This launches the Qt window and loads the `ISC` QML module from `src/pygui/ISC/Main.qml`.
## Project Structure ## Project Structure
- `main.py`: PySide6 bootstrap; creates the Qt app and loads QML module `ISC/Main`. The application lives under a `src/` layout as the `pygui` package:
- `ISC/Main.qml`: top-level window definition.
- `ISC/HomePage.qml`: main UI layout (left action rail, workspace panel, footer tabs). - `src/pygui/__main__.py`: PySide6 bootstrap; creates the Qt app and loads QML module `ISC/Main`. Entry point for `python -m pygui`.
- `ISC/Theme.qml`: shared theme constants and dark/light mode tokens. - `src/pygui/backend/`: Qt-facing models and controllers (device, settings, file browser, wafer parsing).
- `ISC/qmldir`: QML module registration. - `src/pygui/serialcomm/`: serial port, device service, and data-parser layer.
- `src/pygui/ISC/`: the `ISC` QML module (UI).
- `Main.qml`: top-level window definition.
- `HomePage.qml`: main UI layout (left action rail, workspace panel, footer tabs).
- `Theme.qml`: shared theme constants and dark/light mode tokens.
- `qmldir`: QML module registration.
- `tests/`: pytest suite.
- `packaging/`: PyInstaller spec (`isc.spec`) and app icons.
## Window Configuration ## Window Configuration
Window dimensions and constraints are defined in `ISC/Main.qml`: Window dimensions and constraints are defined in `src/pygui/ISC/Main.qml`:
- **Default size**: 1400 × 820 pixels - **Default size**: 1400 × 820 pixels
- **Minimum size**: 1100 × 700 pixels - **Minimum size**: 1100 × 700 pixels
- **Title bar**: "ISenseCloud" - **Title bar**: "ISenseCloud"
To adjust the window, edit the `Window` block in `ISC/Main.qml`: To adjust the window, edit the `Window` block in `src/pygui/ISC/Main.qml`:
```qml ```qml
Window { Window {
@@ -58,8 +66,8 @@ Window {
## Customization ## Customization
- Toggle dark/light mode in `ISC/Theme.qml` via `isDarkMode`. - Toggle dark/light mode in `src/pygui/ISC/Theme.qml` via `isDarkMode`.
- Update sidebar and footer labels in `ISC/HomePage.qml` through `sideActions` and `bottomTabs`. - Update sidebar and footer labels in `src/pygui/ISC/HomePage.qml` through `sideActions` and `bottomTabs`.
## Troubleshooting ## Troubleshooting
+47 -1
View File
@@ -1,3 +1,8 @@
# ===== Build System =====
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
# ===== Project Metadata ===== # ===== Project Metadata =====
[project] [project]
name = "pygui" name = "pygui"
@@ -6,9 +11,50 @@ version = "0.1.0"
[project.optional-dependencies] [project.optional-dependencies]
dev = ["pytest"] dev = ["pytest"]
# ===== Console Entry Point =====
[project.scripts]
isc = "pygui.__main__:main"
# ===== Package Discovery (src layout) =====
[tool.setuptools.packages.find]
where = ["src"]
[tool.setuptools.package-data]
# Ship the QML module alongside the Python package.
pygui = ["ISC/**/*.qml", "ISC/**/qmldir"]
# ===== PySide Build Inputs ===== # ===== PySide Build Inputs =====
[tool.pyside6-project] [tool.pyside6-project]
files = ["ISC/HomePage.qml", "ISC/Main.qml", "ISC/Tabs/DataTab.qml", "ISC/Tabs/GraphTab.qml", "ISC/Tabs/SelectFileDialog.qml", "ISC/Tabs/SettingsTab.qml", "ISC/Tabs/StatusTab.qml", "ISC/Tabs/qmldir", "ISC/Theme.qml", "ISC/qmldir", "backend/data_model.py", "backend/device_controller.py", "backend/graph_view.py", "backend/file_browser.py", "backend/local_settings.py", "backend/local_settings_model.py", "main.py", "serialcomm/__init__.py", "serialcomm/data_parser.py", "serialcomm/serial_port.py", "serialcomm/device_service.py"] files = [
"src/pygui/ISC/HomePage.qml",
"src/pygui/ISC/Main.qml",
"src/pygui/ISC/Theme.qml",
"src/pygui/ISC/qmldir",
"src/pygui/ISC/Tabs/DataTab.qml",
"src/pygui/ISC/Tabs/SelectFileDialog.qml",
"src/pygui/ISC/Tabs/SettingsTab.qml",
"src/pygui/ISC/Tabs/StatusTab.qml",
"src/pygui/ISC/Tabs/qmldir",
"src/pygui/__main__.py",
"src/pygui/backend/contour_models.py",
"src/pygui/backend/crypto_helper.py",
"src/pygui/backend/csv_file_metadata.py",
"src/pygui/backend/data_model.py",
"src/pygui/backend/data_segment.py",
"src/pygui/backend/device_controller.py",
"src/pygui/backend/file_browser.py",
"src/pygui/backend/frame.py",
"src/pygui/backend/graph_view.py",
"src/pygui/backend/local_settings.py",
"src/pygui/backend/local_settings_model.py",
"src/pygui/backend/marching_squares.py",
"src/pygui/backend/zwafer_models.py",
"src/pygui/backend/zwafer_parser.py",
"src/pygui/serialcomm/__init__.py",
"src/pygui/serialcomm/data_parser.py",
"src/pygui/serialcomm/device_service.py",
"src/pygui/serialcomm/serial_port.py",
]
[dependency-groups] [dependency-groups]
dev = [ dev = [
View File
View File
+14 -7
View File
@@ -5,13 +5,14 @@ from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtQuickControls2 import QQuickStyle from PySide6.QtQuickControls2 import QQuickStyle
from PySide6.QtWidgets import QApplication from PySide6.QtWidgets import QApplication
from backend.device_controller import DeviceController from pygui.backend.device_controller import DeviceController
from backend.local_settings import LocalSettings from pygui.backend.local_settings import LocalSettings
from backend.local_settings_model import LocalSettingsModel from pygui.backend.local_settings_model import LocalSettingsModel
from backend.file_browser import FileBrowser from pygui.backend.file_browser import FileBrowser
# ===== Application Entry Point ===== # ===== Application Entry Point =====
if __name__ == "__main__": def main() -> int:
# ===== UI Style Setup ===== # ===== UI Style Setup =====
# Use a non-native controls style so our custom QML button backgrounds are supported. # Use a non-native controls style so our custom QML button backgrounds are supported.
QQuickStyle.setStyle("Basic") QQuickStyle.setStyle("Basic")
@@ -35,11 +36,17 @@ if __name__ == "__main__":
engine.rootContext().setContextProperty("deviceController", device_controller) engine.rootContext().setContextProperty("deviceController", device_controller)
# ===== QML Startup ===== # ===== QML Startup =====
# The "ISC" QML module lives alongside this file (src/pygui/ISC), so the
# package directory is the import path the engine searches for qmldir.
engine.addImportPath(Path(__file__).parent) engine.addImportPath(Path(__file__).parent)
engine.loadFromModule("ISC", "Main") engine.loadFromModule("ISC", "Main")
# ===== Exit Handling ===== # ===== Exit Handling =====
if not engine.rootObjects(): if not engine.rootObjects():
sys.exit(-1) return -1
sys.exit(app.exec()) return app.exec()
if __name__ == "__main__":
sys.exit(main())
@@ -15,17 +15,17 @@ from typing import Any, Optional
from PySide6.QtCore import QObject, Property, Qt, Signal, Slot from PySide6.QtCore import QObject, Property, Qt, Signal, Slot
from backend.data_model import TemperatureTableModel from pygui.backend.data_model import TemperatureTableModel
from backend.graph_view import GraphView from pygui.backend.graph_view import GraphView
from backend.local_settings import LocalSettings from pygui.backend.local_settings import LocalSettings
from serialcomm.data_parser import ( from pygui.serialcomm.data_parser import (
convert_to_temperatures, convert_to_temperatures,
parse_binary_data, parse_binary_data,
remove_trailing_zeros, remove_trailing_zeros,
save_to_csv, save_to_csv,
) )
from serialcomm.device_service import DeviceService from pygui.serialcomm.device_service import DeviceService
from serialcomm.serial_port import WaferInfo from pygui.serialcomm.serial_port import WaferInfo
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -8,8 +8,8 @@ from pathlib import Path
from PySide6.QtCore import QObject, Property, QStandardPaths, Signal, Slot from PySide6.QtCore import QObject, Property, QStandardPaths, Signal, Slot
from PySide6.QtWidgets import QFileDialog, QMessageBox from PySide6.QtWidgets import QFileDialog, QMessageBox
from backend.csv_file_metadata import CSVFileMetadata from pygui.backend.csv_file_metadata import CSVFileMetadata
from backend.zwafer_parser import ZWaferParser from pygui.backend.zwafer_parser import ZWaferParser
# ===== File Browser Model ===== # ===== File Browser Model =====
+11
View File
@@ -0,0 +1,11 @@
from __future__ import annotations
from dataclasses import dataclass
@dataclass(frozen=True)
class Frame:
"""One sample across all sensors ata a point in time"""
seq: int
t: float
values: list[float]
@@ -6,7 +6,7 @@ from typing import Any
from PySide6.QtCore import QObject, Property, QDateTime, QStandardPaths, Signal, Slot from PySide6.QtCore import QObject, Property, QDateTime, QStandardPaths, Signal, Slot
from backend.local_settings import LocalSettings from pygui.backend.local_settings import LocalSettings
MASTER_FAMILIES = ("A", "B", "C", "D", "E", "F", "P", "X", "Z") MASTER_FAMILIES = ("A", "B", "C", "D", "E", "F", "P", "X", "Z")
@@ -2,7 +2,7 @@ from typing import List, Tuple, Optional
import numpy as np import numpy as np
from backend.contour_models import ContourLine, ContourSegment from pygui.backend.contour_models import ContourLine, ContourSegment
# ===== Contour Generation ===== # ===== Contour Generation =====
@@ -2,7 +2,7 @@ from pathlib import Path
from typing import Tuple, Optional from typing import Tuple, Optional
from datetime import datetime, timedelta from datetime import datetime, timedelta
from backend.zwafer_models import ZWaferData, Sensor from pygui.backend.zwafer_models import ZWaferData, Sensor
# ===== Z-Wafer CSV Parser ===== # ===== Z-Wafer CSV Parser =====
@@ -1,6 +1,6 @@
"""Serial port communication layer for the temperature-sensing wafer.""" """Serial port communication layer for the temperature-sensing wafer."""
from serialcomm.device_service import DeviceService from pygui.serialcomm.device_service import DeviceService
from serialcomm.serial_port import SerialPort, WaferInfo from pygui.serialcomm.serial_port import SerialPort, WaferInfo
__all__ = ["DeviceService", "SerialPort", "WaferInfo"] __all__ = ["DeviceService", "SerialPort", "WaferInfo"]
@@ -11,8 +11,8 @@ from typing import Optional
import serial.tools.list_ports import serial.tools.list_ports
from backend.local_settings import LocalSettings from pygui.backend.local_settings import LocalSettings
from serialcomm.serial_port import SerialPort, WaferInfo from pygui.serialcomm.serial_port import SerialPort, WaferInfo
log = logging.getLogger(__name__) log = logging.getLogger(__name__)