finalcif 161__tar.gz → 162__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 (184) hide show
  1. {finalcif-161 → finalcif-162}/AGENTS.md +7 -5
  2. {finalcif-161 → finalcif-162}/PKG-INFO +6 -6
  3. finalcif-162/finalcif/__init__.py +3 -0
  4. {finalcif-161 → finalcif-162}/finalcif/appwindow.py +112 -0
  5. {finalcif-161 → finalcif-162}/finalcif/cif/cif_file_io.py +6 -2
  6. {finalcif-161 → finalcif-162}/finalcif/gui/finalcif_gui_ui.py +1040 -1024
  7. {finalcif-161 → finalcif-162}/finalcif/gui/finalcif_gui_ui.ui +924 -909
  8. finalcif-162/finalcif/tools/z_from_packing.py +631 -0
  9. {finalcif-161 → finalcif-162}/pyproject.toml +5 -5
  10. {finalcif-161 → finalcif-162}/uv.lock +72 -81
  11. finalcif-162/work/diagnose_1519506.py +109 -0
  12. finalcif-162/work/diagnose_z.py +26 -0
  13. finalcif-162/work/investigate_failures.py +73 -0
  14. finalcif-162/work/validate_cod.py +202 -0
  15. finalcif-162/work/validate_zprime.py +110 -0
  16. finalcif-161/finalcif/__init__.py +0 -3
  17. {finalcif-161 → finalcif-162}/.gitattributes +0 -0
  18. {finalcif-161 → finalcif-162}/.githooks/pre-push +0 -0
  19. {finalcif-161 → finalcif-162}/.gitignore +0 -0
  20. {finalcif-161 → finalcif-162}/.python-version +0 -0
  21. {finalcif-161 → finalcif-162}/.ruff.toml +0 -0
  22. {finalcif-161 → finalcif-162}/.run/Make Documentation MAC.run.xml +0 -0
  23. {finalcif-161 → finalcif-162}/.run/compile_ui_files.run.xml +0 -0
  24. {finalcif-161 → finalcif-162}/.run/make documentation Windows.run.xml +0 -0
  25. {finalcif-161 → finalcif-162}/CODE_OF_CONDUCT.md +0 -0
  26. {finalcif-161 → finalcif-162}/CONTRIBUTING.md +0 -0
  27. {finalcif-161 → finalcif-162}/Finalcif_installer_win.spec +0 -0
  28. {finalcif-161 → finalcif-162}/Finalcif_linux_onefile.spec +0 -0
  29. {finalcif-161 → finalcif-162}/Finalcif_mac.spec +0 -0
  30. {finalcif-161 → finalcif-162}/LICENSE +0 -0
  31. {finalcif-161 → finalcif-162}/README.md +0 -0
  32. {finalcif-161 → finalcif-162}/finalcif/app_path.py +0 -0
  33. {finalcif-161 → finalcif-162}/finalcif/cif/__init__.py +0 -0
  34. {finalcif-161 → finalcif-162}/finalcif/cif/all_cif_dicts.py +0 -0
  35. {finalcif-161 → finalcif-162}/finalcif/cif/atoms.py +0 -0
  36. {finalcif-161 → finalcif-162}/finalcif/cif/checkcif/__init__.py +0 -0
  37. {finalcif-161 → finalcif-162}/finalcif/cif/checkcif/checkcif.py +0 -0
  38. {finalcif-161 → finalcif-162}/finalcif/cif/cif_dict_foo.py +0 -0
  39. {finalcif-161 → finalcif-162}/finalcif/cif/cif_order.py +0 -0
  40. {finalcif-161 → finalcif-162}/finalcif/cif/cod/__init__.py +0 -0
  41. {finalcif-161 → finalcif-162}/finalcif/cif/cod/deposit.py +0 -0
  42. {finalcif-161 → finalcif-162}/finalcif/cif/cod/deposit_check.py +0 -0
  43. {finalcif-161 → finalcif-162}/finalcif/cif/cod/deposition_list.py +0 -0
  44. {finalcif-161 → finalcif-162}/finalcif/cif/cod/doi.py +0 -0
  45. {finalcif-161 → finalcif-162}/finalcif/cif/cod/upload.py +0 -0
  46. {finalcif-161 → finalcif-162}/finalcif/cif/cod/website_parser.py +0 -0
  47. {finalcif-161 → finalcif-162}/finalcif/cif/core_dict.py +0 -0
  48. {finalcif-161 → finalcif-162}/finalcif/cif/hkl.py +0 -0
  49. {finalcif-161 → finalcif-162}/finalcif/cif/modulation_dict.py +0 -0
  50. {finalcif-161 → finalcif-162}/finalcif/cif/powder_dict.py +0 -0
  51. {finalcif-161 → finalcif-162}/finalcif/cif/reference.py +0 -0
  52. {finalcif-161 → finalcif-162}/finalcif/cif/restraints_dict.py +0 -0
  53. {finalcif-161 → finalcif-162}/finalcif/cif/text.py +0 -0
  54. {finalcif-161 → finalcif-162}/finalcif/cif/twin_dict.py +0 -0
  55. {finalcif-161 → finalcif-162}/finalcif/cif/vrf_entry.py +0 -0
  56. {finalcif-161 → finalcif-162}/finalcif/ciforder/__init__.py +0 -0
  57. {finalcif-161 → finalcif-162}/finalcif/ciforder/order.py +0 -0
  58. {finalcif-161 → finalcif-162}/finalcif/ciforder/order_ui.py +0 -0
  59. {finalcif-161 → finalcif-162}/finalcif/ciforder/order_ui.ui +0 -0
  60. {finalcif-161 → finalcif-162}/finalcif/datafiles/__init__.py +0 -0
  61. {finalcif-161 → finalcif-162}/finalcif/datafiles/bruker_data.py +0 -0
  62. {finalcif-161 → finalcif-162}/finalcif/datafiles/bruker_frame.py +0 -0
  63. {finalcif-161 → finalcif-162}/finalcif/datafiles/ccdc_mail.py +0 -0
  64. {finalcif-161 → finalcif-162}/finalcif/datafiles/data.py +0 -0
  65. {finalcif-161 → finalcif-162}/finalcif/datafiles/p4p_reader.py +0 -0
  66. {finalcif-161 → finalcif-162}/finalcif/datafiles/sadabs.py +0 -0
  67. {finalcif-161 → finalcif-162}/finalcif/datafiles/saint.py +0 -0
  68. {finalcif-161 → finalcif-162}/finalcif/datafiles/shelx_lst.py +0 -0
  69. {finalcif-161 → finalcif-162}/finalcif/datafiles/utils.py +0 -0
  70. {finalcif-161 → finalcif-162}/finalcif/displaymol/__init__.py +0 -0
  71. {finalcif-161 → finalcif-162}/finalcif/displaymol/vtk_molecule.py +0 -0
  72. {finalcif-161 → finalcif-162}/finalcif/equip_property/__init__.py +0 -0
  73. {finalcif-161 → finalcif-162}/finalcif/equip_property/author_loop_templates.py +0 -0
  74. {finalcif-161 → finalcif-162}/finalcif/equip_property/equipment.py +0 -0
  75. {finalcif-161 → finalcif-162}/finalcif/equip_property/properties.py +0 -0
  76. {finalcif-161 → finalcif-162}/finalcif/equip_property/tools.py +0 -0
  77. {finalcif-161 → finalcif-162}/finalcif/finalcif_start.py +0 -0
  78. {finalcif-161 → finalcif-162}/finalcif/gui/__init__.py +0 -0
  79. {finalcif-161 → finalcif-162}/finalcif/gui/block_combobox.py +0 -0
  80. {finalcif-161 → finalcif-162}/finalcif/gui/cif_table_model.py +0 -0
  81. {finalcif-161 → finalcif-162}/finalcif/gui/cif_table_view.py +0 -0
  82. {finalcif-161 → finalcif-162}/finalcif/gui/combobox.py +0 -0
  83. {finalcif-161 → finalcif-162}/finalcif/gui/custom_classes.py +0 -0
  84. {finalcif-161 → finalcif-162}/finalcif/gui/dialogs.py +0 -0
  85. {finalcif-161 → finalcif-162}/finalcif/gui/edit_button.py +0 -0
  86. {finalcif-161 → finalcif-162}/finalcif/gui/equipmenttable.py +0 -0
  87. {finalcif-161 → finalcif-162}/finalcif/gui/file_editor.py +0 -0
  88. {finalcif-161 → finalcif-162}/finalcif/gui/import_selector.py +0 -0
  89. {finalcif-161 → finalcif-162}/finalcif/gui/import_selector_ui.py +0 -0
  90. {finalcif-161 → finalcif-162}/finalcif/gui/import_selector_ui.ui +0 -0
  91. {finalcif-161 → finalcif-162}/finalcif/gui/loop_creator.py +0 -0
  92. {finalcif-161 → finalcif-162}/finalcif/gui/loop_creator_ui.py +0 -0
  93. {finalcif-161 → finalcif-162}/finalcif/gui/loop_creator_ui.ui +0 -0
  94. {finalcif-161 → finalcif-162}/finalcif/gui/loops.py +0 -0
  95. {finalcif-161 → finalcif-162}/finalcif/gui/mainstackwidget.py +0 -0
  96. {finalcif-161 → finalcif-162}/finalcif/gui/mixins.py +0 -0
  97. {finalcif-161 → finalcif-162}/finalcif/gui/new_key_dialog.py +0 -0
  98. {finalcif-161 → finalcif-162}/finalcif/gui/new_key_dialog_ui.py +0 -0
  99. {finalcif-161 → finalcif-162}/finalcif/gui/new_key_dialog_ui.ui +0 -0
  100. {finalcif-161 → finalcif-162}/finalcif/gui/plaintextedit.py +0 -0
  101. {finalcif-161 → finalcif-162}/finalcif/gui/playground.py +0 -0
  102. {finalcif-161 → finalcif-162}/finalcif/gui/propertytable.py +0 -0
  103. {finalcif-161 → finalcif-162}/finalcif/gui/spell_check_edit.py +0 -0
  104. {finalcif-161 → finalcif-162}/finalcif/gui/syntax_highlighter.py +0 -0
  105. {finalcif-161 → finalcif-162}/finalcif/gui/text_templates_ui.py +0 -0
  106. {finalcif-161 → finalcif-162}/finalcif/gui/text_templates_ui.ui +0 -0
  107. {finalcif-161 → finalcif-162}/finalcif/gui/text_value_editor.py +0 -0
  108. {finalcif-161 → finalcif-162}/finalcif/gui/validators.py +0 -0
  109. {finalcif-161 → finalcif-162}/finalcif/gui/vrf_classes.py +0 -0
  110. {finalcif-161 → finalcif-162}/finalcif/gui/vzs_viewer.py +0 -0
  111. {finalcif-161 → finalcif-162}/finalcif/icon/__init__.py +0 -0
  112. {finalcif-161 → finalcif-162}/finalcif/icon/finalcif.png +0 -0
  113. {finalcif-161 → finalcif-162}/finalcif/icon/finalcif2.ico +0 -0
  114. {finalcif-161 → finalcif-162}/finalcif/icon/finalcif2.png +0 -0
  115. {finalcif-161 → finalcif-162}/finalcif/icon/multitable.ico +0 -0
  116. {finalcif-161 → finalcif-162}/finalcif/icon/multitable.png +0 -0
  117. {finalcif-161 → finalcif-162}/finalcif/icon/multitable.xcf +0 -0
  118. {finalcif-161 → finalcif-162}/finalcif/report/__init__.py +0 -0
  119. {finalcif-161 → finalcif-162}/finalcif/report/archive_report.py +0 -0
  120. {finalcif-161 → finalcif-162}/finalcif/report/gui/__init__.py +0 -0
  121. {finalcif-161 → finalcif-162}/finalcif/report/gui/mainwindow.py +0 -0
  122. {finalcif-161 → finalcif-162}/finalcif/report/gui/mainwindow.ui +0 -0
  123. {finalcif-161 → finalcif-162}/finalcif/report/mtools.py +0 -0
  124. {finalcif-161 → finalcif-162}/finalcif/report/references.py +0 -0
  125. {finalcif-161 → finalcif-162}/finalcif/report/report_text.py +0 -0
  126. {finalcif-161 → finalcif-162}/finalcif/report/symm.py +0 -0
  127. {finalcif-161 → finalcif-162}/finalcif/report/tables.py +0 -0
  128. {finalcif-161 → finalcif-162}/finalcif/report/templated_report.py +0 -0
  129. {finalcif-161 → finalcif-162}/finalcif/template/__init__.py +0 -0
  130. {finalcif-161 → finalcif-162}/finalcif/template/bootstrap/bootstrap.min.css +0 -0
  131. {finalcif-161 → finalcif-162}/finalcif/template/machines/D8_VENTURE.cif +0 -0
  132. {finalcif-161 → finalcif-162}/finalcif/template/mathml2omml.xsl +0 -0
  133. {finalcif-161 → finalcif-162}/finalcif/template/report.tex +0 -0
  134. {finalcif-161 → finalcif-162}/finalcif/template/report.tmpl +0 -0
  135. {finalcif-161 → finalcif-162}/finalcif/template/report2.tex +0 -0
  136. {finalcif-161 → finalcif-162}/finalcif/template/report_default.docx +0 -0
  137. {finalcif-161 → finalcif-162}/finalcif/template/template1.docx +0 -0
  138. {finalcif-161 → finalcif-162}/finalcif/template/template_for_multitable.docx +0 -0
  139. {finalcif-161 → finalcif-162}/finalcif/template/template_text.docx +0 -0
  140. {finalcif-161 → finalcif-162}/finalcif/template/template_without_text.docx +0 -0
  141. {finalcif-161 → finalcif-162}/finalcif/template/templates.py +0 -0
  142. {finalcif-161 → finalcif-162}/finalcif/template/unicode2latex/FakePlasTeX/FakeContext.py +0 -0
  143. {finalcif-161 → finalcif-162}/finalcif/template/unicode2latex/FakePlasTeX/FakeTokenizer.py +0 -0
  144. {finalcif-161 → finalcif-162}/finalcif/template/unicode2latex/FakePlasTeX/README.md +0 -0
  145. {finalcif-161 → finalcif-162}/finalcif/template/unicode2latex/FakePlasTeX/TokenizerPassThru.py +0 -0
  146. {finalcif-161 → finalcif-162}/finalcif/template/unicode2latex/LICENSE +0 -0
  147. {finalcif-161 → finalcif-162}/finalcif/template/unicode2latex/__init__.py +0 -0
  148. {finalcif-161 → finalcif-162}/finalcif/template/unicode2latex/u2l.py +0 -0
  149. {finalcif-161 → finalcif-162}/finalcif/template/unicode2latex/unicode-math-table.tex +0 -0
  150. {finalcif-161 → finalcif-162}/finalcif/template/unicode2latex/unicode-math-xetex.sty +0 -0
  151. {finalcif-161 → finalcif-162}/finalcif/template/xsl/convert.py +0 -0
  152. {finalcif-161 → finalcif-162}/finalcif/template/xsl/xmltohtml.xsl +0 -0
  153. {finalcif-161 → finalcif-162}/finalcif/template/xsl/xmltolatex.xsl +0 -0
  154. {finalcif-161 → finalcif-162}/finalcif/tools/__init__.py +0 -0
  155. {finalcif-161 → finalcif-162}/finalcif/tools/chemparse.py +0 -0
  156. {finalcif-161 → finalcif-162}/finalcif/tools/download.py +0 -0
  157. {finalcif-161 → finalcif-162}/finalcif/tools/dsrmath.py +0 -0
  158. {finalcif-161 → finalcif-162}/finalcif/tools/misc.py +0 -0
  159. {finalcif-161 → finalcif-162}/finalcif/tools/options.py +0 -0
  160. {finalcif-161 → finalcif-162}/finalcif/tools/platon.py +0 -0
  161. {finalcif-161 → finalcif-162}/finalcif/tools/pupdate.py +0 -0
  162. {finalcif-161 → finalcif-162}/finalcif/tools/settings.py +0 -0
  163. {finalcif-161 → finalcif-162}/finalcif/tools/shred.py +0 -0
  164. {finalcif-161 → finalcif-162}/finalcif/tools/space_groups.py +0 -0
  165. {finalcif-161 → finalcif-162}/finalcif/tools/spgr_format.py +0 -0
  166. {finalcif-161 → finalcif-162}/finalcif/tools/statusbar.py +0 -0
  167. {finalcif-161 → finalcif-162}/finalcif/tools/sumformula.py +0 -0
  168. {finalcif-161 → finalcif-162}/finalcif.exe +0 -0
  169. {finalcif-161 → finalcif-162}/platon/README.txt +0 -0
  170. {finalcif-161 → finalcif-162}/platon/compile_platon.bat +0 -0
  171. {finalcif-161 → finalcif-162}/platon/platon_special.exe +0 -0
  172. {finalcif-161 → finalcif-162}/platon/platon_special.f +0 -0
  173. {finalcif-161 → finalcif-162}/pylock.toml +0 -0
  174. {finalcif-161 → finalcif-162}/run_finalcif +0 -0
  175. {finalcif-161 → finalcif-162}/run_finalcif.bat +0 -0
  176. {finalcif-161 → finalcif-162}/screenshots/finalcif_checkcif.png +0 -0
  177. {finalcif-161 → finalcif-162}/screenshots/finalcif_details.png +0 -0
  178. {finalcif-161 → finalcif-162}/screenshots/finalcif_loops.png +0 -0
  179. {finalcif-161 → finalcif-162}/screenshots/finalcif_loops2.png +0 -0
  180. {finalcif-161 → finalcif-162}/screenshots/finalcif_main.png +0 -0
  181. {finalcif-161 → finalcif-162}/screenshots/finalcif_report.png +0 -0
  182. {finalcif-161 → finalcif-162}/screenshots/finalcif_responses.png +0 -0
  183. {finalcif-161 → finalcif-162}/screenshots/loops_column_buttons.png +0 -0
  184. {finalcif-161 → finalcif-162}/update.exe +0 -0
@@ -16,15 +16,15 @@
16
16
  - **Entry Point:** The application starts from `finalcif/finalcif_start.py` (registered as `finalcif` in `pyproject.toml`).
17
17
  - **Main Window:** `AppWindow` (QMainWindow) in `finalcif/appwindow.py` is the orchestrator wiring all subsystems; UI defined in `finalcif/gui/finalcif_gui_ui.ui`.
18
18
  - **Subpackage Map:**
19
- - `finalcif/cif/` — CIF I/O via `gemmi.cif` wrapped in `CifContainer` (`cif_file_io.py`); dict definitions (core/powder/modulation/twin/restraints); subpackages `checkcif/` (IUCr CheckCIF HTML/PDF parsing) and `cod/` (COD/CCDC deposition: `deposit.py`, `upload.py`, `doi.py`, `website_parser.py`).
19
+ - `finalcif/cif/` — CIF I/O via `gemmi.cif` wrapped in `CifContainer` (`cif_file_io.py`); dict definitions (core/powder/modulation/twin/restraints); subpackages `checkcif/` (IUCr CheckCIF HTML/PDF parsing) and `cod/` (COD/CCDC deposition: `deposit.py`, `deposit_check.py`, `deposition_list.py`, `upload.py`, `doi.py`, `website_parser.py`).
20
20
  - `finalcif/ciforder/` — CIF key ordering dialog.
21
21
  - `finalcif/datafiles/` — raw instrument data importers: Bruker (`bruker_data.py`, `bruker_frame.py`), SAINT, SADABS, SHELX `.lst` (`shelx_lst.py`), `.p4p`, CCDC deposition mail parser (`ccdc_mail.py`).
22
22
  - `finalcif/displaymol/` — 3D molecular display (`vtk_molecule.py`) is not used; 2D rendering is provided by the external `fastmolwidget` package (custom widget in `finalcif_gui_ui.ui`).
23
23
  - `finalcif/equip_property/` — Equipment/Properties/AuthorLoops template tables (`equipment.py`, `properties.py`, `author_loop_templates.py`).
24
24
  - `finalcif/gui/` — Qt widgets; `.ui` files compiled to sibling `*_ui.py` modules.
25
- - `finalcif/report/` — DOCX report generation via `docxtpl` (`DocxTemplate`, `RichText`, `InlineImage`) in `templated_report.py`; also archive reports, tables, references, symmetry.
25
+ - `finalcif/report/` — DOCX report generation via `docxtpl` (`DocxTemplate`, `RichText`, `InlineImage`) in `templated_report.py`; also archive reports (`archive_report.py`), tables, references, symmetry. The `report/gui/` subpackage hosts the standalone report-options window (`mainwindow.py` + `mainwindow.ui`).
26
26
  - `finalcif/template/` — Report template infrastructure (`ReportTemplates`).
27
- - `finalcif/tools/` — cross-cutting helpers: `platon.py` (`PlatonRunner`), `shred.py`, `space_groups.py`, `sumformula.py`, `settings.py` (`FinalCifSettings` QSettings wrapper), `download.py`, `pupdate.py` (auto-updater), `statusbar.py`, `chemparse.py`, `dsrmath.py`.
27
+ - `finalcif/tools/` — cross-cutting helpers: `platon.py` (`PlatonRunner`), `shred.py`, `space_groups.py` / `spgr_format.py`, `sumformula.py`, `settings.py` (`FinalCifSettings` QSettings wrapper), `options.py`, `download.py`, `pupdate.py` (auto-updater), `statusbar.py`, `chemparse.py`, `dsrmath.py`, `z_from_packing.py`, `misc.py`.
28
28
  - **External Tool Integration:**
29
29
  - Offline CheckCIF uses PLATON via `finalcif/tools/platon.py`; executable resolution checks bundled `platon/platon_special.exe`, then `C:\pwt\platon.exe` (Windows), then `platon` on `PATH`.
30
30
  - Online CheckCIF (IUCr) via HTTP in `finalcif/cif/checkcif/checkcif.py`.
@@ -32,6 +32,7 @@
32
32
  - DOI / CrossRef lookups via `crossrefapi` (see `finalcif/cif/cod/doi.py`).
33
33
  - SHELX parsing via the external `shelxfile` package (used in `report/templated_report.py`); in-tree `.lst` parser in `datafiles/shelx_lst.py`.
34
34
  - **CIF Parsing:** Standardized on `gemmi` (>=0.7.5); `gemmi.set_leak_warnings(False)` is set at startup.
35
+ - **Key Third-Party Runtime Deps:** `qtpy` + `pyside6` / `pyside6-addons`, `gemmi`, `docxtpl[subdoc]`, `python-docx`, `shelxfile`, `fastmolwidget`, `crossrefapi`, `QtAwesome` (icon fonts), `pyenchant` (spell-check), `html2text`, `lxml`, `numpy<2.4`, `chardet` / `charset-normalizer`, `requests`/`urllib3`, `packaging`, `pefile` (see `pyproject.toml`).
35
36
 
36
37
  ## Conventions
37
38
  - **UI Files:** Edit `.ui` files in Qt Designer (`scripts/designer.py`), then regenerate via `python scripts/compile_ui_files.py`. Never hand-edit generated `*_ui.py`.
@@ -48,8 +49,9 @@
48
49
  - *Network-Gated Tests:* set `NO_NETWORK=1` to skip network-dependent checks (CheckCIF/DOI/deposit/PLATON; see `tests/test_checkcif.py`, `tests/test_doi.py`).
49
50
  - *Helpers:* shared base class `AppWindowTestCase` and `processevents()` in `tests/helpers.py`; `get_platon_exe()` resolves `C:\pwt\platon.exe` on Windows or `which('platon')`. Fixtures live in `tests/examples/`, `tests/statics/`, `tests/checkcif_results/`, and top-level `test-data/`.
50
51
  - **Linting:**
51
- - *Command (local):* `ruff check .` — configured via `.ruff.toml` (line-length 120, target py312).
52
- - *CI Lint:* GitHub Actions runs `flake8` in `.github/workflows/python_tests.yml` (ruff is not invoked in CI).
52
+ - *Command (local):* `ruff check .` — configured via `.ruff.toml` (line-length 120, target py312, rule set `E,F,B,UP,ANN,A,C4,DTZ,NPY,PL,FURB,RUF`).
53
+ - *CI Lint:* GitHub Actions runs `flake8` in `.github/workflows/python_tests.yml` (installed on the fly via `uv add flake8 pytest`; ruff is not invoked in CI).
54
+ - **Type Checking:** The dev dependency group includes `ty` and `pyside6-stubs`; `[tool.pyrefly]` in `pyproject.toml` configures Pyrefly project includes/excludes. There is no CI gate for type checks.
53
55
  - **UI Compilation:**
54
56
  - *Command:* `python scripts/compile_ui_files.py` (discovers `.ui` files, compiles them, and hot-fixes imports to `qtpy`).
55
57
  - **Build/Releases:**
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: finalcif
3
- Version: 161
3
+ Version: 162
4
4
  Summary: CIF file editor
5
5
  Project-URL: Homepage, https://dkratzert.de/finalcif.html
6
6
  Project-URL: Bug Tracker, https://github.com/dkratzert/FinalCif/issues
@@ -17,15 +17,15 @@ Requires-Dist: chardet
17
17
  Requires-Dist: charset-normalizer
18
18
  Requires-Dist: crossrefapi
19
19
  Requires-Dist: docxtpl[subdoc]>=0.20.2
20
- Requires-Dist: fastmolwidget>=0.9.5
20
+ Requires-Dist: fastmolwidget>=0.9.7
21
21
  Requires-Dist: gemmi>=0.7.5
22
22
  Requires-Dist: hatchling
23
23
  Requires-Dist: html2text
24
24
  Requires-Dist: lxml==6.1.0
25
25
  Requires-Dist: numpy<2.4,>=2.3.5
26
- Requires-Dist: packaging
26
+ Requires-Dist: packaging==26.2
27
27
  Requires-Dist: pefile
28
- Requires-Dist: pybind11
28
+ Requires-Dist: pybind11==3.0.4
29
29
  Requires-Dist: pyenchant
30
30
  Requires-Dist: pyside6
31
31
  Requires-Dist: pyside6-addons
@@ -34,8 +34,8 @@ Requires-Dist: qtawesome
34
34
  Requires-Dist: qtpy
35
35
  Requires-Dist: requests
36
36
  Requires-Dist: shelxfile
37
- Requires-Dist: urllib3
38
- Requires-Dist: wheel
37
+ Requires-Dist: urllib3==2.7.0
38
+ Requires-Dist: wheel==0.47.0
39
39
  Description-Content-Type: text/markdown
40
40
 
41
41
  ![Latest Release](https://img.shields.io/github/v/tag/dkratzert/FinalCif?label=Release)
@@ -0,0 +1,3 @@
1
+
2
+ VERSION = 162
3
+ __version__ = VERSION
@@ -75,6 +75,7 @@ from finalcif.tools.platon import PlatonRunner
75
75
  from finalcif.tools.settings import FinalCifSettings
76
76
  from finalcif.tools.shred import ShredCIF
77
77
  from finalcif.tools.space_groups import SpaceGroups
78
+ from finalcif.tools.z_from_packing import count_z_and_zprime, ZResult
78
79
  from finalcif.tools.spgr_format import spgrps
79
80
  from finalcif.tools.statusbar import StatusBar
80
81
  from finalcif.tools.sumformula import formula_str_to_dict, sum_formula_to_html
@@ -88,6 +89,42 @@ with suppress(ImportError):
88
89
  import qtawesome as qta
89
90
 
90
91
 
92
+ class _ZEstimatorThread(QtCore.QThread):
93
+ """Background thread that computes Z and Z′ without blocking the UI.
94
+
95
+ Emits ``result`` (a :class:`ZResult`) on success, or ``failed`` (with an
96
+ empty :class:`ZResult`) on any exception so the caller can always clear
97
+ the label consistently.
98
+ """
99
+
100
+ result = QtCore.Signal(object) # emits a ZResult
101
+
102
+ def __init__(
103
+ self,
104
+ atoms_fract: list,
105
+ symmops: list[str],
106
+ cell: tuple[float, ...],
107
+ formula_sum: str | None = None,
108
+ parent: QtCore.QObject | None = None,
109
+ ) -> None:
110
+ super().__init__(parent)
111
+ # Take immutable snapshots so the background thread never touches CIF state.
112
+ self._atoms_fract = list(atoms_fract)
113
+ self._symmops = list(symmops)
114
+ self._cell = tuple(cell)
115
+ self._formula_sum = formula_sum
116
+
117
+ def run(self) -> None:
118
+ try:
119
+ res = count_z_and_zprime(
120
+ self._atoms_fract, self._symmops, self._cell,
121
+ formula_sum=self._formula_sum,
122
+ )
123
+ except Exception:
124
+ res = None
125
+ self.result.emit(res)
126
+
127
+
91
128
  class AppWindow(QMainWindow):
92
129
 
93
130
  def __init__(self, file: Path | None = None):
@@ -96,6 +133,7 @@ class AppWindow(QMainWindow):
96
133
  self.ckf = None
97
134
  self.thread_version = None
98
135
  self.worker = None
136
+ self._z_thread: _ZEstimatorThread | None = None
99
137
  self.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
100
138
  # This prevents some things to happen during unit tests:
101
139
  # Open of target dir of shred cif,
@@ -1766,6 +1804,7 @@ class AppWindow(QMainWindow):
1766
1804
  if self.ui.MainStackedWidget.on_info_page():
1767
1805
  self.show_residuals()
1768
1806
  self.redraw_molecule()
1807
+ self._update_z_label()
1769
1808
  t = QtCore.QTimer(self)
1770
1809
  t.singleShot(1000, self.check_cecksums)
1771
1810
 
@@ -1987,6 +2026,79 @@ class AppWindow(QMainWindow):
1987
2026
  self.ui.render_widget.grow_molecule(atom_tuples,
1988
2027
  cell=self.cif.cell[:6])
1989
2028
 
2029
+ def _update_z_label(self) -> None:
2030
+ """Start a background computation of Z and Z′ and show a placeholder.
2031
+
2032
+ Cancels any in-flight worker before starting a new one so that opening
2033
+ files in rapid succession never delivers a stale result to the label.
2034
+
2035
+ Uses the full symmetry expansion of the asymmetric unit to fill the unit
2036
+ cell, then counts discrete connected molecular graphs. Disorder is handled
2037
+ by retaining only the primary component of each disordered site so that
2038
+ each atomic position is populated exactly once.
2039
+
2040
+ Z′ = Z / Z_sg indicates the number of formula units per asymmetric unit:
2041
+ * Z′ = 1 → one molecule per ASU (most common, high confidence).
2042
+ * Z′ = ½ → molecule on 2-fold special position (medium confidence).
2043
+ * Z′ = ⅓ → molecule on 3-fold axis, trigonal/hexagonal (medium confidence).
2044
+ * Z′ = ¼ → molecule on 4-fold axis, tetragonal (medium confidence).
2045
+ * Z′ = ⅙ → molecule on 6-fold axis, hexagonal (medium confidence).
2046
+ * Z′ not a multiple of 1/n (n∈{1,2,3,4,6}) → estimate likely unreliable (low).
2047
+ """
2048
+ # Abort any previous worker still running.
2049
+ if self._z_thread is not None and self._z_thread.isRunning():
2050
+ self._z_thread.result.disconnect()
2051
+ self._z_thread.quit()
2052
+ self._z_thread.wait(50) # give it 50 ms to stop cleanly
2053
+ self._z_thread = None
2054
+
2055
+ try:
2056
+ atoms = list(self.cif.atoms_fract)
2057
+ symmops = list(self.cif.symmops)
2058
+ cell = tuple(self.cif.cell[:6])
2059
+ formula_sum = self.cif['_chemical_formula_sum'] or None
2060
+ except Exception:
2061
+ self.ui.zEstimateLabel.setText('?')
2062
+ self.ui.zEstimateLabel2.setText('?')
2063
+ return
2064
+
2065
+ self.ui.zEstimateLabel.setText('Z = ')
2066
+ self.ui.zEstimateLabel2.setText('Z = ')
2067
+ self.ui.zEstimateLabel.setToolTip('')
2068
+ self.ui.zEstimateLabel2.setToolTip('')
2069
+
2070
+ self._z_thread = _ZEstimatorThread(atoms, symmops, cell, formula_sum, parent=self)
2071
+ self._z_thread.result.connect(self._on_z_result)
2072
+ self._z_thread.start()
2073
+
2074
+ def _on_z_result(self, result: ZResult | None) -> None:
2075
+ """Receive the computed :class:`ZResult` from the background thread and update the label."""
2076
+ if result is None:
2077
+ self.ui.zEstimateLabel.setText('?')
2078
+ self.ui.zEstimateLabel2.setText('?')
2079
+ return
2080
+ confidence_icon = {'high': '✓', 'medium': '~', 'low': '?'}[result.confidence]
2081
+ self.ui.zEstimateLabel.setText(f"Z = {result.z} Z′ = {result.z_prime:.3g} {confidence_icon}")
2082
+ self.ui.zEstimateLabel2.setText(f"Z = {result.z} Z′ = {result.z_prime:.3g} {confidence_icon}")
2083
+ tip_lines = [
2084
+ f"Estimated Z = {result.z} formula units per unit cell",
2085
+ f"Z′ = {result.z_prime:.4f} (formula units per asymmetric unit)",
2086
+ f"Z_sg = {result.z_sg} (general positions in space group)",
2087
+ "",
2088
+ ]
2089
+ if result.confidence == 'high':
2090
+ tip_lines.append("Confidence: HIGH — Z′ is a positive integer.")
2091
+ elif result.confidence == 'medium':
2092
+ tip_lines.append("Confidence: MEDIUM — Z′ is a non-integer crystallographic")
2093
+ tip_lines.append("fraction (½, ⅓, ¼, or ⅙), suggesting the molecule sits")
2094
+ tip_lines.append("on a special position; the true Z may be higher.")
2095
+ else:
2096
+ tip_lines.append("Confidence: LOW — Z′ is not a multiple of ½.")
2097
+ tip_lines.append("The bond-graph estimate is likely incorrect")
2098
+ tip_lines.append("(common for polymers, frameworks, or salts).")
2099
+ self.ui.zEstimateLabel.setToolTip('\n'.join(tip_lines))
2100
+ self.ui.zEstimateLabel2.setToolTip('\n'.join(tip_lines))
2101
+
1990
2102
  def _calc_grown_atoms(self):
1991
2103
  """Helper to generate the symmetry expanded atoms without drawing them."""
1992
2104
  atoms_fract = tuple(self.cif.atoms_fract)
@@ -70,8 +70,12 @@ class CifContainer:
70
70
  raise GemmiError(e)
71
71
  # Starting with first block, but can use others with subsequent self._onload():
72
72
  self.block: gemmi.cif.Block = self.doc[0]
73
- self.shx = Shelxfile(verbose=True)
74
- self.shx.read_string(self.res_file_data[1:-1])
73
+ try:
74
+ self.shx = Shelxfile(verbose=True)
75
+ self.shx.read_string(self.res_file_data[1:-1])
76
+ except Exception:
77
+ print('Shelx file parsing failed.')
78
+ self.shx = None
75
79
  self._on_load()
76
80
  self.order = []
77
81
  self.essential_keys = []