finalcif 153__tar.gz → 155__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 (165) hide show
  1. {finalcif-153 → finalcif-155}/.gitignore +1 -0
  2. {finalcif-153 → finalcif-155}/PKG-INFO +1 -1
  3. finalcif-155/finalcif/__init__.py +2 -0
  4. {finalcif-153 → finalcif-155}/finalcif/appwindow.py +25 -3
  5. {finalcif-153 → finalcif-155}/finalcif/cif/atoms.py +3 -3
  6. {finalcif-153 → finalcif-155}/finalcif/cif/checkcif/checkcif.py +4 -1
  7. {finalcif-153 → finalcif-155}/finalcif/cif/cif_file_io.py +36 -27
  8. {finalcif-153 → finalcif-155}/finalcif/equip_property/author_loop_templates.py +14 -23
  9. {finalcif-153 → finalcif-155}/finalcif/finalcif_start.py +1 -1
  10. {finalcif-153 → finalcif-155}/finalcif/gui/finalcif_gui_ui.py +671 -667
  11. {finalcif-153 → finalcif-155}/finalcif/gui/finalcif_gui_ui.ui +13 -3
  12. {finalcif-153 → finalcif-155}/finalcif/report/templated_report.py +62 -39
  13. {finalcif-153 → finalcif-155}/finalcif/template/report.tmpl +7 -1
  14. {finalcif-153 → finalcif-155}/finalcif/tools/platon.py +10 -7
  15. {finalcif-153 → finalcif-155}/finalcif/tools/sumformula.py +0 -2
  16. finalcif-155/platon/README.txt +29 -0
  17. finalcif-155/platon/compile_platon.bat +4 -0
  18. finalcif-155/platon/platon_special.exe +0 -0
  19. finalcif-155/platon/platon_special.f +167105 -0
  20. finalcif-153/finalcif/__init__.py +0 -2
  21. {finalcif-153 → finalcif-155}/.gitattributes +0 -0
  22. {finalcif-153 → finalcif-155}/.python-version +0 -0
  23. {finalcif-153 → finalcif-155}/.ruff.toml +0 -0
  24. {finalcif-153 → finalcif-155}/.run/Make Documentation MAC.run.xml +0 -0
  25. {finalcif-153 → finalcif-155}/.run/compile_ui_files.run.xml +0 -0
  26. {finalcif-153 → finalcif-155}/.run/make documentation Windows.run.xml +0 -0
  27. {finalcif-153 → finalcif-155}/CODE_OF_CONDUCT.md +0 -0
  28. {finalcif-153 → finalcif-155}/CONTRIBUTING.md +0 -0
  29. {finalcif-153 → finalcif-155}/Finalcif_installer_win.spec +0 -0
  30. {finalcif-153 → finalcif-155}/Finalcif_linux_onefile.spec +0 -0
  31. {finalcif-153 → finalcif-155}/Finalcif_mac.spec +0 -0
  32. {finalcif-153 → finalcif-155}/Hydrogen_solution_type.cif +0 -0
  33. {finalcif-153 → finalcif-155}/LICENSE +0 -0
  34. {finalcif-153 → finalcif-155}/README.md +0 -0
  35. {finalcif-153 → finalcif-155}/finalcif/app_path.py +0 -0
  36. {finalcif-153 → finalcif-155}/finalcif/cif/__init__.py +0 -0
  37. {finalcif-153 → finalcif-155}/finalcif/cif/all_cif_dicts.py +0 -0
  38. {finalcif-153 → finalcif-155}/finalcif/cif/checkcif/__init__.py +0 -0
  39. {finalcif-153 → finalcif-155}/finalcif/cif/cif_dict_foo.py +0 -0
  40. {finalcif-153 → finalcif-155}/finalcif/cif/cif_order.py +0 -0
  41. {finalcif-153 → finalcif-155}/finalcif/cif/cod/__init__.py +0 -0
  42. {finalcif-153 → finalcif-155}/finalcif/cif/cod/deposit.py +0 -0
  43. {finalcif-153 → finalcif-155}/finalcif/cif/cod/deposit_check.py +0 -0
  44. {finalcif-153 → finalcif-155}/finalcif/cif/cod/deposition_list.py +0 -0
  45. {finalcif-153 → finalcif-155}/finalcif/cif/cod/doi.py +0 -0
  46. {finalcif-153 → finalcif-155}/finalcif/cif/cod/upload.py +0 -0
  47. {finalcif-153 → finalcif-155}/finalcif/cif/cod/website_parser.py +0 -0
  48. {finalcif-153 → finalcif-155}/finalcif/cif/core_dict.py +0 -0
  49. {finalcif-153 → finalcif-155}/finalcif/cif/hkl.py +0 -0
  50. {finalcif-153 → finalcif-155}/finalcif/cif/modulation_dict.py +0 -0
  51. {finalcif-153 → finalcif-155}/finalcif/cif/powder_dict.py +0 -0
  52. {finalcif-153 → finalcif-155}/finalcif/cif/reference.py +0 -0
  53. {finalcif-153 → finalcif-155}/finalcif/cif/restraints_dict.py +0 -0
  54. {finalcif-153 → finalcif-155}/finalcif/cif/text.py +0 -0
  55. {finalcif-153 → finalcif-155}/finalcif/cif/twin_dict.py +0 -0
  56. {finalcif-153 → finalcif-155}/finalcif/ciforder/__init__.py +0 -0
  57. {finalcif-153 → finalcif-155}/finalcif/ciforder/order.py +0 -0
  58. {finalcif-153 → finalcif-155}/finalcif/ciforder/order_ui.py +0 -0
  59. {finalcif-153 → finalcif-155}/finalcif/ciforder/order_ui.ui +0 -0
  60. {finalcif-153 → finalcif-155}/finalcif/datafiles/__init__.py +0 -0
  61. {finalcif-153 → finalcif-155}/finalcif/datafiles/bruker_data.py +0 -0
  62. {finalcif-153 → finalcif-155}/finalcif/datafiles/bruker_frame.py +0 -0
  63. {finalcif-153 → finalcif-155}/finalcif/datafiles/ccdc_mail.py +0 -0
  64. {finalcif-153 → finalcif-155}/finalcif/datafiles/data.py +0 -0
  65. {finalcif-153 → finalcif-155}/finalcif/datafiles/p4p_reader.py +0 -0
  66. {finalcif-153 → finalcif-155}/finalcif/datafiles/sadabs.py +0 -0
  67. {finalcif-153 → finalcif-155}/finalcif/datafiles/saint.py +0 -0
  68. {finalcif-153 → finalcif-155}/finalcif/datafiles/shelx_lst.py +0 -0
  69. {finalcif-153 → finalcif-155}/finalcif/datafiles/utils.py +0 -0
  70. {finalcif-153 → finalcif-155}/finalcif/displaymol/__init__.py +0 -0
  71. {finalcif-153 → finalcif-155}/finalcif/displaymol/molecule2D.py +0 -0
  72. {finalcif-153 → finalcif-155}/finalcif/displaymol/sdm.py +0 -0
  73. {finalcif-153 → finalcif-155}/finalcif/displaymol/vtk_molecule.py +0 -0
  74. {finalcif-153 → finalcif-155}/finalcif/equip_property/__init__.py +0 -0
  75. {finalcif-153 → finalcif-155}/finalcif/equip_property/equipment.py +0 -0
  76. {finalcif-153 → finalcif-155}/finalcif/equip_property/properties.py +0 -0
  77. {finalcif-153 → finalcif-155}/finalcif/equip_property/tools.py +0 -0
  78. {finalcif-153 → finalcif-155}/finalcif/gui/__init__.py +0 -0
  79. {finalcif-153 → finalcif-155}/finalcif/gui/block_combobox.py +0 -0
  80. {finalcif-153 → finalcif-155}/finalcif/gui/combobox.py +0 -0
  81. {finalcif-153 → finalcif-155}/finalcif/gui/custom_classes.py +0 -0
  82. {finalcif-153 → finalcif-155}/finalcif/gui/dialogs.py +0 -0
  83. {finalcif-153 → finalcif-155}/finalcif/gui/edit_button.py +0 -0
  84. {finalcif-153 → finalcif-155}/finalcif/gui/equipmenttable.py +0 -0
  85. {finalcif-153 → finalcif-155}/finalcif/gui/file_editor.py +0 -0
  86. {finalcif-153 → finalcif-155}/finalcif/gui/import_selector.py +0 -0
  87. {finalcif-153 → finalcif-155}/finalcif/gui/import_selector_ui.py +0 -0
  88. {finalcif-153 → finalcif-155}/finalcif/gui/import_selector_ui.ui +0 -0
  89. {finalcif-153 → finalcif-155}/finalcif/gui/loop_creator.py +0 -0
  90. {finalcif-153 → finalcif-155}/finalcif/gui/loop_creator_ui.py +0 -0
  91. {finalcif-153 → finalcif-155}/finalcif/gui/loop_creator_ui.ui +0 -0
  92. {finalcif-153 → finalcif-155}/finalcif/gui/loops.py +0 -0
  93. {finalcif-153 → finalcif-155}/finalcif/gui/mainstackwidget.py +0 -0
  94. {finalcif-153 → finalcif-155}/finalcif/gui/mixins.py +0 -0
  95. {finalcif-153 → finalcif-155}/finalcif/gui/new_key_dialog.py +0 -0
  96. {finalcif-153 → finalcif-155}/finalcif/gui/new_key_dialog_ui.py +0 -0
  97. {finalcif-153 → finalcif-155}/finalcif/gui/new_key_dialog_ui.ui +0 -0
  98. {finalcif-153 → finalcif-155}/finalcif/gui/plaintextedit.py +0 -0
  99. {finalcif-153 → finalcif-155}/finalcif/gui/playground.py +0 -0
  100. {finalcif-153 → finalcif-155}/finalcif/gui/propertytable.py +0 -0
  101. {finalcif-153 → finalcif-155}/finalcif/gui/spell_check_edit.py +0 -0
  102. {finalcif-153 → finalcif-155}/finalcif/gui/text_templates_ui.py +0 -0
  103. {finalcif-153 → finalcif-155}/finalcif/gui/text_templates_ui.ui +0 -0
  104. {finalcif-153 → finalcif-155}/finalcif/gui/text_value_editor.py +0 -0
  105. {finalcif-153 → finalcif-155}/finalcif/gui/validators.py +0 -0
  106. {finalcif-153 → finalcif-155}/finalcif/gui/vrf_classes.py +0 -0
  107. {finalcif-153 → finalcif-155}/finalcif/icon/__init__.py +0 -0
  108. {finalcif-153 → finalcif-155}/finalcif/icon/finalcif.png +0 -0
  109. {finalcif-153 → finalcif-155}/finalcif/icon/finalcif2.ico +0 -0
  110. {finalcif-153 → finalcif-155}/finalcif/icon/finalcif2.png +0 -0
  111. {finalcif-153 → finalcif-155}/finalcif/icon/multitable.ico +0 -0
  112. {finalcif-153 → finalcif-155}/finalcif/icon/multitable.png +0 -0
  113. {finalcif-153 → finalcif-155}/finalcif/icon/multitable.xcf +0 -0
  114. {finalcif-153 → finalcif-155}/finalcif/report/__init__.py +0 -0
  115. {finalcif-153 → finalcif-155}/finalcif/report/archive_report.py +0 -0
  116. {finalcif-153 → finalcif-155}/finalcif/report/gui/__init__.py +0 -0
  117. {finalcif-153 → finalcif-155}/finalcif/report/gui/mainwindow.py +0 -0
  118. {finalcif-153 → finalcif-155}/finalcif/report/gui/mainwindow.ui +0 -0
  119. {finalcif-153 → finalcif-155}/finalcif/report/mtools.py +0 -0
  120. {finalcif-153 → finalcif-155}/finalcif/report/references.py +0 -0
  121. {finalcif-153 → finalcif-155}/finalcif/report/report_text.py +0 -0
  122. {finalcif-153 → finalcif-155}/finalcif/report/symm.py +0 -0
  123. {finalcif-153 → finalcif-155}/finalcif/report/tables.py +0 -0
  124. {finalcif-153 → finalcif-155}/finalcif/template/__init__.py +0 -0
  125. {finalcif-153 → finalcif-155}/finalcif/template/bootstrap/bootstrap.min.css +0 -0
  126. {finalcif-153 → finalcif-155}/finalcif/template/machines/D8_VENTURE.cif +0 -0
  127. {finalcif-153 → finalcif-155}/finalcif/template/mathml2omml.xsl +0 -0
  128. {finalcif-153 → finalcif-155}/finalcif/template/report_default.docx +0 -0
  129. {finalcif-153 → finalcif-155}/finalcif/template/template1.docx +0 -0
  130. {finalcif-153 → finalcif-155}/finalcif/template/template_for_multitable.docx +0 -0
  131. {finalcif-153 → finalcif-155}/finalcif/template/template_text.docx +0 -0
  132. {finalcif-153 → finalcif-155}/finalcif/template/template_without_text.docx +0 -0
  133. {finalcif-153 → finalcif-155}/finalcif/template/templates.py +0 -0
  134. {finalcif-153 → finalcif-155}/finalcif/template/xsl/convert.py +0 -0
  135. {finalcif-153 → finalcif-155}/finalcif/template/xsl/xmltohtml.xsl +0 -0
  136. {finalcif-153 → finalcif-155}/finalcif/tools/__init__.py +0 -0
  137. {finalcif-153 → finalcif-155}/finalcif/tools/chemparse.py +0 -0
  138. {finalcif-153 → finalcif-155}/finalcif/tools/download.py +0 -0
  139. {finalcif-153 → finalcif-155}/finalcif/tools/dsrmath.py +0 -0
  140. {finalcif-153 → finalcif-155}/finalcif/tools/misc.py +0 -0
  141. {finalcif-153 → finalcif-155}/finalcif/tools/options.py +0 -0
  142. {finalcif-153 → finalcif-155}/finalcif/tools/pupdate.py +0 -0
  143. {finalcif-153 → finalcif-155}/finalcif/tools/settings.py +0 -0
  144. {finalcif-153 → finalcif-155}/finalcif/tools/shred.py +0 -0
  145. {finalcif-153 → finalcif-155}/finalcif/tools/space_groups.py +0 -0
  146. {finalcif-153 → finalcif-155}/finalcif/tools/spgr_format.py +0 -0
  147. {finalcif-153 → finalcif-155}/finalcif/tools/statusbar.py +0 -0
  148. {finalcif-153 → finalcif-155}/finalcif.exe +0 -0
  149. {finalcif-153 → finalcif-155}/install_requirements +0 -0
  150. {finalcif-153 → finalcif-155}/install_requirements.bat +0 -0
  151. {finalcif-153 → finalcif-155}/pyproject.toml +0 -0
  152. {finalcif-153 → finalcif-155}/requirements.txt +0 -0
  153. {finalcif-153 → finalcif-155}/requirements_devel.txt +0 -0
  154. {finalcif-153 → finalcif-155}/requirements_doku.txt +0 -0
  155. {finalcif-153 → finalcif-155}/run_finalcif +0 -0
  156. {finalcif-153 → finalcif-155}/run_finalcif.bat +0 -0
  157. {finalcif-153 → finalcif-155}/screenshots/finalcif_checkcif.png +0 -0
  158. {finalcif-153 → finalcif-155}/screenshots/finalcif_details.png +0 -0
  159. {finalcif-153 → finalcif-155}/screenshots/finalcif_loops.png +0 -0
  160. {finalcif-153 → finalcif-155}/screenshots/finalcif_loops2.png +0 -0
  161. {finalcif-153 → finalcif-155}/screenshots/finalcif_main.png +0 -0
  162. {finalcif-153 → finalcif-155}/screenshots/finalcif_report.png +0 -0
  163. {finalcif-153 → finalcif-155}/screenshots/finalcif_responses.png +0 -0
  164. {finalcif-153 → finalcif-155}/update.exe +0 -0
  165. {finalcif-153 → finalcif-155}/uv.lock +0 -0
@@ -226,3 +226,4 @@ tests/examples/report_multi-finalcif.docx
226
226
  tests/examples/work/cu_BruecknerJK_153F40_0m.lxt
227
227
  finalcif_keys_order.cif
228
228
  !/.python-version
229
+ /platon/*.mod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: finalcif
3
- Version: 153
3
+ Version: 155
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
@@ -0,0 +1,2 @@
1
+
2
+ VERSION = 155
@@ -127,6 +127,25 @@ class AppWindow(QMainWindow):
127
127
  if file:
128
128
  self.load_cif_file(file)
129
129
  self.load_recent_cifs_list()
130
+ self.ui.CheckcifPlaintextEdit.insertPlainText(
131
+ "\n"
132
+ " Offline CheckCIF uses PLATON from Anthony L. Spek, Utrecht University, The Netherlands."
133
+ "\n"
134
+ " https://platonsoft.nl\n"
135
+ " \n"
136
+ " Please cite PLATON in your publication if you use it.\n"
137
+ " \n"
138
+ " - PLATON References : \n"
139
+ " Spek, A.L. (2003). J. Appl. Cryst. 36, 7-13.\n"
140
+ " Spek, A.L. (2009). Acta Cryst. D65, 148-155.\n"
141
+ " Spek, A.L. (2015). Acta Cryst. C71, 9-18.\n"
142
+ " Spek, A.L. (2018). Inorg. Chim. Acta, 470, 232-237.\n"
143
+ " Spek, A.L. (2020). Acta Cryst. E76, 1-11.\n"
144
+ " \n"
145
+ " \n"
146
+ " - Recent versions of PLATON may be obtained from: https://platonsoft.nl/xraysoft\n"
147
+ " "
148
+ )
130
149
  self.set_checkcif_output_font(self.ui.CheckcifPlaintextEdit)
131
150
  # To make file drag&drop working:
132
151
  self.setAcceptDrops(True)
@@ -902,7 +921,7 @@ class AppWindow(QMainWindow):
902
921
  url = QtCore.QUrl.fromLocalFile(str(self.htmlfile.resolve()))
903
922
  self.ui.MainStackedWidget.go_to_checkcif_page()
904
923
  self.ui.CheckCIFResultsTabWidget.setCurrentIndex(1) # Index 1 is html page
905
- self.checkcif_browser.load(url)
924
+ self.checkcif_browser.setHtml(self.htmlfile.resolve().read_text('utf-8', 'ignore'))
906
925
  self.ui.ResponsesTabWidget.setCurrentIndex(0)
907
926
  threading.Thread(target=self._display_structure_factor_report, args=(parser,)).start()
908
927
  # The picture file linked in the html file:
@@ -960,7 +979,8 @@ class AppWindow(QMainWindow):
960
979
  self.ckf = CheckCif(parent=self, cif=self.cif, outfile=self.htmlfile,
961
980
  hkl_upload=(not self.ui.structfactCheckBox.isChecked()), pdf=False,
962
981
  url=self.options.checkcif_url,
963
- full_iucr=self.ui.fullIucrCheckBox.isChecked())
982
+ full_iucr=self.ui.fullIucrCheckBox.isChecked(),
983
+ check_duplicates=self.ui.checkDuplicatesCheckBox.isChecked())
964
984
  self.ckf.progress.connect(self._ckf_progress)
965
985
  self.ckf.failed.connect(self._checkcif_failed)
966
986
  self.ckf.finished.connect(self._checkcif_finished)
@@ -1033,7 +1053,8 @@ class AppWindow(QMainWindow):
1033
1053
  self.ckf = CheckCif(parent=self, cif=self.cif, outfile=htmlfile,
1034
1054
  hkl_upload=(not self.ui.structfactCheckBox.isChecked()),
1035
1055
  pdf=True, url=self.options.checkcif_url,
1036
- full_iucr=self.ui.fullIucrCheckBox.isChecked())
1056
+ full_iucr=self.ui.fullIucrCheckBox.isChecked(),
1057
+ check_duplicates=self.ui.checkDuplicatesCheckBox.isChecked())
1037
1058
  self.ckf.failed.connect(self._checkcif_failed)
1038
1059
  # noinspection PyUnresolvedReferences
1039
1060
  self.ckf.finished.connect(self._pdf_checkcif_finished)
@@ -1073,6 +1094,7 @@ class AppWindow(QMainWindow):
1073
1094
  runner.finished.connect(lambda: self.ui.CheckcifButton.setEnabled(True))
1074
1095
  runner.run_process()
1075
1096
  runner.formula.connect(self.add_moiety_furmula)
1097
+ return None
1076
1098
 
1077
1099
  def add_moiety_furmula(self, formula_moiety):
1078
1100
  moiety = self.ui.cif_main_table.getTextFromKey(key='_chemical_formula_moiety', col=Column.CIF)
@@ -816,7 +816,7 @@ def get_atomlabel(input_atom: str) -> str:
816
816
  else:
817
817
  # print('*** {} is not a valid atom!! ***'.format(atom))
818
818
  raise KeyError
819
- except(IndexError):
819
+ except IndexError:
820
820
  # print('*** {} is not a valid atom! ***'.format(atom))
821
821
  raise KeyError
822
822
 
@@ -826,6 +826,6 @@ if __name__ == '__main__':
826
826
 
827
827
  failed, attempted = doctest.testmod() # verbose=True)
828
828
  if failed == 0:
829
- print('passed all {} tests!'.format(attempted))
829
+ print(f'passed all {attempted} tests!')
830
830
  else:
831
- print('{} of {} tests failed'.format(failed, attempted))
831
+ print(f'{failed} of {attempted} tests failed')
@@ -25,7 +25,7 @@ class CheckCif(QThread):
25
25
  failed = Signal(str)
26
26
 
27
27
  def __init__(self, parent, cif: CifContainer, outfile: Path, hkl_upload: bool = True,
28
- pdf: bool = False, url: str = '', full_iucr: bool = False):
28
+ pdf: bool = False, url: str = '', full_iucr: bool = False, check_duplicates: bool = True):
29
29
  # hkl == False means no hkl upload
30
30
  super().__init__(parent=parent)
31
31
  self.hkl_upload = hkl_upload
@@ -34,6 +34,7 @@ class CheckCif(QThread):
34
34
  self.pdf = pdf
35
35
  self.checkcif_url = url
36
36
  self.full_iucr = full_iucr
37
+ self.check_duplicates = check_duplicates
37
38
 
38
39
  def _html_check(self) -> None:
39
40
  if self.hkl_upload:
@@ -63,11 +64,13 @@ class CheckCif(QThread):
63
64
  validation_type = 'checkcif_with_hkl'
64
65
  vrf = self.get_vrf()
65
66
  headers = {
67
+ "from_index": "from_index",
66
68
  "runtype" : "symmonly",
67
69
  "referer" : "checkcif_server",
68
70
  "outputtype": 'PDF' if self.pdf else 'HTML',
69
71
  "validtype" : validation_type,
70
72
  "valout" : vrf,
73
+ "duplic" : "duplicyes" if self.check_duplicates else "duplicno",
71
74
  }
72
75
  t1 = time.perf_counter()
73
76
  self.progress.emit('Report request sent. Please wait...')
@@ -5,6 +5,7 @@
5
5
  # and you think this stuff is worth it, you can buy me a beer in return.
6
6
  # Dr. Daniel Kratzert
7
7
  # ----------------------------------------------------------------------------
8
+ import os
8
9
  import re
9
10
  from collections import namedtuple
10
11
  from collections.abc import Generator
@@ -13,6 +14,8 @@ from pathlib import Path
13
14
 
14
15
  import gemmi
15
16
 
17
+ from finalcif.cif import atoms
18
+
16
19
  if hasattr(gemmi, 'set_leak_warnings'):
17
20
  gemmi.set_leak_warnings(False)
18
21
 
@@ -24,8 +27,7 @@ from finalcif.cif.cif_order import order, special_keys, essential_keys, non_cent
24
27
  from finalcif.cif.hkl import HKL, Limit
25
28
  from finalcif.cif.text import utf8_to_str, quote, retranslate_delimiter
26
29
  from finalcif.datafiles.utils import DSRFind
27
- from finalcif.tools.misc import isnumeric, grouper, strip_finalcif_of_name, \
28
- _angstrom_to_x
30
+ from finalcif.tools.misc import isnumeric, grouper, strip_finalcif_of_name, _angstrom_to_x
29
31
 
30
32
 
31
33
  class GemmiError(Exception):
@@ -45,6 +47,7 @@ class CifContainer:
45
47
  new_block: Create a new block (new file) if a name is given. Otherwise, just
46
48
  the existing document is opened.
47
49
  """
50
+ self.chars_ok = True
48
51
  self.picometer: bool = False
49
52
  self._abs_hkl_details: dict[str, str] = {}
50
53
  if isinstance(file, str):
@@ -72,6 +75,7 @@ class CifContainer:
72
75
  self._on_load()
73
76
  self.order = []
74
77
  self.essential_keys = []
78
+ # May be overwritten later:
75
79
  self.set_order_keys(order)
76
80
  self.set_essential_keys(essential_keys)
77
81
 
@@ -123,7 +127,7 @@ class CifContainer:
123
127
  for num, line in enumerate(filepath.read_bytes().splitlines(keepends=True)):
124
128
  try:
125
129
  line.decode('ascii')
126
- except(UnicodeDecodeError):
130
+ except UnicodeDecodeError:
127
131
  line_numbers.append(num)
128
132
  return line_numbers
129
133
 
@@ -155,9 +159,8 @@ class CifContainer:
155
159
  self.dsr_used = DSRFind(self.res_file_data).dsr_used
156
160
  self.atomic_struct: gemmi.SmallStructure = gemmi.make_small_structure_from_block(self.block)
157
161
  # A dictionary to convert Atom names like 'C1_2' or 'Ga3' into Element names like 'C' or 'Ga'
158
- self._name2elements = dict(
159
- zip([x.upper() for x in self.block.find_loop('_atom_site_label')],
160
- [x.upper() for x in self.block.find_loop('_atom_site_type_symbol')], strict=True))
162
+ self._name2elements = dict(zip([x.upper() for x in self.block.find_loop('_atom_site_label')],
163
+ [y.upper() for y in self.block.find_loop('_atom_site_type_symbol')], strict=False))
161
164
  self.check_hkl_min_max()
162
165
 
163
166
  @property
@@ -211,8 +214,8 @@ class CifContainer:
211
214
  return doc
212
215
 
213
216
  def cif_as_string(self, without_hkl=False) -> str:
214
- opt = gemmi.cif.WriteOptions(gemmi.cif.Style.Indent35)
215
-
217
+ opt = gemmi.cif.WriteOptions()
218
+ opt.align_pairs = 33
216
219
  if without_hkl:
217
220
  # return a copy, do not delete hkl from original:
218
221
  doc = gemmi.cif.Document()
@@ -257,7 +260,6 @@ class CifContainer:
257
260
  f", {self.nangles()} angles")
258
261
 
259
262
  def file_is_there_and_writable(self) -> bool:
260
- import os
261
263
  return self.fileobj.exists() and self.fileobj.is_file() and os.access(self.fileobj, os.W_OK)
262
264
 
263
265
  def set_pair_delimited(self, key: str, txt: str):
@@ -278,13 +280,13 @@ class CifContainer:
278
280
 
279
281
  def save(self, filename: Path | None = None) -> None:
280
282
  """
281
- Saves the current cif file.
282
- :param filename: Name to save cif file to.
283
+ Saves the current cif file. It uses the '[basename]-finalcif.cif' suffix by default.
284
+ :param filename: Name to save cif file to if the default is not sufficient.
283
285
  """
284
286
  if not filename:
285
287
  filename = self.finalcif_file
286
288
  if self.is_empty():
287
- print(f'File {filename} is empty.')
289
+ print(f'DBG> CIF file {filename} is empty.')
288
290
  return
289
291
  if not self.is_writable(filename):
290
292
  raise PermissionError(f'Failed to open {filename.resolve()} for writing: Operation not permitted')
@@ -714,18 +716,18 @@ class CifContainer:
714
716
  Atoms from the CIF where values are returned as string like in the CIF with esds.
715
717
  """
716
718
  labels = self.block.find_loop('_atom_site_label')
717
- types = self.block.find_loop('_atom_site_type_symbol')
719
+ types = self.block.find_loop('_atom_site_type_symbol') or [atoms.get_atomlabel(x) for x in labels]
718
720
  x = self.block.find_loop('_atom_site_fract_x')
719
721
  y = self.block.find_loop('_atom_site_fract_y')
720
722
  z = self.block.find_loop('_atom_site_fract_z')
721
723
  part = self.block.find_loop('_atom_site_disorder_group') or ('0',) * len(labels)
722
724
  occ = self.block.find_loop('_atom_site_occupancy') or ('1.000000',) * len(labels)
723
- u_eq = self.block.find_loop('_atom_site_U_iso_or_equiv')
725
+ u_eq = self.block.find_loop('_atom_site_U_iso_or_equiv') or ('.',) * len(labels)
724
726
  atom = namedtuple('atom', ('label', 'type', 'x', 'y', 'z', 'part', 'occ', 'u_eq'))
725
727
  for label, type, x, y, z, part, occ, u_eq in zip(labels, types, x, y, z,
726
728
  part,
727
729
  occ,
728
- u_eq, strict=True):
730
+ u_eq, strict=False):
729
731
  if without_h and self.ishydrogen(label):
730
732
  continue
731
733
  if self.picometer:
@@ -761,7 +763,7 @@ class CifContainer:
761
763
  u13 = self.block.find_loop('_atom_site_aniso_U_13')
762
764
  u12 = self.block.find_loop('_atom_site_aniso_U_12')
763
765
  adp = namedtuple('adp', ('label', 'U11', 'U22', 'U33', 'U23', 'U13', 'U12'))
764
- for label, u11, u22, u33, u23, u13, u12 in zip(labels, u11, u22, u33, u23, u13, u12):
766
+ for label, u11, u22, u33, u23, u13, u12 in zip(labels, u11, u22, u33, u23, u13, u12, strict=True):
765
767
  if self.picometer:
766
768
  u11 = _angstrom_to_x(u11, factor=100 ** 2)
767
769
  u22 = _angstrom_to_x(u22, factor=100 ** 2)
@@ -817,7 +819,7 @@ class CifContainer:
817
819
  symm = self.block.find_loop('_geom_bond_site_symmetry_2')
818
820
  publ_loop = self.block.find_loop('_geom_bond_publ_flag') or len(label1) * ['?']
819
821
  bond = namedtuple('bond', ('label1', 'label2', 'dist', 'symm'))
820
- for label1, label2, dist, symm, publ in zip(label1, label2, dist, symm, publ_loop):
822
+ for label1, label2, dist, symm, publ in zip(label1, label2, dist, symm, publ_loop, strict=False):
821
823
  if (without_h and (self.ishydrogen(label1) or self.ishydrogen(label2))) or self.yes_not_set(publ):
822
824
  continue
823
825
  else:
@@ -855,17 +857,20 @@ class CifContainer:
855
857
  symm2 = self.block.find_loop('_geom_angle_site_symmetry_3')
856
858
  publ_loop = self.block.find_loop('_geom_angle_publ_flag') or len(label1) * ['?']
857
859
  angle = namedtuple('angle', ('label1', 'label2', 'label3', 'angle_val', 'symm1', 'symm2'))
858
- for label1, label2, label3, angle_val, symm1, symm2, publ in \
859
- zip(label1, label2, label3, angle_val, symm1, symm2, publ_loop, strict=True):
860
- if ((without_H and (self.ishydrogen(label1) or self.ishydrogen(label2) or
861
- self.ishydrogen(label3))) or (self.yes_not_set(publ))):
860
+ for label1, label2, label3, angle_val, symm1, symm2, publ in (
861
+ zip(label1, label2, label3, angle_val, symm1, symm2, publ_loop, strict=False)):
862
+ if ((without_H and (self.ishydrogen(label1) or self.ishydrogen(label2) or self.ishydrogen(label3))) or
863
+ self.yes_not_set(publ)):
862
864
  continue
863
865
  else:
864
866
  yield angle(label1=label1, label2=label2, label3=label3, angle_val=angle_val,
865
867
  symm1=self.checksymm(symm1), symm2=self.checksymm(symm2))
866
868
 
867
869
  def _iselement(self, name: str) -> str:
868
- return self._name2elements[name.upper()]
870
+ try:
871
+ return self._name2elements[name.upper()]
872
+ except KeyError:
873
+ return atoms.get_atomlabel(name)
869
874
 
870
875
  def natoms(self, without_h: bool = False) -> int:
871
876
  return len(tuple(self.atoms(without_h)))
@@ -902,12 +907,11 @@ class CifContainer:
902
907
  tors = namedtuple('Torsion',
903
908
  ('label1', 'label2', 'label3', 'label4', 'torsang', 'symm1', 'symm2', 'symm3', 'symm4'))
904
909
  for label1, label2, label3, label4, torsang, symm1, symm2, symm3, symm4, publ in zip(label1, label2, label3,
905
- # noqa: B020
906
910
  label4,
907
911
  torsang, symm1, symm2,
908
912
  symm3,
909
913
  symm4, publ_loop,
910
- strict=True):
914
+ strict=False):
911
915
  if ((without_h and (self.ishydrogen(label1) or self.ishydrogen(label2)
912
916
  or self.ishydrogen(label3) or self.ishydrogen(label3))) or self.yes_not_set(publ)):
913
917
  continue
@@ -930,7 +934,7 @@ class CifContainer:
930
934
  hydr = namedtuple('HydrogenBond', ('label_d', 'label_h', 'label_a', 'dist_dh', 'dist_ha', 'dist_da',
931
935
  'angle_dha', 'symm'))
932
936
  for label_d, label_h, label_a, dist_dh, dist_ha, dist_da, angle_dha, symm, publ in (
933
- zip(label_d, label_h, label_a, dist_dh, dist_ha, dist_da, angle_dha, symm, publ_loop)):
937
+ zip(label_d, label_h, label_a, dist_dh, dist_ha, dist_da, angle_dha, symm, publ_loop, strict=False)):
934
938
  if self.yes_not_set(publ):
935
939
  continue
936
940
  if self.picometer:
@@ -947,7 +951,12 @@ class CifContainer:
947
951
  Returns the key/value pairs of a cif file sorted by priority.
948
952
  """
949
953
  keys_without_values, keys_with_values = self.keys_with_essentials()
950
- return keys_without_values + [('These below are already in:', '---------------------')] + keys_with_values
954
+ return [*keys_without_values, ('These below are already in:', '---------------------'), *keys_with_values]
955
+
956
+ def pairs(self):
957
+ for item in self.block:
958
+ if item.pair is not None:
959
+ yield item.pair
951
960
 
952
961
  def _is_centrokey(self, key) -> bool:
953
962
  """
@@ -973,7 +982,7 @@ class CifContainer:
973
982
  # continue
974
983
  if self._is_centrokey(key):
975
984
  continue
976
- if not value or value == '?' or value == "'?'":
985
+ if not value or value in {'?', "'?'"}:
977
986
  questions.append((key, value))
978
987
  else:
979
988
  with_values.append((key, value))
@@ -171,34 +171,24 @@ class AuthorLoops:
171
171
  def save_author_to_loop(self) -> None:
172
172
  author = self.get_author_info()
173
173
  row = [author.name, author.address, author.email, author.phone, author.orcid, author.iucr_id, author.footnote]
174
- if author.contact_author:
175
- author_type = f'_{author.author_type}_contact_author_name'
176
- del row[-1] # contact author has no footnote
177
- else:
178
- author_type = f'_{author.author_type}_author_name'
179
- if self.cif.block.find_loop(author_type):
180
- gemmi_loop: Loop = self.cif.block.find_loop(author_type).get_loop()
181
- if tuple(row) in list(grouper(gemmi_loop.values, len(row))):
182
- self.app.status_bar.show_message('This author already exists.', 10)
174
+ author_loop_tags = self.get_author_loop(author)
175
+ table: gemmi.cif.Table = self.cif.block.find(author_loop_tags)
176
+ if table.width():
177
+ gemmi_loop: Loop = table.loop
178
+ if tuple(row) in list(grouper(gemmi_loop.values, gemmi_loop.width())):
179
+ self.app.status_bar.show_message('This author already exists.', 15)
183
180
  print('dbg> Author already exists.')
184
181
  return
185
182
  else:
186
- author_loop = self.get_author_loop(author)
187
- gemmi_loop = self.cif.init_loop(author_loop)
188
- self.check_if_loop_and_row_size_fit_together(gemmi_loop, row)
183
+ gemmi_loop = self.cif.init_loop(author_loop_tags)
184
+ table = self.cif.block.find(author_loop_tags)
185
+ if gemmi_loop.width() < len(row):
186
+ row = row[:gemmi_loop.width()]
187
+ if row not in table:
188
+ table.append_row(row)
189
189
  self.app.make_loops_tables()
190
190
  self.show_authors_list()
191
191
 
192
- def check_if_loop_and_row_size_fit_together(self, gemmi_loop: gemmi.cif.Loop, row: list[str]) -> None:
193
- if gemmi_loop.width() < len(row):
194
- cut_row = row[:gemmi_loop.width()]
195
- if cut_row not in gemmi_loop.values:
196
- gemmi_loop.add_row(cut_row)
197
- elif gemmi_loop.width() > len(row):
198
- show_general_warning(self, 'An author loop with larger size is already in the CIF. Can not proceed.')
199
- elif row not in gemmi_loop.values:
200
- gemmi_loop.add_row(row)
201
-
202
192
  def get_author_info(self) -> Author:
203
193
  if self.ui.authorEditTabWidget.currentWidget().objectName() == 'page_publication':
204
194
  name = quote(utf8_to_str(self.ui.FullNameLineEdit.text()))
@@ -445,4 +435,5 @@ if __name__ == '__main__':
445
435
  import pprint
446
436
 
447
437
  l = AuthorLoops(Ui_FinalCifWindow(), CifContainer('test-data/1000007.cif'), None)
448
- pprint.pprint(l.export_raw_data())
438
+ data = l.export_raw_data()
439
+ pprint.pprint(data)
@@ -53,7 +53,7 @@ def my_exception_hook(exctype: type[BaseException], value: BaseException, error_
53
53
 
54
54
  logfile = Path.home().joinpath(Path(r'finalcif-crash.txt'))
55
55
  try:
56
- logfile.write_text(errortext)
56
+ logfile.write_text(errortext, encoding='utf-8', errors='ignore')
57
57
  except PermissionError:
58
58
  pass
59
59
  sys.__excepthook__(exctype, value, error_traceback)