gratinglab 0.2.1__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gratinglab-0.2.1/src/gratinglab.egg-info → gratinglab-0.2.2}/PKG-INFO +3 -3
- {gratinglab-0.2.1 → gratinglab-0.2.2}/pyproject.toml +2 -2
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/__init__.py +1 -1
- gratinglab-0.2.2/src/gratinglab/gui/__init__.py +30 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/app.py +4 -12
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/launcher.py +5 -13
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/__init__.py +4 -5
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/main_window.py +10 -9
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/worker.py +15 -17
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/state.py +6 -7
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/config.py +13 -17
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/__init__.py +11 -13
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/__init__.py +8 -6
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/main_window.py +8 -7
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/__init__.py +10 -20
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/base.py +8 -1
- {gratinglab-0.2.1 → gratinglab-0.2.2/src/gratinglab.egg-info}/PKG-INFO +3 -3
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab.egg-info/SOURCES.txt +0 -1
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab.egg-info/requires.txt +2 -2
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_qt.py +35 -30
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_solver_progress.py +27 -0
- gratinglab-0.2.1/src/gratinglab/gui/qt/gc_guard.py +0 -102
- gratinglab-0.2.1/src/gratinglab/io/__init__.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/LICENSE +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/README.md +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/setup.cfg +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/checks.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/compare.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/convergence.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/geometry.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/cmethod_options.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/diagram.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/diagram3d.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/docs.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/efficiency_plot.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/icon.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/integral_options.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/mathtext.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/orders.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/provenance.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/cmethod_tab.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/geometry_panel.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/geometry_tab.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/integral_tab.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/launcher.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/profile_plot_panel.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/rcwa_tab.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/scalar_tab.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/scene3d_panel.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/setup_tab.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/sizing.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/theming.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/theory_viewer.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/rcwa_options.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/richtext.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/scalar_options.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/theme.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/illumination.py +0 -0
- {gratinglab-0.2.1/src/gratinglab/gui → gratinglab-0.2.2/src/gratinglab/io}/__init__.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/io/boundary.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/io/efficiency_table.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/io/ggp.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/materials/__init__.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/materials/data/SOURCES.md +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/materials/fresnel.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/materials/optical.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/materials/refractiveindex.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/measured.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/__init__.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/analyzer.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/boundary/__init__.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/boundary/average.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/boundary/pipeline.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/boundary/swaths.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/cli.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/core/__init__.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/core/analysis.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/core/image_flatten.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/core/orientation.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/core/processing.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/core/region_mask.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/core/scars.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/core/tip.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/data/__init__.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/data/aggregation.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/icon.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/app.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/boundary_view.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/canvas.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/efficiency_dialog.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/import_view.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/swath_dialog.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/wiki_view.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/worker.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/state.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/io/__init__.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/io/file_io.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/io/ibw.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/io/metrics.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/io/spm.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/settings.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/stats/__init__.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/stats/analysis.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/stats/icc.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/visualization/__init__.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/visualization/diagnostics.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/visualization/profiles.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/visualization/statistics.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/facet-fitting.md +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/file-formats.md +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/flattening.md +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/groove-orientation.md +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/icc-correction.md +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/overview.md +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/reading-outputs.md +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/row-groups.md +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/scars-and-masking.md +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/swath-statistics.md +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/tip-correction.md +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/workflows.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/problem.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/profiles.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/resolution.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/result.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/__init__.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/_conditioning.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/_execution.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/cmethod/__init__.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/cmethod/_core.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/cmethod/_series.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/integral/__init__.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/integral/_boundary.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/integral/_core.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/integral/_finite.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/integral/_greens.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/integral/_kernels.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/integral/_nystrom.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/rcwa/__init__.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/rcwa/_core.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/rcwa/_stack.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/scalar.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab.egg-info/dependency_links.txt +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab.egg-info/entry_points.txt +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab.egg-info/top_level.txt +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_changelog.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_checks.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_cmethod.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_compare.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_conditioning.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_convergence.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_cross_method.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_efficiency_table.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_fresnel.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_geometry.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_ggp.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_cmethod_options.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_cmethod_tab.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_diagram.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_diagram3d.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_docs.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_integral_options.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_launcher.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_mathtext.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_orders.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_provenance.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_rcwa_options.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_richtext.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_scalar_options.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_state.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_theme.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_illumination.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_integral_core.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_integral_corpus.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_integral_finite.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_integral_graded.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_integral_greens.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_integral_kernels.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_integral_operators.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_integral_solver.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_io_boundary.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_materials.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_measured.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_perturbation.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_profiles.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_rayleigh_anomaly.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_rcwa.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_rcwa_benchmarks.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_refractiveindex.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_resolution.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_result.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_scalar.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_solver_conformance.py +0 -0
- {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_solver_execution.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gratinglab
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Open platform for diffraction grating groove metrology and efficiency analysis
|
|
5
5
|
Author: Jake McCoy, Aditi Gangadharan
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -22,7 +22,7 @@ License-File: LICENSE
|
|
|
22
22
|
Requires-Dist: numpy>=1.26
|
|
23
23
|
Requires-Dist: scipy>=1.11
|
|
24
24
|
Requires-Dist: pydantic>=2.6
|
|
25
|
-
Requires-Dist: endstation>=0.2.
|
|
25
|
+
Requires-Dist: endstation>=0.2.6
|
|
26
26
|
Requires-Dist: threadpoolctl>=3.1
|
|
27
27
|
Provides-Extra: dev
|
|
28
28
|
Requires-Dist: pytest>=7; extra == "dev"
|
|
@@ -34,7 +34,7 @@ Provides-Extra: gui
|
|
|
34
34
|
Requires-Dist: matplotlib>=3.8; extra == "gui"
|
|
35
35
|
Requires-Dist: PySide6-Essentials!=6.12.0,>=6.7; python_version < "3.12" and extra == "gui"
|
|
36
36
|
Requires-Dist: PySide6-Essentials>=6.7; python_version >= "3.12" and extra == "gui"
|
|
37
|
-
Requires-Dist: endstation[qt]>=0.2.
|
|
37
|
+
Requires-Dist: endstation[qt]>=0.2.6; extra == "gui"
|
|
38
38
|
Provides-Extra: metrology
|
|
39
39
|
Requires-Dist: matplotlib>=3.9; extra == "metrology"
|
|
40
40
|
Provides-Extra: refractiveindex
|
|
@@ -51,7 +51,7 @@ dependencies = [
|
|
|
51
51
|
# `dependencies = []` and has a test that fails if that stops being true,
|
|
52
52
|
# so this line adds no transitive weight -- the Qt and matplotlib parts sit
|
|
53
53
|
# behind its own extras, which our `gui` extra asks for by name.
|
|
54
|
-
"endstation>=0.2.
|
|
54
|
+
"endstation>=0.2.6",
|
|
55
55
|
# The rigorous solvers cap BLAS threads for the length of a solve
|
|
56
56
|
# (solvers/_execution.py): OpenBLAS's thread-per-core default made RCWA
|
|
57
57
|
# up to 135x slower on a busy machine, measured in benchmarks/
|
|
@@ -85,7 +85,7 @@ gui = [
|
|
|
85
85
|
"matplotlib>=3.8",
|
|
86
86
|
"PySide6-Essentials>=6.7,!=6.12.0; python_version < '3.12'",
|
|
87
87
|
"PySide6-Essentials>=6.7; python_version >= '3.12'",
|
|
88
|
-
"endstation[qt]>=0.2.
|
|
88
|
+
"endstation[qt]>=0.2.6",
|
|
89
89
|
]
|
|
90
90
|
# Groove metrology: reading AFM scans and averaging them into a boundary
|
|
91
91
|
# profile. An extra for the same reason the GUI is one -- `pip install
|
|
@@ -22,7 +22,7 @@ from .measured import MeasuredEfficiency, MeasurementProvenance
|
|
|
22
22
|
from .resolution import ResolvingPower, resolving_power
|
|
23
23
|
from .result import EfficiencyScan, OrderEfficiency, Provenance
|
|
24
24
|
|
|
25
|
-
__version__ = "0.2.
|
|
25
|
+
__version__ = "0.2.2"
|
|
26
26
|
|
|
27
27
|
__all__ = [
|
|
28
28
|
"Illumination",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""The desktop layer: two windows and the chooser that opens them.
|
|
2
|
+
|
|
3
|
+
Only :mod:`gratinglab.gui.qt` may import a toolkit. This module stays
|
|
4
|
+
toolkit-free so the console-script entry points, which live beside it, can
|
|
5
|
+
explain a missing PySide6 before anything tries to import it.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from endstation.optional import MissingExtra, require_extra
|
|
11
|
+
|
|
12
|
+
__all__ = ["STILL_WORKS", "exit_without_qt"]
|
|
13
|
+
|
|
14
|
+
#: What a missing toolkit leaves working. Without it, a user who wanted only
|
|
15
|
+
#: the solvers reads an absent GUI extra as a broken install.
|
|
16
|
+
STILL_WORKS = "Everything except the GUI works without it."
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def exit_without_qt() -> None:
|
|
20
|
+
"""Exit with an install instruction if PySide6 is not installed.
|
|
21
|
+
|
|
22
|
+
The check is :func:`endstation.optional.require_extra`, a ``find_spec``
|
|
23
|
+
call rather than an import. Its :class:`~endstation.optional.MissingExtra`
|
|
24
|
+
becomes ``SystemExit`` here, so a console script prints the message and
|
|
25
|
+
not a traceback naming a package the user never asked for.
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
require_extra("PySide6", "gui", still_works=STILL_WORKS)
|
|
29
|
+
except MissingExtra as exc:
|
|
30
|
+
raise SystemExit(str(exc)) from exc
|
|
@@ -15,22 +15,14 @@ from __future__ import annotations
|
|
|
15
15
|
|
|
16
16
|
import sys
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
gratinglab-gui needs PySide6, which this install does not have.
|
|
20
|
-
|
|
21
|
-
pip install -e ".[gui]" (from a checkout)
|
|
22
|
-
pip install gratinglab[gui] (once published)
|
|
23
|
-
|
|
24
|
-
Everything except the GUI works without it.
|
|
25
|
-
"""
|
|
18
|
+
from . import exit_without_qt
|
|
26
19
|
|
|
27
20
|
|
|
28
21
|
def _require_qt():
|
|
29
22
|
"""Import PySide6 with an explanation instead of a traceback."""
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
raise SystemExit(_QT_HELP) from exc
|
|
23
|
+
exit_without_qt()
|
|
24
|
+
from PySide6.QtWidgets import QApplication
|
|
25
|
+
|
|
34
26
|
return QApplication
|
|
35
27
|
|
|
36
28
|
|
|
@@ -28,16 +28,9 @@ from __future__ import annotations
|
|
|
28
28
|
import sys
|
|
29
29
|
from dataclasses import dataclass
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
_QT_HELP = """\
|
|
34
|
-
gratinglab-app needs PySide6, which this install does not have.
|
|
35
|
-
|
|
36
|
-
pip install -e ".[gui]" (from a checkout)
|
|
37
|
-
pip install gratinglab[gui]
|
|
31
|
+
from . import exit_without_qt
|
|
38
32
|
|
|
39
|
-
|
|
40
|
-
"""
|
|
33
|
+
__all__ = ["Mode", "MODES", "mode", "main"]
|
|
41
34
|
|
|
42
35
|
|
|
43
36
|
@dataclass(frozen=True)
|
|
@@ -91,10 +84,9 @@ def mode(key: str) -> Mode:
|
|
|
91
84
|
|
|
92
85
|
def _require_qt():
|
|
93
86
|
"""Import PySide6 with an explanation instead of a traceback."""
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
raise SystemExit(_QT_HELP) from exc
|
|
87
|
+
exit_without_qt()
|
|
88
|
+
from PySide6.QtWidgets import QApplication
|
|
89
|
+
|
|
98
90
|
return QApplication
|
|
99
91
|
|
|
100
92
|
|
|
@@ -14,9 +14,8 @@ headlessly.
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
-
import os
|
|
18
|
-
|
|
19
17
|
# matplotlib's Qt shim picks a binding from sys.modules first and QT_API
|
|
20
|
-
# second. Without
|
|
21
|
-
# canvas would refuse to parent into a PySide6 window.
|
|
22
|
-
|
|
18
|
+
# second. Without QT_API, a stray PyQt5 in the environment would win and its
|
|
19
|
+
# canvas would refuse to parent into a PySide6 window. Importing endstation.qt
|
|
20
|
+
# sets it, with setdefault, before anything here imports matplotlib.
|
|
21
|
+
import endstation.qt # noqa: F401
|
|
@@ -25,6 +25,7 @@ from __future__ import annotations
|
|
|
25
25
|
|
|
26
26
|
import threading
|
|
27
27
|
|
|
28
|
+
from endstation.qt.tokens import StaleGuard
|
|
28
29
|
from PySide6.QtCore import Qt, QThread, QTimer, Signal
|
|
29
30
|
from PySide6.QtGui import QKeySequence
|
|
30
31
|
from PySide6.QtWidgets import (
|
|
@@ -131,12 +132,12 @@ class MainWindow(QMainWindow):
|
|
|
131
132
|
|
|
132
133
|
self._themes = theming.install(QApplication.instance())
|
|
133
134
|
# Solves run on a worker thread; keep the cycle collector off it.
|
|
134
|
-
from .gc_guard import guard
|
|
135
|
+
from endstation.qt.gc_guard import guard
|
|
135
136
|
|
|
136
137
|
guard(self)
|
|
137
138
|
|
|
138
139
|
self._parsed = None
|
|
139
|
-
self.
|
|
140
|
+
self._tokens = StaleGuard()
|
|
140
141
|
self._running = False
|
|
141
142
|
self._active_name: str | None = None
|
|
142
143
|
# Replaced per solve rather than reused, so a Cancel clicked between
|
|
@@ -441,7 +442,7 @@ class MainWindow(QMainWindow):
|
|
|
441
442
|
|
|
442
443
|
self._active_name = name
|
|
443
444
|
self._parsed = geometry
|
|
444
|
-
self.
|
|
445
|
+
token = self._tokens.issue()
|
|
445
446
|
self._cancel = threading.Event()
|
|
446
447
|
self._set_running(True)
|
|
447
448
|
tab.show_solving(len(geometry.wavelengths))
|
|
@@ -455,7 +456,7 @@ class MainWindow(QMainWindow):
|
|
|
455
456
|
_PROGRESS_DELAY_MS, tab, lambda: self._show_progress_if_still_running(tab)
|
|
456
457
|
)
|
|
457
458
|
signal = self._convergence_requested if sweep else self._requested
|
|
458
|
-
signal.emit(
|
|
459
|
+
signal.emit(token, name, geometry, options, self._cancel)
|
|
459
460
|
|
|
460
461
|
def cancel(self) -> None:
|
|
461
462
|
"""Stop the running solve.
|
|
@@ -475,13 +476,13 @@ class MainWindow(QMainWindow):
|
|
|
475
476
|
if not self._running:
|
|
476
477
|
return
|
|
477
478
|
self._cancel.set()
|
|
478
|
-
self.
|
|
479
|
+
self._tokens.invalidate()
|
|
479
480
|
self._set_running(False)
|
|
480
481
|
if self._active_name is not None:
|
|
481
482
|
self.tabs[self._active_name].show_cancelled()
|
|
482
483
|
|
|
483
484
|
def _on_solved(self, token: int, method: str, result) -> None:
|
|
484
|
-
if
|
|
485
|
+
if self._tokens.is_stale(token):
|
|
485
486
|
return # cancelled or superseded; the window has moved on
|
|
486
487
|
self._set_running(False)
|
|
487
488
|
# No `profile_panel.draw` here any more: the groove's shape owes
|
|
@@ -501,7 +502,7 @@ class MainWindow(QMainWindow):
|
|
|
501
502
|
be enough to move a bar backwards over a result the window has already
|
|
502
503
|
replaced.
|
|
503
504
|
"""
|
|
504
|
-
if
|
|
505
|
+
if self._tokens.is_stale(token) or self._active_name is None:
|
|
505
506
|
return
|
|
506
507
|
self.tabs[self._active_name].show_progress_value(done, total)
|
|
507
508
|
|
|
@@ -515,7 +516,7 @@ class MainWindow(QMainWindow):
|
|
|
515
516
|
"""
|
|
516
517
|
|
|
517
518
|
def _on_converged(self, token: int, method: str, result, report) -> None:
|
|
518
|
-
if
|
|
519
|
+
if self._tokens.is_stale(token):
|
|
519
520
|
return
|
|
520
521
|
self._set_running(False)
|
|
521
522
|
self.geometry_tab.show_geometry(self._parsed)
|
|
@@ -525,7 +526,7 @@ class MainWindow(QMainWindow):
|
|
|
525
526
|
self.solved.emit()
|
|
526
527
|
|
|
527
528
|
def _on_failed(self, token: int, method: str, message: str) -> None:
|
|
528
|
-
if
|
|
529
|
+
if self._tokens.is_stale(token):
|
|
529
530
|
return
|
|
530
531
|
self._set_running(False)
|
|
531
532
|
self.tabs[method].show_error(message)
|
|
@@ -10,7 +10,8 @@ only the latter, because ``sweep`` reaches a NumPy-bound call with no check
|
|
|
10
10
|
point and a Python thread cannot be killed, so there was nothing to interrupt
|
|
11
11
|
from here. `Capabilities.reports_progress` changed that: the solver calls back
|
|
12
12
|
once per wavelength, and a callback that raises
|
|
13
|
-
:class:`~
|
|
13
|
+
:class:`~endstation.progress.Cancelled` (or its subclass
|
|
14
|
+
:class:`~gratinglab.solvers.base.SolveCancelled`) unwinds it.
|
|
14
15
|
|
|
15
16
|
The token is still needed, because real cancellation does not remove the race
|
|
16
17
|
it was covering. The worker may already have finished when Cancel is clicked,
|
|
@@ -28,10 +29,9 @@ from __future__ import annotations
|
|
|
28
29
|
import threading
|
|
29
30
|
from typing import TYPE_CHECKING, NamedTuple
|
|
30
31
|
|
|
32
|
+
from endstation.progress import Cancelled, cancelling, throttled
|
|
31
33
|
from PySide6.QtCore import QObject, Signal, Slot
|
|
32
34
|
|
|
33
|
-
from ...solvers.base import SolveCancelled
|
|
34
|
-
|
|
35
35
|
if TYPE_CHECKING: # pragma: no cover - imports for type checkers only
|
|
36
36
|
from ...checks import EnergyReport
|
|
37
37
|
from ...result import EfficiencyScan
|
|
@@ -123,7 +123,7 @@ class SolveWorker(QObject):
|
|
|
123
123
|
progress=self._reporter(token, cancel),
|
|
124
124
|
)[0]
|
|
125
125
|
self.finished.emit(token, method, SolveResult(scan, _energy(scan)))
|
|
126
|
-
except
|
|
126
|
+
except Cancelled:
|
|
127
127
|
# Before the broad handler: the user asked for this, so
|
|
128
128
|
# it is not a failure and rendering it as one would be the same
|
|
129
129
|
# category mistake M8 fixed for the no-coating default.
|
|
@@ -176,7 +176,7 @@ class SolveWorker(QObject):
|
|
|
176
176
|
SolveResult(report.scan, _energy(report.scan)),
|
|
177
177
|
report,
|
|
178
178
|
)
|
|
179
|
-
except
|
|
179
|
+
except Cancelled:
|
|
180
180
|
self.cancelled.emit(token, method)
|
|
181
181
|
except Exception as exc: # noqa: BLE001 - see `run`
|
|
182
182
|
self.failed.emit(token, method, f"{type(exc).__name__}: {exc}")
|
|
@@ -190,16 +190,14 @@ class SolveWorker(QObject):
|
|
|
190
190
|
that could not have been seen. The cancellation check is not
|
|
191
191
|
throttled: it runs on every call, so a Cancel click takes effect at
|
|
192
192
|
the next wavelength instead of the next percent.
|
|
193
|
+
|
|
194
|
+
Both halves are endstation's: :func:`~endstation.progress.throttled`
|
|
195
|
+
for the bar and :func:`~endstation.progress.cancelling` for the stop,
|
|
196
|
+
which raises :class:`~endstation.progress.Cancelled`, the base class
|
|
197
|
+
of :class:`~gratinglab.solvers.base.SolveCancelled`.
|
|
193
198
|
"""
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if percent != last:
|
|
200
|
-
last = percent
|
|
201
|
-
self.progress.emit(token, done, total)
|
|
202
|
-
if cancel.is_set():
|
|
203
|
-
raise SolveCancelled(f"cancelled at {done} of {total}")
|
|
204
|
-
|
|
205
|
-
return report
|
|
199
|
+
|
|
200
|
+
def emit(done: int, total: int) -> None:
|
|
201
|
+
self.progress.emit(token, done, total)
|
|
202
|
+
|
|
203
|
+
return cancelling(throttled(emit), cancel.is_set)
|
|
@@ -20,6 +20,7 @@ from __future__ import annotations
|
|
|
20
20
|
from dataclasses import dataclass, replace
|
|
21
21
|
|
|
22
22
|
from endstation.forms import FieldError as _FieldError
|
|
23
|
+
from endstation.forms import FormErrors as _FormErrors
|
|
23
24
|
from pathlib import Path
|
|
24
25
|
from typing import Any
|
|
25
26
|
|
|
@@ -79,13 +80,11 @@ ANGLE_LABELS: dict[str, tuple[str, str | None]] = {
|
|
|
79
80
|
#: from the module it is used in.
|
|
80
81
|
FieldError = _FieldError
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
self.errors = errors
|
|
88
|
-
super().__init__("; ".join(str(e) for e in errors))
|
|
83
|
+
#: Raised by :func:`build`, and by each tab's ``build_options``, when a form
|
|
84
|
+
#: cannot be turned into a problem. endstation's, re-exported for the same
|
|
85
|
+
#: reason as `FieldError`. It derives from ``Exception``, not ``ValueError``
|
|
86
|
+
#: as this module's own copy did; nothing here caught it as a ValueError.
|
|
87
|
+
FormErrors = _FormErrors
|
|
89
88
|
|
|
90
89
|
|
|
91
90
|
@dataclass(frozen=True, slots=True)
|
|
@@ -3,6 +3,8 @@ Configuration file for AFM blaze angle analysis
|
|
|
3
3
|
"""
|
|
4
4
|
import os
|
|
5
5
|
|
|
6
|
+
from endstation.paths import data_root, find_project_root
|
|
7
|
+
|
|
6
8
|
# ============ PROJECT PATHS ============
|
|
7
9
|
# All paths below are resolved relative to the project root, not the current
|
|
8
10
|
# working directory, so the analysis runs the same from anywhere.
|
|
@@ -16,22 +18,16 @@ import os
|
|
|
16
18
|
# An installed (non-editable) copy has no project root above it; fall back to the
|
|
17
19
|
# working directory so `pip install gratinglab[metrology]` still runs against a user's
|
|
18
20
|
# own data and results folders.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if parent == path:
|
|
30
|
-
return os.getcwd()
|
|
31
|
-
path = parent
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
PROJECT_ROOT = _find_project_root(__file__)
|
|
21
|
+
#
|
|
22
|
+
# pyproject.toml only, not endstation's default markers. Searching for a "data"
|
|
23
|
+
# directory looked reasonable and was wrong: this package contains its own data
|
|
24
|
+
# subpackage (gratinglab/metrology/data/aggregation.py), so the walk stopped
|
|
25
|
+
# immediately and DATA_DIR pointed inside the source tree. A `.git` marker is
|
|
26
|
+
# no better: an installed copy inside some other checkout would claim that
|
|
27
|
+
# checkout's root.
|
|
28
|
+
#
|
|
29
|
+
# Kept as a str, as it always was: every consumer builds paths with os.path.
|
|
30
|
+
PROJECT_ROOT = str(find_project_root(__file__, markers=("pyproject.toml",)))
|
|
35
31
|
RESULTS_DIR = os.path.join(PROJECT_ROOT, 'results')
|
|
36
32
|
|
|
37
33
|
# Scans live *outside* the checkout, and are not committed.
|
|
@@ -45,7 +41,7 @@ RESULTS_DIR = os.path.join(PROJECT_ROOT, 'results')
|
|
|
45
41
|
#
|
|
46
42
|
# Point GRATINGLAB_AFM_DIR somewhere else to override.
|
|
47
43
|
DEFAULT_AFM_DIR = os.path.join(os.path.expanduser('~'), 'Documents', 'afm_scans')
|
|
48
|
-
DATA_DIR =
|
|
44
|
+
DATA_DIR = str(data_root('GRATINGLAB_AFM_DIR', DEFAULT_AFM_DIR))
|
|
49
45
|
|
|
50
46
|
|
|
51
47
|
def resolve_path(name):
|
|
@@ -23,15 +23,16 @@ Importing `gui.qt` requires PySide6, which is an optional extra::
|
|
|
23
23
|
"""
|
|
24
24
|
from __future__ import annotations
|
|
25
25
|
|
|
26
|
+
from endstation.optional import missing_extra_message, require_extra
|
|
27
|
+
|
|
26
28
|
__all__ = ["main", "require_qt", "QT_MISSING_MESSAGE"]
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
"the Qt interface needs PySide6, which is an optional extra.\n"
|
|
30
|
-
"Install it with:\n\n"
|
|
31
|
-
" pip install -e '.[gui]'\n\n"
|
|
30
|
+
_STILL_WORKS = (
|
|
32
31
|
"The analysis, the .ggp export and the ICC diagnostic all work without it."
|
|
33
32
|
)
|
|
34
33
|
|
|
34
|
+
QT_MISSING_MESSAGE = missing_extra_message("PySide6", "gui", still_works=_STILL_WORKS)
|
|
35
|
+
|
|
35
36
|
|
|
36
37
|
def require_qt() -> None:
|
|
37
38
|
"""Raise something useful rather than a bare ImportError.
|
|
@@ -39,16 +40,13 @@ def require_qt() -> None:
|
|
|
39
40
|
``ModuleNotFoundError: No module named 'PySide6'`` tells a user nothing about
|
|
40
41
|
what to do, and this is the most likely first-run failure.
|
|
41
42
|
|
|
42
|
-
The check
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
The check is :func:`endstation.optional.require_extra`, a ``find_spec``
|
|
44
|
+
call rather than an import, because this module sits outside `gui/qt` and
|
|
45
|
+
the rule that only `gui/qt` touches a toolkit is enforced by
|
|
46
|
+
`tests/test_gui_boundary.py`. It raises
|
|
47
|
+
:class:`~endstation.optional.MissingExtra`, a ``ModuleNotFoundError``.
|
|
47
48
|
"""
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if find_spec("PySide6") is None: # pragma: no cover - depends on environment
|
|
51
|
-
raise ModuleNotFoundError(QT_MISSING_MESSAGE)
|
|
49
|
+
require_extra("PySide6", "gui", still_works=_STILL_WORKS)
|
|
52
50
|
|
|
53
51
|
|
|
54
52
|
def main(argv=None):
|
|
@@ -10,10 +10,12 @@ it belongs in `gui/state.py`, which is pure and tested headlessly.
|
|
|
10
10
|
"""
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
-
import os
|
|
14
|
-
|
|
15
13
|
# matplotlib's Qt shim picks a binding from sys.modules first and QT_API second.
|
|
16
|
-
# Without
|
|
17
|
-
# refuse to parent into a PySide6 window. This project shipped PyQt5
|
|
18
|
-
# recently, so that stray binding is a realistic thing to find installed.
|
|
19
|
-
|
|
14
|
+
# Without QT_API, a stray PyQt5 in the environment would win and its canvas
|
|
15
|
+
# would refuse to parent into a PySide6 window. This project shipped PyQt5
|
|
16
|
+
# until recently, so that stray binding is a realistic thing to find installed.
|
|
17
|
+
# Importing endstation.qt sets it, with setdefault; the modules here run
|
|
18
|
+
# `from . import *` first thing to get this import, so it exports nothing.
|
|
19
|
+
import endstation.qt # noqa: F401
|
|
20
|
+
|
|
21
|
+
__all__: list[str] = []
|
|
@@ -14,6 +14,7 @@ import os
|
|
|
14
14
|
|
|
15
15
|
from . import * # noqa: F401,F403 - sets QT_API before matplotlib's shim loads
|
|
16
16
|
|
|
17
|
+
from endstation.qt.tokens import StaleGuard
|
|
17
18
|
from PySide6.QtCore import Qt, QThread, Signal
|
|
18
19
|
from PySide6.QtGui import QFont
|
|
19
20
|
from PySide6.QtWidgets import (
|
|
@@ -49,7 +50,7 @@ class MainWindow(QMainWindow):
|
|
|
49
50
|
self.resize(1280, 760)
|
|
50
51
|
# The analysis runs on a worker thread, and a cycle collection there
|
|
51
52
|
# once destroyed Qt objects off the GUI thread: a segfault on macOS.
|
|
52
|
-
from
|
|
53
|
+
from endstation.qt.gc_guard import guard
|
|
53
54
|
|
|
54
55
|
guard(self)
|
|
55
56
|
|
|
@@ -61,7 +62,7 @@ class MainWindow(QMainWindow):
|
|
|
61
62
|
self._filename = None
|
|
62
63
|
self._result = None
|
|
63
64
|
self._settings = None
|
|
64
|
-
self.
|
|
65
|
+
self._tokens = StaleGuard() # abandons an in-flight result
|
|
65
66
|
|
|
66
67
|
self._build_ui()
|
|
67
68
|
self._start_worker()
|
|
@@ -78,7 +79,7 @@ class MainWindow(QMainWindow):
|
|
|
78
79
|
self._thread.start()
|
|
79
80
|
|
|
80
81
|
def closeEvent(self, event):
|
|
81
|
-
self.
|
|
82
|
+
self._tokens.invalidate()
|
|
82
83
|
self._thread.quit()
|
|
83
84
|
self._thread.wait(3000)
|
|
84
85
|
super().closeEvent(event)
|
|
@@ -547,13 +548,13 @@ class MainWindow(QMainWindow):
|
|
|
547
548
|
self._set_status("Fix the highlighted settings and try again.")
|
|
548
549
|
return
|
|
549
550
|
|
|
550
|
-
self.
|
|
551
|
+
token = self._tokens.issue()
|
|
551
552
|
self.run_btn.setEnabled(False)
|
|
552
553
|
self._set_status("Running analysis…")
|
|
553
|
-
self._request_analysis.emit(self._filename, settings,
|
|
554
|
+
self._request_analysis.emit(self._filename, settings, token)
|
|
554
555
|
|
|
555
556
|
def _on_finished(self, result, settings, token):
|
|
556
|
-
if
|
|
557
|
+
if self._tokens.is_stale(token):
|
|
557
558
|
return # a later run superseded this one
|
|
558
559
|
self.run_btn.setEnabled(True)
|
|
559
560
|
|
|
@@ -581,7 +582,7 @@ class MainWindow(QMainWindow):
|
|
|
581
582
|
f"mean {result['mean_angle']:.2f}°")
|
|
582
583
|
|
|
583
584
|
def _on_failed(self, message, token):
|
|
584
|
-
if
|
|
585
|
+
if self._tokens.is_stale(token):
|
|
585
586
|
return
|
|
586
587
|
self.run_btn.setEnabled(True)
|
|
587
588
|
self._result = self._settings = None
|
|
@@ -17,6 +17,8 @@ import re
|
|
|
17
17
|
from dataclasses import dataclass
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
|
|
20
|
+
from endstation.docs.index import load_pages
|
|
21
|
+
|
|
20
22
|
__all__ = ["WikiPage", "pages", "page", "PAGE_ORDER"]
|
|
21
23
|
|
|
22
24
|
WIKI_DIR = Path(__file__).resolve().parent
|
|
@@ -57,20 +59,6 @@ class WikiPage:
|
|
|
57
59
|
return not body.strip()
|
|
58
60
|
|
|
59
61
|
|
|
60
|
-
def _title_of(markdown: str, slug: str) -> str:
|
|
61
|
-
"""The leading `# ` heading, or a readable fallback built from the slug."""
|
|
62
|
-
match = _TITLE_RE.search(markdown)
|
|
63
|
-
if match:
|
|
64
|
-
return match.group(1).strip()
|
|
65
|
-
return slug.replace("-", " ").capitalize()
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def _load(path: Path) -> WikiPage:
|
|
69
|
-
markdown = path.read_text(encoding="utf-8")
|
|
70
|
-
slug = path.stem
|
|
71
|
-
return WikiPage(slug=slug, title=_title_of(markdown, slug), markdown=markdown)
|
|
72
|
-
|
|
73
|
-
|
|
74
62
|
def pages() -> tuple[WikiPage, ...]:
|
|
75
63
|
"""
|
|
76
64
|
Every wiki page, in reading order.
|
|
@@ -79,13 +67,15 @@ def pages() -> tuple[WikiPage, ...]:
|
|
|
79
67
|
import, so editing a page and reopening the tab shows the edit. The pages
|
|
80
68
|
are prose under active revision, and a stale cache would be confusing to
|
|
81
69
|
debug.
|
|
82
|
-
"""
|
|
83
|
-
found = {path.stem: path for path in sorted(WIKI_DIR.glob("*.md"))}
|
|
84
70
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
71
|
+
Which pages exist, their order and their titles come from
|
|
72
|
+
:func:`endstation.docs.index.load_pages`, which applies the rules
|
|
73
|
+
`PAGE_ORDER` describes.
|
|
74
|
+
"""
|
|
75
|
+
return tuple(
|
|
76
|
+
WikiPage(slug=entry.slug, title=entry.title, markdown=entry.read())
|
|
77
|
+
for entry in load_pages(WIKI_DIR, PAGE_ORDER)
|
|
78
|
+
)
|
|
89
79
|
|
|
90
80
|
|
|
91
81
|
def page(slug: str) -> WikiPage | None:
|
|
@@ -20,6 +20,7 @@ from dataclasses import dataclass
|
|
|
20
20
|
from importlib.metadata import entry_points
|
|
21
21
|
from typing import Callable, Protocol, runtime_checkable
|
|
22
22
|
|
|
23
|
+
from endstation.progress import Cancelled
|
|
23
24
|
from numpy.typing import ArrayLike
|
|
24
25
|
|
|
25
26
|
from ..illumination import Illumination, Polarization
|
|
@@ -46,7 +47,7 @@ class UnsupportedConfiguration(NotImplementedError):
|
|
|
46
47
|
"""A solver was asked for something outside its declared capabilities."""
|
|
47
48
|
|
|
48
49
|
|
|
49
|
-
class SolveCancelled(
|
|
50
|
+
class SolveCancelled(Cancelled):
|
|
50
51
|
"""Raised by a caller's progress callback to abandon a solve.
|
|
51
52
|
|
|
52
53
|
A Python thread cannot be killed and a NumPy-bound call has no check
|
|
@@ -65,6 +66,12 @@ class SolveCancelled(Exception):
|
|
|
65
66
|
aborting the process, and an unhandled ``BaseException`` on a worker
|
|
66
67
|
thread is a worse failure than the one being guarded against. The
|
|
67
68
|
contract is therefore stated and tested.
|
|
69
|
+
|
|
70
|
+
It is a subclass of :class:`endstation.progress.Cancelled`, the same
|
|
71
|
+
convention in the shared library, so code that catches ``Cancelled``
|
|
72
|
+
catches this too. The window's own callback raises ``Cancelled`` itself,
|
|
73
|
+
through :func:`endstation.progress.cancelling`; a solver lets either
|
|
74
|
+
through, as rule 3 of :class:`Solver` says of any exception.
|
|
68
75
|
"""
|
|
69
76
|
|
|
70
77
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gratinglab
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Open platform for diffraction grating groove metrology and efficiency analysis
|
|
5
5
|
Author: Jake McCoy, Aditi Gangadharan
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -22,7 +22,7 @@ License-File: LICENSE
|
|
|
22
22
|
Requires-Dist: numpy>=1.26
|
|
23
23
|
Requires-Dist: scipy>=1.11
|
|
24
24
|
Requires-Dist: pydantic>=2.6
|
|
25
|
-
Requires-Dist: endstation>=0.2.
|
|
25
|
+
Requires-Dist: endstation>=0.2.6
|
|
26
26
|
Requires-Dist: threadpoolctl>=3.1
|
|
27
27
|
Provides-Extra: dev
|
|
28
28
|
Requires-Dist: pytest>=7; extra == "dev"
|
|
@@ -34,7 +34,7 @@ Provides-Extra: gui
|
|
|
34
34
|
Requires-Dist: matplotlib>=3.8; extra == "gui"
|
|
35
35
|
Requires-Dist: PySide6-Essentials!=6.12.0,>=6.7; python_version < "3.12" and extra == "gui"
|
|
36
36
|
Requires-Dist: PySide6-Essentials>=6.7; python_version >= "3.12" and extra == "gui"
|
|
37
|
-
Requires-Dist: endstation[qt]>=0.2.
|
|
37
|
+
Requires-Dist: endstation[qt]>=0.2.6; extra == "gui"
|
|
38
38
|
Provides-Extra: metrology
|
|
39
39
|
Requires-Dist: matplotlib>=3.9; extra == "metrology"
|
|
40
40
|
Provides-Extra: refractiveindex
|
|
@@ -38,7 +38,6 @@ src/gratinglab/gui/state.py
|
|
|
38
38
|
src/gratinglab/gui/theme.py
|
|
39
39
|
src/gratinglab/gui/qt/__init__.py
|
|
40
40
|
src/gratinglab/gui/qt/cmethod_tab.py
|
|
41
|
-
src/gratinglab/gui/qt/gc_guard.py
|
|
42
41
|
src/gratinglab/gui/qt/geometry_panel.py
|
|
43
42
|
src/gratinglab/gui/qt/geometry_tab.py
|
|
44
43
|
src/gratinglab/gui/qt/integral_tab.py
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
numpy>=1.26
|
|
2
2
|
scipy>=1.11
|
|
3
3
|
pydantic>=2.6
|
|
4
|
-
endstation>=0.2.
|
|
4
|
+
endstation>=0.2.6
|
|
5
5
|
threadpoolctl>=3.1
|
|
6
6
|
|
|
7
7
|
[dev]
|
|
@@ -13,7 +13,7 @@ ruff>=0.16
|
|
|
13
13
|
|
|
14
14
|
[gui]
|
|
15
15
|
matplotlib>=3.8
|
|
16
|
-
endstation[qt]>=0.2.
|
|
16
|
+
endstation[qt]>=0.2.6
|
|
17
17
|
|
|
18
18
|
[gui:python_version < "3.12"]
|
|
19
19
|
PySide6-Essentials!=6.12.0,>=6.7
|