Probe QtWidgets, not QtCore: the qt backend needs the full GUI stack

QtCore's libraries (glib, icu) can load while QtWidgets/QtGui still
miss libGL - matplotlib's qt backend imports all three, so the old
probe could promise QtAgg and lose even the error figure at
switch_backend, one library layer deeper than the NixOS fix. The
regression test's mock now mirrors the real failure (bare package and
QtCore import fine, GUI modules raise): both the bare-import and the
QtCore-probe variants now fail it, verified by mutation. Also drop two
redundant f-prefixes on placeholder-less strings in report.py.
This commit is contained in:
2026-07-23 17:25:19 +07:00
parent a4945f419c
commit de7a4f8641
3 changed files with 28 additions and 16 deletions
+10 -6
View File
@@ -26,14 +26,18 @@ def _pick_backend():
("Cannot load backend 'TkAgg' ... as 'qt' is currently running") -
exactly what happened on macOS, whose bundled Python ships tkinter.
The probe must import the binding's native core, not just the
package: on NixOS `import PySide6` succeeds (pure __init__) while
QtCore's .so cannot find the system libraries pip wheels expect
("libgthread-2.0.so.0: cannot open shared object file") - promising
QtAgg then kills even the error figure at switch_backend time."""
The probe must import QtWidgets, not just the package or QtCore:
on NixOS `import PySide6` succeeds (pure __init__) while QtCore's
.so cannot find the system libraries pip wheels expect
("libgthread-2.0.so.0: cannot open shared object file"), and on a
partially provisioned system QtCore's deps (glib, icu) can be
present while QtWidgets/QtGui still miss libGL/libEGL. matplotlib's
qt backend imports QtCore, QtGui and QtWidgets, so probe the widest
one - promising QtAgg then kills even the error figure at
switch_backend time."""
for qt in ("PySide6", "PyQt6", "PyQt5", "PySide2"):
try:
__import__(qt + ".QtCore")
__import__(qt + ".QtWidgets")
return "QtAgg" if qt in ("PySide6", "PyQt6") else "Qt5Agg"
except Exception:
continue
+2 -2
View File
@@ -68,7 +68,7 @@ def write_summary(outdir: Path, problem: Problem, stack: RasterStack,
f"resistivity: {problem.rho_ohm_m:.3e} ohm*m",
f"via plating: {problem.plating_nm / 1000:.0f} um",
"",
(f"frequency: "
("frequency: "
+ (f"{result.freq_hz:g} Hz (skin depth {result.skin_depth_um:.0f} um)"
if result.freq_hz > 0 else "DC")),
f"RESISTANCE: {result.R_ohm * 1000:.6g} mOhm"
@@ -127,7 +127,7 @@ def write_summary(outdir: Path, problem: Problem, stack: RasterStack,
f"contact model: {result.contact_model}"
+ (" (uniform orthogonal injection; R is the upper contact bound)"
if result.contact_model == "uniform" else " (ideal bonded lug)"),
f"terminals:",
"terminals:",
f" V+ ({len(problem.electrodes1)} injection area(s)):",
*(f" {_electrode_line(e)}" for e in problem.electrodes1),
f" V- ({len(problem.electrodes2)} injection area(s)):",