From 9779baa468ac4976b05d7da7affd927a75a041ef Mon Sep 17 00:00:00 2001 From: jack Date: Wed, 3 Jun 2026 11:41:45 -0700 Subject: [PATCH] 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 --- .gitignore | 10 +++- ISC.qmlproject | 16 +++++++ README.md | 30 +++++++----- pyproject.toml | 48 ++++++++++++++++++- {ISC => src/pygui/ISC}/HomePage.qml | 0 {ISC => src/pygui/ISC}/Main.qml | 0 {ISC => src/pygui/ISC}/Tabs/DataTab.qml | 0 .../pygui/ISC}/Tabs/SelectFileDialog.qml | 0 {ISC => src/pygui/ISC}/Tabs/SettingsTab.qml | 0 {ISC => src/pygui/ISC}/Tabs/StatusTab.qml | 0 {ISC => src/pygui/ISC}/Tabs/qmldir | 0 {ISC => src/pygui/ISC}/Theme.qml | 0 {ISC => src/pygui/ISC}/qmldir | 0 src/pygui/__init__.py | 0 main.py => src/pygui/__main__.py | 21 +++++--- {backend => src/pygui/backend}/__init__.py | 0 .../pygui/backend}/contour_models.py | 0 .../pygui/backend}/crypto_helper.py | 0 .../pygui/backend}/csv_file_metadata.py | 0 {backend => src/pygui/backend}/data_model.py | 0 .../pygui/backend}/data_segment.py | 0 .../pygui/backend}/device_controller.py | 12 ++--- .../pygui/backend}/file_browser.py | 4 +- src/pygui/backend/frame.py | 11 +++++ {backend => src/pygui/backend}/graph_view.py | 0 .../pygui/backend}/local_settings.py | 0 .../pygui/backend}/local_settings_model.py | 2 +- .../pygui/backend}/marching_squares.py | 2 +- .../pygui/backend}/zwafer_models.py | 0 .../pygui/backend}/zwafer_parser.py | 2 +- .../pygui/serialcomm}/__init__.py | 4 +- .../pygui/serialcomm}/data_parser.py | 0 .../pygui/serialcomm}/device_service.py | 4 +- .../pygui/serialcomm}/serial_port.py | 0 34 files changed, 130 insertions(+), 36 deletions(-) create mode 100644 ISC.qmlproject rename {ISC => src/pygui/ISC}/HomePage.qml (100%) rename {ISC => src/pygui/ISC}/Main.qml (100%) rename {ISC => src/pygui/ISC}/Tabs/DataTab.qml (100%) rename {ISC => src/pygui/ISC}/Tabs/SelectFileDialog.qml (100%) rename {ISC => src/pygui/ISC}/Tabs/SettingsTab.qml (100%) rename {ISC => src/pygui/ISC}/Tabs/StatusTab.qml (100%) rename {ISC => src/pygui/ISC}/Tabs/qmldir (100%) rename {ISC => src/pygui/ISC}/Theme.qml (100%) rename {ISC => src/pygui/ISC}/qmldir (100%) create mode 100644 src/pygui/__init__.py rename main.py => src/pygui/__main__.py (74%) rename {backend => src/pygui/backend}/__init__.py (100%) rename {backend => src/pygui/backend}/contour_models.py (100%) rename {backend => src/pygui/backend}/crypto_helper.py (100%) rename {backend => src/pygui/backend}/csv_file_metadata.py (100%) rename {backend => src/pygui/backend}/data_model.py (100%) rename {backend => src/pygui/backend}/data_segment.py (100%) rename {backend => src/pygui/backend}/device_controller.py (98%) rename {backend => src/pygui/backend}/file_browser.py (98%) create mode 100644 src/pygui/backend/frame.py rename {backend => src/pygui/backend}/graph_view.py (100%) rename {backend => src/pygui/backend}/local_settings.py (100%) rename {backend => src/pygui/backend}/local_settings_model.py (99%) rename {backend => src/pygui/backend}/marching_squares.py (98%) rename {backend => src/pygui/backend}/zwafer_models.py (100%) rename {backend => src/pygui/backend}/zwafer_parser.py (98%) rename {serialcomm => src/pygui/serialcomm}/__init__.py (51%) rename {serialcomm => src/pygui/serialcomm}/data_parser.py (100%) rename {serialcomm => src/pygui/serialcomm}/device_service.py (97%) rename {serialcomm => src/pygui/serialcomm}/serial_port.py (100%) diff --git a/.gitignore b/.gitignore index 0d1ca56..0835d53 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ __pycache__/ *.pyd *.so *.a +*.egg-info/ +.pytest_cache/ +.ruff_cache/ # Virtual environment .venv/ @@ -14,10 +17,11 @@ env/ # IDE .vscode/ .idea/ +.qtcreator/ *.swp *.swo *~ - +docs # OS .DS_Store Thumbs.db @@ -34,4 +38,6 @@ build/ dist/ *.spec *.icns -*.ico \ No newline at end of file +*.ico +# Superpowers brainstorming visual companion +.superpowers/ diff --git a/ISC.qmlproject b/ISC.qmlproject new file mode 100644 index 0000000..c43abbd --- /dev/null +++ b/ISC.qmlproject @@ -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" + ] +} diff --git a/README.md b/README.md index b0fb2c2..47b9eb8 100644 --- a/README.md +++ b/README.md @@ -16,34 +16,42 @@ python3 -m venv .venv source .venv/bin/activate python -m pip install --upgrade pip pip install -r requirements.txt +pip install -e . # install the `pygui` package (src/ layout) in editable mode ``` ## Run ```bash 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 -- `main.py`: PySide6 bootstrap; creates the Qt app and loads QML module `ISC/Main`. -- `ISC/Main.qml`: top-level window definition. -- `ISC/HomePage.qml`: main UI layout (left action rail, workspace panel, footer tabs). -- `ISC/Theme.qml`: shared theme constants and dark/light mode tokens. -- `ISC/qmldir`: QML module registration. +The application lives under a `src/` layout as the `pygui` package: + +- `src/pygui/__main__.py`: PySide6 bootstrap; creates the Qt app and loads QML module `ISC/Main`. Entry point for `python -m pygui`. +- `src/pygui/backend/`: Qt-facing models and controllers (device, settings, file browser, wafer parsing). +- `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 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 - **Minimum size**: 1100 × 700 pixels - **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 Window { @@ -58,8 +66,8 @@ Window { ## Customization -- Toggle dark/light mode in `ISC/Theme.qml` via `isDarkMode`. -- Update sidebar and footer labels in `ISC/HomePage.qml` through `sideActions` and `bottomTabs`. +- Toggle dark/light mode in `src/pygui/ISC/Theme.qml` via `isDarkMode`. +- Update sidebar and footer labels in `src/pygui/ISC/HomePage.qml` through `sideActions` and `bottomTabs`. ## Troubleshooting diff --git a/pyproject.toml b/pyproject.toml index 33774a0..f1454bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,8 @@ +# ===== Build System ===== +[build-system] +requires = ["setuptools>=61"] +build-backend = "setuptools.build_meta" + # ===== Project Metadata ===== [project] name = "pygui" @@ -6,9 +11,50 @@ version = "0.1.0" [project.optional-dependencies] 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 ===== [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] dev = [ diff --git a/ISC/HomePage.qml b/src/pygui/ISC/HomePage.qml similarity index 100% rename from ISC/HomePage.qml rename to src/pygui/ISC/HomePage.qml diff --git a/ISC/Main.qml b/src/pygui/ISC/Main.qml similarity index 100% rename from ISC/Main.qml rename to src/pygui/ISC/Main.qml diff --git a/ISC/Tabs/DataTab.qml b/src/pygui/ISC/Tabs/DataTab.qml similarity index 100% rename from ISC/Tabs/DataTab.qml rename to src/pygui/ISC/Tabs/DataTab.qml diff --git a/ISC/Tabs/SelectFileDialog.qml b/src/pygui/ISC/Tabs/SelectFileDialog.qml similarity index 100% rename from ISC/Tabs/SelectFileDialog.qml rename to src/pygui/ISC/Tabs/SelectFileDialog.qml diff --git a/ISC/Tabs/SettingsTab.qml b/src/pygui/ISC/Tabs/SettingsTab.qml similarity index 100% rename from ISC/Tabs/SettingsTab.qml rename to src/pygui/ISC/Tabs/SettingsTab.qml diff --git a/ISC/Tabs/StatusTab.qml b/src/pygui/ISC/Tabs/StatusTab.qml similarity index 100% rename from ISC/Tabs/StatusTab.qml rename to src/pygui/ISC/Tabs/StatusTab.qml diff --git a/ISC/Tabs/qmldir b/src/pygui/ISC/Tabs/qmldir similarity index 100% rename from ISC/Tabs/qmldir rename to src/pygui/ISC/Tabs/qmldir diff --git a/ISC/Theme.qml b/src/pygui/ISC/Theme.qml similarity index 100% rename from ISC/Theme.qml rename to src/pygui/ISC/Theme.qml diff --git a/ISC/qmldir b/src/pygui/ISC/qmldir similarity index 100% rename from ISC/qmldir rename to src/pygui/ISC/qmldir diff --git a/src/pygui/__init__.py b/src/pygui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/main.py b/src/pygui/__main__.py similarity index 74% rename from main.py rename to src/pygui/__main__.py index 18f4954..8f87c21 100644 --- a/main.py +++ b/src/pygui/__main__.py @@ -5,13 +5,14 @@ from PySide6.QtQml import QQmlApplicationEngine from PySide6.QtQuickControls2 import QQuickStyle from PySide6.QtWidgets import QApplication -from backend.device_controller import DeviceController -from backend.local_settings import LocalSettings -from backend.local_settings_model import LocalSettingsModel -from backend.file_browser import FileBrowser +from pygui.backend.device_controller import DeviceController +from pygui.backend.local_settings import LocalSettings +from pygui.backend.local_settings_model import LocalSettingsModel +from pygui.backend.file_browser import FileBrowser + # ===== Application Entry Point ===== -if __name__ == "__main__": +def main() -> int: # ===== UI Style Setup ===== # Use a non-native controls style so our custom QML button backgrounds are supported. QQuickStyle.setStyle("Basic") @@ -35,11 +36,17 @@ if __name__ == "__main__": engine.rootContext().setContextProperty("deviceController", device_controller) # ===== 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.loadFromModule("ISC", "Main") # ===== Exit Handling ===== if not engine.rootObjects(): - sys.exit(-1) + return -1 - sys.exit(app.exec()) + return app.exec() + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/backend/__init__.py b/src/pygui/backend/__init__.py similarity index 100% rename from backend/__init__.py rename to src/pygui/backend/__init__.py diff --git a/backend/contour_models.py b/src/pygui/backend/contour_models.py similarity index 100% rename from backend/contour_models.py rename to src/pygui/backend/contour_models.py diff --git a/backend/crypto_helper.py b/src/pygui/backend/crypto_helper.py similarity index 100% rename from backend/crypto_helper.py rename to src/pygui/backend/crypto_helper.py diff --git a/backend/csv_file_metadata.py b/src/pygui/backend/csv_file_metadata.py similarity index 100% rename from backend/csv_file_metadata.py rename to src/pygui/backend/csv_file_metadata.py diff --git a/backend/data_model.py b/src/pygui/backend/data_model.py similarity index 100% rename from backend/data_model.py rename to src/pygui/backend/data_model.py diff --git a/backend/data_segment.py b/src/pygui/backend/data_segment.py similarity index 100% rename from backend/data_segment.py rename to src/pygui/backend/data_segment.py diff --git a/backend/device_controller.py b/src/pygui/backend/device_controller.py similarity index 98% rename from backend/device_controller.py rename to src/pygui/backend/device_controller.py index cd014b8..18a0a09 100644 --- a/backend/device_controller.py +++ b/src/pygui/backend/device_controller.py @@ -15,17 +15,17 @@ from typing import Any, Optional from PySide6.QtCore import QObject, Property, Qt, Signal, Slot -from backend.data_model import TemperatureTableModel -from backend.graph_view import GraphView -from backend.local_settings import LocalSettings -from serialcomm.data_parser import ( +from pygui.backend.data_model import TemperatureTableModel +from pygui.backend.graph_view import GraphView +from pygui.backend.local_settings import LocalSettings +from pygui.serialcomm.data_parser import ( convert_to_temperatures, parse_binary_data, remove_trailing_zeros, save_to_csv, ) -from serialcomm.device_service import DeviceService -from serialcomm.serial_port import WaferInfo +from pygui.serialcomm.device_service import DeviceService +from pygui.serialcomm.serial_port import WaferInfo log = logging.getLogger(__name__) diff --git a/backend/file_browser.py b/src/pygui/backend/file_browser.py similarity index 98% rename from backend/file_browser.py rename to src/pygui/backend/file_browser.py index 9d1035b..748c0be 100644 --- a/backend/file_browser.py +++ b/src/pygui/backend/file_browser.py @@ -8,8 +8,8 @@ from pathlib import Path from PySide6.QtCore import QObject, Property, QStandardPaths, Signal, Slot from PySide6.QtWidgets import QFileDialog, QMessageBox -from backend.csv_file_metadata import CSVFileMetadata -from backend.zwafer_parser import ZWaferParser +from pygui.backend.csv_file_metadata import CSVFileMetadata +from pygui.backend.zwafer_parser import ZWaferParser # ===== File Browser Model ===== diff --git a/src/pygui/backend/frame.py b/src/pygui/backend/frame.py new file mode 100644 index 0000000..eb44475 --- /dev/null +++ b/src/pygui/backend/frame.py @@ -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] diff --git a/backend/graph_view.py b/src/pygui/backend/graph_view.py similarity index 100% rename from backend/graph_view.py rename to src/pygui/backend/graph_view.py diff --git a/backend/local_settings.py b/src/pygui/backend/local_settings.py similarity index 100% rename from backend/local_settings.py rename to src/pygui/backend/local_settings.py diff --git a/backend/local_settings_model.py b/src/pygui/backend/local_settings_model.py similarity index 99% rename from backend/local_settings_model.py rename to src/pygui/backend/local_settings_model.py index 2c221a9..a7966ba 100644 --- a/backend/local_settings_model.py +++ b/src/pygui/backend/local_settings_model.py @@ -6,7 +6,7 @@ from typing import Any 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") diff --git a/backend/marching_squares.py b/src/pygui/backend/marching_squares.py similarity index 98% rename from backend/marching_squares.py rename to src/pygui/backend/marching_squares.py index 7f1787d..b66cc6a 100644 --- a/backend/marching_squares.py +++ b/src/pygui/backend/marching_squares.py @@ -2,7 +2,7 @@ from typing import List, Tuple, Optional import numpy as np -from backend.contour_models import ContourLine, ContourSegment +from pygui.backend.contour_models import ContourLine, ContourSegment # ===== Contour Generation ===== diff --git a/backend/zwafer_models.py b/src/pygui/backend/zwafer_models.py similarity index 100% rename from backend/zwafer_models.py rename to src/pygui/backend/zwafer_models.py diff --git a/backend/zwafer_parser.py b/src/pygui/backend/zwafer_parser.py similarity index 98% rename from backend/zwafer_parser.py rename to src/pygui/backend/zwafer_parser.py index 41e6cf5..92b6140 100644 --- a/backend/zwafer_parser.py +++ b/src/pygui/backend/zwafer_parser.py @@ -2,7 +2,7 @@ from pathlib import Path from typing import Tuple, Optional from datetime import datetime, timedelta -from backend.zwafer_models import ZWaferData, Sensor +from pygui.backend.zwafer_models import ZWaferData, Sensor # ===== Z-Wafer CSV Parser ===== diff --git a/serialcomm/__init__.py b/src/pygui/serialcomm/__init__.py similarity index 51% rename from serialcomm/__init__.py rename to src/pygui/serialcomm/__init__.py index 3c4c280..9ae3021 100644 --- a/serialcomm/__init__.py +++ b/src/pygui/serialcomm/__init__.py @@ -1,6 +1,6 @@ """Serial port communication layer for the temperature-sensing wafer.""" -from serialcomm.device_service import DeviceService -from serialcomm.serial_port import SerialPort, WaferInfo +from pygui.serialcomm.device_service import DeviceService +from pygui.serialcomm.serial_port import SerialPort, WaferInfo __all__ = ["DeviceService", "SerialPort", "WaferInfo"] diff --git a/serialcomm/data_parser.py b/src/pygui/serialcomm/data_parser.py similarity index 100% rename from serialcomm/data_parser.py rename to src/pygui/serialcomm/data_parser.py diff --git a/serialcomm/device_service.py b/src/pygui/serialcomm/device_service.py similarity index 97% rename from serialcomm/device_service.py rename to src/pygui/serialcomm/device_service.py index b4a5a8b..7ecaa56 100644 --- a/serialcomm/device_service.py +++ b/src/pygui/serialcomm/device_service.py @@ -11,8 +11,8 @@ from typing import Optional import serial.tools.list_ports -from backend.local_settings import LocalSettings -from serialcomm.serial_port import SerialPort, WaferInfo +from pygui.backend.local_settings import LocalSettings +from pygui.serialcomm.serial_port import SerialPort, WaferInfo log = logging.getLogger(__name__) diff --git a/serialcomm/serial_port.py b/src/pygui/serialcomm/serial_port.py similarity index 100% rename from serialcomm/serial_port.py rename to src/pygui/serialcomm/serial_port.py