migrate pyqt5 to pyside6
This commit is contained in:
+13
-4
@@ -24,7 +24,7 @@ dist/
|
||||
OrisysStudio/
|
||||
OrisysStudio.exe
|
||||
config/ # lean locales + sensors (no finger visualization/)
|
||||
_internal/ # Python, PyQt5, opencv-headless, orisys, OpenGL, …
|
||||
_internal/ # Python, PySide6, opencv-headless, orisys, OpenGL, …
|
||||
```
|
||||
|
||||
## Why there are two steps
|
||||
@@ -137,11 +137,20 @@ loaded" can only be printed if those modules imported.)
|
||||
|
||||
## Dependency notes
|
||||
|
||||
- App shared dep uses **`opencv-python-headless`** (PyQt owns the GUI). `setup.ps1` uninstalls
|
||||
any `opencv-python` and pins `opencv-python-headless>=4.10,<5` (5.x has shipped wheels
|
||||
without a `cv2` module) and verifies `import cv2`.
|
||||
- **PySide6** is the Qt binding (LGPLv3). PyQt5 was replaced because its free option is
|
||||
GPLv3-or-commercial; the apps are PySide6-only and their tests enforce it
|
||||
(`apps/studio/tests/test_binding_purity.py`, `apps/autocal/tests/test_binding_purity.py`).
|
||||
`setup.ps1` **uninstalls** PyQt5/PyQt6/PySide2 from the build venv and verifies only one
|
||||
binding is present: `pyqtgraph` binds to whichever toolkit it imports first, so a leftover
|
||||
PyQt5 yields a bundle whose plots were built from the wrong binding. The spec also excludes
|
||||
`PyQt5`/`PyQt6`/`PySide2` outright, and must never exclude `PySide6` itself.
|
||||
- App shared dep uses **`opencv-python-headless`** (PySide6 owns the GUI). `setup.ps1`
|
||||
uninstalls any `opencv-python` and pins `opencv-python-headless>=4.10,<5` (5.x has shipped
|
||||
wheels without a `cv2` module) and verifies `import cv2`.
|
||||
- **PyOpenGL** is the GL binding for the 3D pane and comes from the studio's `three-d` extra.
|
||||
Add the `protected-assets` extra (`cryptography`) if sealed `.oryscene` packages are shipped.
|
||||
- **`QOpenGLWidget` lives in `PySide6.QtOpenGLWidgets`**, not `QtWidgets` (Qt6 moved it): the
|
||||
spec names it in `hiddenimports` so the module and `Qt6OpenGLWidgets.dll` are bundled.
|
||||
- **Open3D stays excluded** (spec + build env): it is an offline geometry tool. The 3D pane is
|
||||
Qt `QOpenGLWidget` + PyOpenGL.
|
||||
- `constraints-build.txt` pins the toolchain. Move a pin only together with `setup.bat -Force`
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
# on the spec (which Qt, which numpy, which hooks-contrib), so a build that "works" on one
|
||||
# machine and produces a non-starting EXE on another is usually a version difference that
|
||||
# nobody wrote down. `-c` constrains whatever else asks for these packages, so the app's
|
||||
# own loose requirements (PyQt5>=5.15, scipy>=1.10.1, ...) resolve to exactly this set.
|
||||
# own loose requirements (PySide6>=6.6, scipy>=1.10.1, ...) resolve to exactly this set.
|
||||
#
|
||||
# PySide6 rather than PyQt5: the apps are LGPLv3-bound. PyQt5 must not be present in the
|
||||
# build environment at all — `pyqtgraph` binds to whichever toolkit it finds first, so a
|
||||
# leftover PyQt5 produces plots built from the wrong binding (setup.ps1 uninstalls it).
|
||||
#
|
||||
# `orisys` is deliberately absent: it is installed from a local checkout (or deliberately
|
||||
# from PyPI with -FromPypi), and pinning it by name would fight the editable install.
|
||||
@@ -15,9 +19,10 @@
|
||||
PyInstaller==6.22.2
|
||||
pyinstaller-hooks-contrib==2026.7
|
||||
PyOpenGL==3.1.10
|
||||
PyQt5==5.15.11
|
||||
PyQt5-Qt5==5.15.2
|
||||
PyQt5-sip==12.19.0
|
||||
PySide6==6.11.2
|
||||
PySide6-Essentials==6.11.2
|
||||
PySide6-Addons==6.11.2
|
||||
shiboken6==6.11.2
|
||||
numpy==2.2.6
|
||||
scipy==1.15.3
|
||||
numba==0.67.0
|
||||
|
||||
@@ -79,7 +79,12 @@ datas = _branding_datas()
|
||||
binaries: list = _extra_runtime_binaries()
|
||||
hiddenimports = [
|
||||
"cv2",
|
||||
"PyQt5.sip",
|
||||
# PySide6's runtime is shiboken6; the Qt binding itself is found from the imports, but
|
||||
# QtOpenGLWidgets is unusual enough to name explicitly: it is a separate module and DLL
|
||||
# (Qt6OpenGLWidgets), and without it the 3D pane reports "no OpenGL widgets" while the
|
||||
# rest of the app looks fine.
|
||||
"shiboken6",
|
||||
"PySide6.QtOpenGLWidgets",
|
||||
"pyqtgraph.graphicsItems",
|
||||
"scipy.signal",
|
||||
"numba",
|
||||
@@ -126,22 +131,50 @@ a = Analysis(
|
||||
"jupyter",
|
||||
"notebook",
|
||||
"pytest",
|
||||
"PyQt5.QtBluetooth",
|
||||
"PyQt5.QtNfc",
|
||||
"PyQt5.QtPositioning",
|
||||
"PyQt5.QtLocation",
|
||||
"PyQt5.QtMultimedia",
|
||||
"PyQt5.QtMultimediaWidgets",
|
||||
"PyQt5.QtSensors",
|
||||
"PyQt5.QtSerialPort",
|
||||
"PyQt5.QtWebChannel",
|
||||
"PyQt5.QtWebEngine",
|
||||
"PyQt5.QtWebEngineCore",
|
||||
"PyQt5.QtWebEngineWidgets",
|
||||
"PyQt5.QtWebSockets",
|
||||
"PySide2",
|
||||
"PySide6",
|
||||
# The app uses PySide6 only. Qt modules it never imports are excluded to keep the
|
||||
# bundle small — but `"PySide6"` itself must NOT appear here: excluding the whole
|
||||
# binding produces an EXE with no Qt at all (it fails at start-up, not at build
|
||||
# time). `PySide6.QtOpenGLWidgets` is required by the 3D pane and is deliberately
|
||||
# absent from this list.
|
||||
"PySide6.Qt3DAnimation",
|
||||
"PySide6.Qt3DCore",
|
||||
"PySide6.Qt3DExtras",
|
||||
"PySide6.Qt3DInput",
|
||||
"PySide6.Qt3DLogic",
|
||||
"PySide6.Qt3DRender",
|
||||
"PySide6.QtBluetooth",
|
||||
"PySide6.QtCharts",
|
||||
"PySide6.QtDataVisualization",
|
||||
"PySide6.QtGraphs",
|
||||
"PySide6.QtHelp",
|
||||
"PySide6.QtLocation",
|
||||
"PySide6.QtMultimedia",
|
||||
"PySide6.QtMultimediaWidgets",
|
||||
"PySide6.QtNfc",
|
||||
"PySide6.QtPdf",
|
||||
"PySide6.QtPdfWidgets",
|
||||
"PySide6.QtPositioning",
|
||||
"PySide6.QtQml",
|
||||
"PySide6.QtQuick",
|
||||
"PySide6.QtQuick3D",
|
||||
"PySide6.QtQuickControls2",
|
||||
"PySide6.QtQuickWidgets",
|
||||
"PySide6.QtRemoteObjects",
|
||||
"PySide6.QtScxml",
|
||||
"PySide6.QtSensors",
|
||||
"PySide6.QtSerialBus",
|
||||
"PySide6.QtSerialPort",
|
||||
"PySide6.QtSpatialAudio",
|
||||
"PySide6.QtStateMachine",
|
||||
"PySide6.QtTextToSpeech",
|
||||
"PySide6.QtWebChannel",
|
||||
"PySide6.QtWebEngineCore",
|
||||
"PySide6.QtWebEngineQuick",
|
||||
"PySide6.QtWebEngineWidgets",
|
||||
"PySide6.QtWebSockets",
|
||||
"PyQt5",
|
||||
"PyQt6",
|
||||
"PySide2",
|
||||
],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
|
||||
+13
-2
@@ -237,12 +237,19 @@ $install += @($PyInstallerPin, $CvPin)
|
||||
& $VenvPy @install
|
||||
if ($LASTEXITCODE -ne 0) { throw "pip install failed" }
|
||||
|
||||
# Upstream `orisys` may declare opencv-python; the app needs the headless wheel (PyQt owns
|
||||
# Upstream `orisys` may declare opencv-python; the app needs the headless wheel (PySide6 owns
|
||||
# the GUI), and 5.x can arrive without a `cv2` module.
|
||||
& $VenvPy -m pip uninstall -y opencv-python opencv-python-headless 2>$null | Out-Null
|
||||
& $VenvPy -m pip install --disable-pip-version-check -c $Constraints $CvPin
|
||||
if ($LASTEXITCODE -ne 0) { throw "opencv-python-headless install failed" }
|
||||
|
||||
# PyQt5 must not be in the build environment, even though the SDK's `gui` extra asks for it
|
||||
# when someone installs that: `pyqtgraph` binds to whichever toolkit it imports first, and a
|
||||
# frozen app built where PyQt5 is present can end up with plots built from the wrong binding.
|
||||
# The apps are PySide6-only (see apps/studio/tests/test_binding_purity.py).
|
||||
Write-Host "Removing any other Qt binding from the build venv ..."
|
||||
& $VenvPy -m pip uninstall -y PyQt5 PyQt5-Qt5 PyQt5-sip PyQt6 PySide2 2>$null | Out-Null
|
||||
|
||||
# Open3D must not be in the build env: it is an offline tool, and the spec excludes it.
|
||||
$hasOpen3d = & $VenvPy -c "import importlib.util; print(importlib.util.find_spec('open3d') is not None)"
|
||||
if ($hasOpen3d -eq "True") {
|
||||
@@ -256,8 +263,12 @@ if (-not $NoVerify) {
|
||||
Write-Host "Verifying the provisioned environment ..."
|
||||
# These are the imports the frozen app makes before it can draw anything. Checking them
|
||||
# here is what turns "the build succeeded" into "the build can start".
|
||||
& $VenvPy -c "import orisys_app_shared, orisys_studio, orisys, OpenGL, cv2, PyQt5.QtWidgets, PyInstaller; print('imports OK')"
|
||||
& $VenvPy -c "import orisys_app_shared, orisys_studio, orisys, OpenGL, cv2, PySide6.QtWidgets, PySide6.QtOpenGLWidgets, PyInstaller; print('imports OK')"
|
||||
if ($LASTEXITCODE -ne 0) { throw "the venv cannot import what the app imports (see above)" }
|
||||
# A second binding in the venv is the failure mode the uninstall above exists for: it
|
||||
# imports fine here and breaks `pyqtgraph` at run time.
|
||||
& $VenvPy -c "import importlib.util as u; bad=[m for m in ('PyQt5','PyQt6','PySide2') if u.find_spec(m)]; assert not bad, 'another Qt binding is installed: '+str(bad); print('binding OK: PySide6 only')"
|
||||
if ($LASTEXITCODE -ne 0) { throw "the build venv has more than one Qt binding" }
|
||||
# Theme colours are package data: PyInstaller only bundles them if the package is
|
||||
# importable at build time, so a stale editable install loses them silently.
|
||||
& $VenvPy -c "from PyInstaller.utils.hooks import collect_data_files as c; d=c('orisys_app_shared', includes=['theme/*.json']); assert d, 'theme data not collectable'; print('theme data OK:', len(d), 'files')"
|
||||
|
||||
Reference in New Issue
Block a user