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.
Files changed (193) hide show
  1. {gratinglab-0.2.1/src/gratinglab.egg-info → gratinglab-0.2.2}/PKG-INFO +3 -3
  2. {gratinglab-0.2.1 → gratinglab-0.2.2}/pyproject.toml +2 -2
  3. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/__init__.py +1 -1
  4. gratinglab-0.2.2/src/gratinglab/gui/__init__.py +30 -0
  5. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/app.py +4 -12
  6. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/launcher.py +5 -13
  7. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/__init__.py +4 -5
  8. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/main_window.py +10 -9
  9. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/worker.py +15 -17
  10. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/state.py +6 -7
  11. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/config.py +13 -17
  12. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/__init__.py +11 -13
  13. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/__init__.py +8 -6
  14. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/main_window.py +8 -7
  15. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/__init__.py +10 -20
  16. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/base.py +8 -1
  17. {gratinglab-0.2.1 → gratinglab-0.2.2/src/gratinglab.egg-info}/PKG-INFO +3 -3
  18. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab.egg-info/SOURCES.txt +0 -1
  19. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab.egg-info/requires.txt +2 -2
  20. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_qt.py +35 -30
  21. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_solver_progress.py +27 -0
  22. gratinglab-0.2.1/src/gratinglab/gui/qt/gc_guard.py +0 -102
  23. gratinglab-0.2.1/src/gratinglab/io/__init__.py +0 -0
  24. {gratinglab-0.2.1 → gratinglab-0.2.2}/LICENSE +0 -0
  25. {gratinglab-0.2.1 → gratinglab-0.2.2}/README.md +0 -0
  26. {gratinglab-0.2.1 → gratinglab-0.2.2}/setup.cfg +0 -0
  27. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/checks.py +0 -0
  28. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/compare.py +0 -0
  29. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/convergence.py +0 -0
  30. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/geometry.py +0 -0
  31. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/cmethod_options.py +0 -0
  32. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/diagram.py +0 -0
  33. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/diagram3d.py +0 -0
  34. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/docs.py +0 -0
  35. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/efficiency_plot.py +0 -0
  36. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/icon.py +0 -0
  37. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/integral_options.py +0 -0
  38. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/mathtext.py +0 -0
  39. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/orders.py +0 -0
  40. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/provenance.py +0 -0
  41. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/cmethod_tab.py +0 -0
  42. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/geometry_panel.py +0 -0
  43. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/geometry_tab.py +0 -0
  44. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/integral_tab.py +0 -0
  45. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/launcher.py +0 -0
  46. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/profile_plot_panel.py +0 -0
  47. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/rcwa_tab.py +0 -0
  48. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/scalar_tab.py +0 -0
  49. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/scene3d_panel.py +0 -0
  50. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/setup_tab.py +0 -0
  51. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/sizing.py +0 -0
  52. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/theming.py +0 -0
  53. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/qt/theory_viewer.py +0 -0
  54. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/rcwa_options.py +0 -0
  55. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/richtext.py +0 -0
  56. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/scalar_options.py +0 -0
  57. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/gui/theme.py +0 -0
  58. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/illumination.py +0 -0
  59. {gratinglab-0.2.1/src/gratinglab/gui → gratinglab-0.2.2/src/gratinglab/io}/__init__.py +0 -0
  60. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/io/boundary.py +0 -0
  61. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/io/efficiency_table.py +0 -0
  62. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/io/ggp.py +0 -0
  63. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/materials/__init__.py +0 -0
  64. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/materials/data/SOURCES.md +0 -0
  65. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/materials/fresnel.py +0 -0
  66. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/materials/optical.py +0 -0
  67. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/materials/refractiveindex.py +0 -0
  68. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/measured.py +0 -0
  69. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/__init__.py +0 -0
  70. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/analyzer.py +0 -0
  71. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/boundary/__init__.py +0 -0
  72. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/boundary/average.py +0 -0
  73. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/boundary/pipeline.py +0 -0
  74. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/boundary/swaths.py +0 -0
  75. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/cli.py +0 -0
  76. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/core/__init__.py +0 -0
  77. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/core/analysis.py +0 -0
  78. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/core/image_flatten.py +0 -0
  79. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/core/orientation.py +0 -0
  80. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/core/processing.py +0 -0
  81. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/core/region_mask.py +0 -0
  82. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/core/scars.py +0 -0
  83. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/core/tip.py +0 -0
  84. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/data/__init__.py +0 -0
  85. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/data/aggregation.py +0 -0
  86. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/icon.py +0 -0
  87. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/app.py +0 -0
  88. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/boundary_view.py +0 -0
  89. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/canvas.py +0 -0
  90. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/efficiency_dialog.py +0 -0
  91. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/import_view.py +0 -0
  92. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/swath_dialog.py +0 -0
  93. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/wiki_view.py +0 -0
  94. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/qt/worker.py +0 -0
  95. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/gui/state.py +0 -0
  96. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/io/__init__.py +0 -0
  97. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/io/file_io.py +0 -0
  98. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/io/ibw.py +0 -0
  99. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/io/metrics.py +0 -0
  100. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/io/spm.py +0 -0
  101. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/settings.py +0 -0
  102. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/stats/__init__.py +0 -0
  103. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/stats/analysis.py +0 -0
  104. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/stats/icc.py +0 -0
  105. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/visualization/__init__.py +0 -0
  106. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/visualization/diagnostics.py +0 -0
  107. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/visualization/profiles.py +0 -0
  108. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/visualization/statistics.py +0 -0
  109. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/facet-fitting.md +0 -0
  110. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/file-formats.md +0 -0
  111. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/flattening.md +0 -0
  112. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/groove-orientation.md +0 -0
  113. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/icc-correction.md +0 -0
  114. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/overview.md +0 -0
  115. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/reading-outputs.md +0 -0
  116. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/row-groups.md +0 -0
  117. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/scars-and-masking.md +0 -0
  118. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/swath-statistics.md +0 -0
  119. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/wiki/tip-correction.md +0 -0
  120. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/metrology/workflows.py +0 -0
  121. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/problem.py +0 -0
  122. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/profiles.py +0 -0
  123. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/resolution.py +0 -0
  124. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/result.py +0 -0
  125. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/__init__.py +0 -0
  126. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/_conditioning.py +0 -0
  127. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/_execution.py +0 -0
  128. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/cmethod/__init__.py +0 -0
  129. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/cmethod/_core.py +0 -0
  130. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/cmethod/_series.py +0 -0
  131. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/integral/__init__.py +0 -0
  132. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/integral/_boundary.py +0 -0
  133. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/integral/_core.py +0 -0
  134. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/integral/_finite.py +0 -0
  135. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/integral/_greens.py +0 -0
  136. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/integral/_kernels.py +0 -0
  137. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/integral/_nystrom.py +0 -0
  138. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/rcwa/__init__.py +0 -0
  139. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/rcwa/_core.py +0 -0
  140. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/rcwa/_stack.py +0 -0
  141. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab/solvers/scalar.py +0 -0
  142. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab.egg-info/dependency_links.txt +0 -0
  143. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab.egg-info/entry_points.txt +0 -0
  144. {gratinglab-0.2.1 → gratinglab-0.2.2}/src/gratinglab.egg-info/top_level.txt +0 -0
  145. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_changelog.py +0 -0
  146. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_checks.py +0 -0
  147. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_cmethod.py +0 -0
  148. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_compare.py +0 -0
  149. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_conditioning.py +0 -0
  150. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_convergence.py +0 -0
  151. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_cross_method.py +0 -0
  152. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_efficiency_table.py +0 -0
  153. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_fresnel.py +0 -0
  154. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_geometry.py +0 -0
  155. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_ggp.py +0 -0
  156. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_cmethod_options.py +0 -0
  157. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_cmethod_tab.py +0 -0
  158. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_diagram.py +0 -0
  159. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_diagram3d.py +0 -0
  160. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_docs.py +0 -0
  161. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_integral_options.py +0 -0
  162. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_launcher.py +0 -0
  163. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_mathtext.py +0 -0
  164. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_orders.py +0 -0
  165. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_provenance.py +0 -0
  166. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_rcwa_options.py +0 -0
  167. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_richtext.py +0 -0
  168. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_scalar_options.py +0 -0
  169. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_state.py +0 -0
  170. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_gui_theme.py +0 -0
  171. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_illumination.py +0 -0
  172. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_integral_core.py +0 -0
  173. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_integral_corpus.py +0 -0
  174. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_integral_finite.py +0 -0
  175. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_integral_graded.py +0 -0
  176. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_integral_greens.py +0 -0
  177. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_integral_kernels.py +0 -0
  178. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_integral_operators.py +0 -0
  179. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_integral_solver.py +0 -0
  180. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_io_boundary.py +0 -0
  181. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_materials.py +0 -0
  182. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_measured.py +0 -0
  183. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_perturbation.py +0 -0
  184. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_profiles.py +0 -0
  185. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_rayleigh_anomaly.py +0 -0
  186. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_rcwa.py +0 -0
  187. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_rcwa_benchmarks.py +0 -0
  188. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_refractiveindex.py +0 -0
  189. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_resolution.py +0 -0
  190. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_result.py +0 -0
  191. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_scalar.py +0 -0
  192. {gratinglab-0.2.1 → gratinglab-0.2.2}/tests/test_solver_conformance.py +0 -0
  193. {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.1
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.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.2; extra == "gui"
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.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.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.1"
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
- _QT_HELP = """\
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
- try:
31
- from PySide6.QtWidgets import QApplication
32
- except ImportError as exc: # pragma: no cover - depends on the install
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
- __all__ = ["Mode", "MODES", "mode", "main"]
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
- Everything except the GUI works without it.
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
- try:
95
- from PySide6.QtWidgets import QApplication
96
- except ImportError as exc: # pragma: no cover - depends on the install
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 this, a stray PyQt5 in the environment would win and its
21
- # canvas would refuse to parent into a PySide6 window.
22
- os.environ.setdefault("QT_API", "PySide6")
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._token = 0
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._token += 1
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(self._token, name, geometry, options, self._cancel)
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._token += 1
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 token != self._token:
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 token != self._token or self._active_name is None:
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 token != self._token:
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 token != self._token:
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:`~gratinglab.solvers.base.SolveCancelled` unwinds it.
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 SolveCancelled:
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 SolveCancelled:
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
- last = -1
195
-
196
- def report(done: int, total: int) -> None:
197
- nonlocal last
198
- percent = done * 100 // total if total else 100
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
- class FormErrors(ValueError):
84
- """Raised by :func:`build` when a form cannot be turned into a problem."""
85
-
86
- def __init__(self, errors: tuple[FieldError, ...]) -> None:
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
- def _find_project_root(start):
20
- # pyproject.toml only. Searching for a "data" directory looked reasonable and
21
- # was wrong: this package contains its own data subpackage
22
- # (gratinglab/metrology/data/aggregation.py), so the walk stopped immediately and
23
- # DATA_DIR pointed inside the source tree.
24
- path = os.path.dirname(os.path.abspath(start))
25
- while True:
26
- if os.path.isfile(os.path.join(path, 'pyproject.toml')):
27
- return path
28
- parent = os.path.dirname(path)
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 = os.environ.get('GRATINGLAB_AFM_DIR') or DEFAULT_AFM_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
- QT_MISSING_MESSAGE = (
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 uses find_spec instead of an import because this module sits
43
- outside `gui/qt`, and the rule that only `gui/qt` touches a toolkit is
44
- enforced by `tests/test_gui_boundary.py`. Checking availability should
45
- also not cost a full toolkit import, since the aim is to fail quickly and
46
- clearly.
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
- from importlib.util import find_spec
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 this, a stray PyQt5 in the environment would win and its canvas would
17
- # refuse to parent into a PySide6 window. This project shipped PyQt5 until
18
- # recently, so that stray binding is a realistic thing to find installed.
19
- os.environ.setdefault("QT_API", "PySide6")
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 gratinglab.gui.qt.gc_guard import guard
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._token = 0 # bumped to abandon an in-flight result
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._token += 1
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._token += 1
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, self._token)
554
+ self._request_analysis.emit(self._filename, settings, token)
554
555
 
555
556
  def _on_finished(self, result, settings, token):
556
- if token != self._token:
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 token != self._token:
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
- ordered = [found.pop(slug) for slug in PAGE_ORDER if slug in found]
86
- ordered.extend(found[slug] for slug in sorted(found))
87
-
88
- return tuple(_load(path) for path in ordered)
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(Exception):
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.1
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.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.2; extra == "gui"
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.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.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