finalcif 144__tar.gz → 147__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 (158) hide show
  1. {finalcif-144 → finalcif-147}/.gitignore +2 -0
  2. {finalcif-144 → finalcif-147}/PKG-INFO +4 -4
  3. finalcif-147/finalcif/__init__.py +2 -0
  4. {finalcif-144 → finalcif-147}/finalcif/appwindow.py +25 -4
  5. {finalcif-144 → finalcif-147}/finalcif/cif/cif_file_io.py +37 -16
  6. {finalcif-144 → finalcif-147}/finalcif/cif/cif_order.py +128 -0
  7. finalcif-147/finalcif/ciforder/order.py +229 -0
  8. finalcif-147/finalcif/ciforder/order_ui.py +103 -0
  9. finalcif-147/finalcif/ciforder/order_ui.ui +192 -0
  10. {finalcif-144 → finalcif-147}/finalcif/gui/combobox.py +34 -3
  11. {finalcif-144 → finalcif-147}/finalcif/gui/custom_classes.py +15 -0
  12. {finalcif-144 → finalcif-147}/finalcif/gui/finalcif_gui_ui.py +203 -158
  13. {finalcif-144 → finalcif-147}/finalcif/gui/finalcif_gui_ui.ui +255 -193
  14. {finalcif-144 → finalcif-147}/finalcif/gui/import_selector.py +9 -2
  15. {finalcif-144 → finalcif-147}/finalcif/gui/loops.py +2 -2
  16. {finalcif-144 → finalcif-147}/finalcif/gui/new_key_dialog.py +14 -4
  17. {finalcif-144 → finalcif-147}/finalcif/gui/new_key_dialog_ui.py +1 -1
  18. {finalcif-144 → finalcif-147}/finalcif/gui/new_key_dialog_ui.ui +1 -1
  19. {finalcif-144 → finalcif-147}/finalcif/gui/plaintextedit.py +25 -9
  20. {finalcif-144 → finalcif-147}/finalcif/report/templated_report.py +106 -30
  21. finalcif-147/finalcif/tools/__init__.py +0 -0
  22. {finalcif-144 → finalcif-147}/finalcif/tools/misc.py +0 -129
  23. {finalcif-144 → finalcif-147}/finalcif/tools/space_groups.py +1 -1
  24. {finalcif-144 → finalcif-147}/finalcif/tools/sumformula.py +6 -6
  25. {finalcif-144 → finalcif-147}/requirements.txt +2 -2
  26. finalcif-144/finalcif/__init__.py +0 -2
  27. {finalcif-144 → finalcif-147}/.gitattributes +0 -0
  28. {finalcif-144 → finalcif-147}/.run/Make Documentation MAC.run.xml +0 -0
  29. {finalcif-144 → finalcif-147}/.run/compile_ui_files.run.xml +0 -0
  30. {finalcif-144 → finalcif-147}/.run/make documentation Windows.run.xml +0 -0
  31. {finalcif-144 → finalcif-147}/CODE_OF_CONDUCT.md +0 -0
  32. {finalcif-144 → finalcif-147}/CONTRIBUTING.md +0 -0
  33. {finalcif-144 → finalcif-147}/Finalcif_installer_win.spec +0 -0
  34. {finalcif-144 → finalcif-147}/Finalcif_linux_onefile.spec +0 -0
  35. {finalcif-144 → finalcif-147}/Finalcif_mac.spec +0 -0
  36. {finalcif-144 → finalcif-147}/Hydrogen_solution_type.cif +0 -0
  37. {finalcif-144 → finalcif-147}/LICENSE +0 -0
  38. {finalcif-144 → finalcif-147}/README.md +0 -0
  39. {finalcif-144 → finalcif-147}/finalcif/app_path.py +0 -0
  40. {finalcif-144 → finalcif-147}/finalcif/cif/__init__.py +0 -0
  41. {finalcif-144 → finalcif-147}/finalcif/cif/all_cif_dicts.py +0 -0
  42. {finalcif-144 → finalcif-147}/finalcif/cif/atoms.py +0 -0
  43. {finalcif-144 → finalcif-147}/finalcif/cif/checkcif/__init__.py +0 -0
  44. {finalcif-144 → finalcif-147}/finalcif/cif/checkcif/checkcif.py +0 -0
  45. {finalcif-144 → finalcif-147}/finalcif/cif/cif_dict_foo.py +0 -0
  46. {finalcif-144 → finalcif-147}/finalcif/cif/cod/__init__.py +0 -0
  47. {finalcif-144 → finalcif-147}/finalcif/cif/cod/deposit.py +0 -0
  48. {finalcif-144 → finalcif-147}/finalcif/cif/cod/deposit_check.py +0 -0
  49. {finalcif-144 → finalcif-147}/finalcif/cif/cod/deposition_list.py +0 -0
  50. {finalcif-144 → finalcif-147}/finalcif/cif/cod/doi.py +0 -0
  51. {finalcif-144 → finalcif-147}/finalcif/cif/cod/upload.py +0 -0
  52. {finalcif-144 → finalcif-147}/finalcif/cif/cod/website_parser.py +0 -0
  53. {finalcif-144 → finalcif-147}/finalcif/cif/core_dict.py +0 -0
  54. {finalcif-144 → finalcif-147}/finalcif/cif/hkl.py +0 -0
  55. {finalcif-144 → finalcif-147}/finalcif/cif/modulation_dict.py +0 -0
  56. {finalcif-144 → finalcif-147}/finalcif/cif/powder_dict.py +0 -0
  57. {finalcif-144 → finalcif-147}/finalcif/cif/reference.py +0 -0
  58. {finalcif-144 → finalcif-147}/finalcif/cif/restraints_dict.py +0 -0
  59. {finalcif-144 → finalcif-147}/finalcif/cif/text.py +0 -0
  60. {finalcif-144 → finalcif-147}/finalcif/cif/twin_dict.py +0 -0
  61. {finalcif-144/finalcif/datafiles → finalcif-147/finalcif/ciforder}/__init__.py +0 -0
  62. {finalcif-144/finalcif/displaymol → finalcif-147/finalcif/datafiles}/__init__.py +0 -0
  63. {finalcif-144 → finalcif-147}/finalcif/datafiles/bruker_data.py +0 -0
  64. {finalcif-144 → finalcif-147}/finalcif/datafiles/bruker_frame.py +0 -0
  65. {finalcif-144 → finalcif-147}/finalcif/datafiles/ccdc_mail.py +0 -0
  66. {finalcif-144 → finalcif-147}/finalcif/datafiles/data.py +0 -0
  67. {finalcif-144 → finalcif-147}/finalcif/datafiles/p4p_reader.py +0 -0
  68. {finalcif-144 → finalcif-147}/finalcif/datafiles/sadabs.py +0 -0
  69. {finalcif-144 → finalcif-147}/finalcif/datafiles/saint.py +0 -0
  70. {finalcif-144 → finalcif-147}/finalcif/datafiles/shelx_lst.py +0 -0
  71. {finalcif-144 → finalcif-147}/finalcif/datafiles/utils.py +0 -0
  72. {finalcif-144/finalcif/equip_property → finalcif-147/finalcif/displaymol}/__init__.py +0 -0
  73. {finalcif-144 → finalcif-147}/finalcif/displaymol/molecule2D.py +0 -0
  74. {finalcif-144 → finalcif-147}/finalcif/displaymol/sdm.py +0 -0
  75. {finalcif-144 → finalcif-147}/finalcif/displaymol/vtk_molecule.py +0 -0
  76. {finalcif-144/finalcif/gui → finalcif-147/finalcif/equip_property}/__init__.py +0 -0
  77. {finalcif-144 → finalcif-147}/finalcif/equip_property/author_loop_templates.py +0 -0
  78. {finalcif-144 → finalcif-147}/finalcif/equip_property/equipment.py +0 -0
  79. {finalcif-144 → finalcif-147}/finalcif/equip_property/properties.py +0 -0
  80. {finalcif-144 → finalcif-147}/finalcif/equip_property/tools.py +0 -0
  81. {finalcif-144 → finalcif-147}/finalcif/finalcif_start.py +0 -0
  82. {finalcif-144/finalcif/icon → finalcif-147/finalcif/gui}/__init__.py +0 -0
  83. {finalcif-144 → finalcif-147}/finalcif/gui/block_combobox.py +0 -0
  84. {finalcif-144 → finalcif-147}/finalcif/gui/dialogs.py +0 -0
  85. {finalcif-144 → finalcif-147}/finalcif/gui/edit_button.py +0 -0
  86. {finalcif-144 → finalcif-147}/finalcif/gui/equipmenttable.py +0 -0
  87. {finalcif-144 → finalcif-147}/finalcif/gui/file_editor.py +0 -0
  88. {finalcif-144 → finalcif-147}/finalcif/gui/import_selector_ui.py +0 -0
  89. {finalcif-144 → finalcif-147}/finalcif/gui/import_selector_ui.ui +0 -0
  90. {finalcif-144 → finalcif-147}/finalcif/gui/loop_creator.py +0 -0
  91. {finalcif-144 → finalcif-147}/finalcif/gui/loop_creator_ui.py +0 -0
  92. {finalcif-144 → finalcif-147}/finalcif/gui/loop_creator_ui.ui +0 -0
  93. {finalcif-144 → finalcif-147}/finalcif/gui/mainstackwidget.py +0 -0
  94. {finalcif-144 → finalcif-147}/finalcif/gui/mixins.py +0 -0
  95. {finalcif-144 → finalcif-147}/finalcif/gui/playground.py +0 -0
  96. {finalcif-144 → finalcif-147}/finalcif/gui/propertytable.py +0 -0
  97. {finalcif-144 → finalcif-147}/finalcif/gui/spell_check_edit.py +0 -0
  98. {finalcif-144 → finalcif-147}/finalcif/gui/text_templates_ui.py +0 -0
  99. {finalcif-144 → finalcif-147}/finalcif/gui/text_templates_ui.ui +0 -0
  100. {finalcif-144 → finalcif-147}/finalcif/gui/text_value_editor.py +0 -0
  101. {finalcif-144 → finalcif-147}/finalcif/gui/validators.py +0 -0
  102. {finalcif-144 → finalcif-147}/finalcif/gui/vrf_classes.py +0 -0
  103. {finalcif-144/finalcif/report/gui → finalcif-147/finalcif/icon}/__init__.py +0 -0
  104. {finalcif-144 → finalcif-147}/finalcif/icon/finalcif.png +0 -0
  105. {finalcif-144 → finalcif-147}/finalcif/icon/finalcif2.ico +0 -0
  106. {finalcif-144 → finalcif-147}/finalcif/icon/finalcif2.png +0 -0
  107. {finalcif-144 → finalcif-147}/finalcif/icon/multitable.ico +0 -0
  108. {finalcif-144 → finalcif-147}/finalcif/icon/multitable.png +0 -0
  109. {finalcif-144 → finalcif-147}/finalcif/icon/multitable.xcf +0 -0
  110. {finalcif-144 → finalcif-147}/finalcif/report/__init__.py +0 -0
  111. {finalcif-144 → finalcif-147}/finalcif/report/archive_report.py +0 -0
  112. {finalcif-144/finalcif/template → finalcif-147/finalcif/report/gui}/__init__.py +0 -0
  113. {finalcif-144 → finalcif-147}/finalcif/report/gui/mainwindow.py +0 -0
  114. {finalcif-144 → finalcif-147}/finalcif/report/gui/mainwindow.ui +0 -0
  115. {finalcif-144 → finalcif-147}/finalcif/report/mtools.py +0 -0
  116. {finalcif-144 → finalcif-147}/finalcif/report/references.py +0 -0
  117. {finalcif-144 → finalcif-147}/finalcif/report/report_text.py +0 -0
  118. {finalcif-144 → finalcif-147}/finalcif/report/symm.py +0 -0
  119. {finalcif-144 → finalcif-147}/finalcif/report/tables.py +0 -0
  120. {finalcif-144/finalcif/tools → finalcif-147/finalcif/template}/__init__.py +0 -0
  121. {finalcif-144 → finalcif-147}/finalcif/template/bootstrap/bootstrap.min.css +0 -0
  122. {finalcif-144 → finalcif-147}/finalcif/template/machines/D8_VENTURE.cif +0 -0
  123. {finalcif-144 → finalcif-147}/finalcif/template/mathml2omml.xsl +0 -0
  124. {finalcif-144 → finalcif-147}/finalcif/template/report.tmpl +0 -0
  125. {finalcif-144 → finalcif-147}/finalcif/template/report_default.docx +0 -0
  126. {finalcif-144 → finalcif-147}/finalcif/template/template1.docx +0 -0
  127. {finalcif-144 → finalcif-147}/finalcif/template/template_for_multitable.docx +0 -0
  128. {finalcif-144 → finalcif-147}/finalcif/template/template_text.docx +0 -0
  129. {finalcif-144 → finalcif-147}/finalcif/template/template_without_text.docx +0 -0
  130. {finalcif-144 → finalcif-147}/finalcif/template/templates.py +0 -0
  131. {finalcif-144 → finalcif-147}/finalcif/template/xsl/convert.py +0 -0
  132. {finalcif-144 → finalcif-147}/finalcif/template/xsl/xmltohtml.xsl +0 -0
  133. {finalcif-144 → finalcif-147}/finalcif/tools/chemparse.py +0 -0
  134. {finalcif-144 → finalcif-147}/finalcif/tools/download.py +0 -0
  135. {finalcif-144 → finalcif-147}/finalcif/tools/dsrmath.py +0 -0
  136. {finalcif-144 → finalcif-147}/finalcif/tools/options.py +0 -0
  137. {finalcif-144 → finalcif-147}/finalcif/tools/platon.py +0 -0
  138. {finalcif-144 → finalcif-147}/finalcif/tools/pupdate.py +0 -0
  139. {finalcif-144 → finalcif-147}/finalcif/tools/settings.py +0 -0
  140. {finalcif-144 → finalcif-147}/finalcif/tools/shred.py +0 -0
  141. {finalcif-144 → finalcif-147}/finalcif/tools/spgr_format.py +0 -0
  142. {finalcif-144 → finalcif-147}/finalcif/tools/statusbar.py +0 -0
  143. {finalcif-144 → finalcif-147}/finalcif.exe +0 -0
  144. {finalcif-144 → finalcif-147}/install_requirements +0 -0
  145. {finalcif-144 → finalcif-147}/install_requirements.bat +0 -0
  146. {finalcif-144 → finalcif-147}/pyproject.toml +0 -0
  147. {finalcif-144 → finalcif-147}/requirements_devel.txt +0 -0
  148. {finalcif-144 → finalcif-147}/requirements_doku.txt +0 -0
  149. {finalcif-144 → finalcif-147}/run_finalcif +0 -0
  150. {finalcif-144 → finalcif-147}/run_finalcif.bat +0 -0
  151. {finalcif-144 → finalcif-147}/screenshots/finalcif_checkcif.png +0 -0
  152. {finalcif-144 → finalcif-147}/screenshots/finalcif_details.png +0 -0
  153. {finalcif-144 → finalcif-147}/screenshots/finalcif_loops.png +0 -0
  154. {finalcif-144 → finalcif-147}/screenshots/finalcif_loops2.png +0 -0
  155. {finalcif-144 → finalcif-147}/screenshots/finalcif_main.png +0 -0
  156. {finalcif-144 → finalcif-147}/screenshots/finalcif_report.png +0 -0
  157. {finalcif-144 → finalcif-147}/screenshots/finalcif_responses.png +0 -0
  158. {finalcif-144 → finalcif-147}/update.exe +0 -0
@@ -223,3 +223,5 @@ work/~$test.docx
223
223
  tests/examples/work/report_cu_BruecknerJK_153F40_0m-finalcif_pm.docx
224
224
  tests/examples/multi-multitable.docx
225
225
  tests/examples/report_multi-finalcif.docx
226
+ tests/examples/work/cu_BruecknerJK_153F40_0m.lxt
227
+ finalcif_keys_order.cif
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: finalcif
3
- Version: 144
3
+ Version: 147
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
@@ -16,10 +16,10 @@ Requires-Dist: certifi
16
16
  Requires-Dist: chardet
17
17
  Requires-Dist: crossrefapi
18
18
  Requires-Dist: docxtpl
19
- Requires-Dist: gemmi~=0.6
19
+ Requires-Dist: gemmi<0.7.0
20
20
  Requires-Dist: html2text
21
21
  Requires-Dist: lxml
22
- Requires-Dist: numpy<2.0
22
+ Requires-Dist: numpy
23
23
  Requires-Dist: pefile
24
24
  Requires-Dist: pybind11
25
25
  Requires-Dist: pyenchant
@@ -0,0 +1,2 @@
1
+
2
+ VERSION = 147
@@ -29,6 +29,7 @@ from finalcif.app_path import application_path
29
29
  # QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
30
30
  from finalcif.cif.checkcif.checkcif import MyHTMLParser, AlertHelp, CheckCif
31
31
  from finalcif.cif.cif_file_io import CifContainer, GemmiError
32
+ from finalcif.cif import cif_order
32
33
  from finalcif.cif.cod.deposit import CODdeposit
33
34
  from finalcif.cif.text import utf8_to_str, quote
34
35
  from finalcif.datafiles.bruker_data import BrukerData
@@ -61,6 +62,7 @@ from finalcif.tools.platon import PlatonRunner
61
62
  from finalcif.tools.settings import FinalCifSettings
62
63
  from finalcif.tools.shred import ShredCIF
63
64
  from finalcif.tools.space_groups import SpaceGroups
65
+ from finalcif.tools.spgr_format import spgrps
64
66
  from finalcif.tools.statusbar import StatusBar
65
67
  from finalcif.tools.sumformula import formula_str_to_dict, sum_formula_to_html
66
68
 
@@ -104,6 +106,8 @@ class AppWindow(QMainWindow):
104
106
  self.status_bar = StatusBar(ui=self.ui)
105
107
  self.status_bar.show_message('FinalCif version {}'.format(VERSION))
106
108
  self.authors: Union[AuthorLoops, None] = None
109
+ self.ui.cifOrderWidget.settings = self.settings
110
+ self.ui.cifOrderWidget.set_order_from_settings(self.settings)
107
111
  self.set_window_size_and_position()
108
112
  self.ui.cif_main_table.installEventFilter(self)
109
113
  # Sorting desynchronized header and columns:
@@ -297,6 +301,14 @@ class AppWindow(QMainWindow):
297
301
  self.ui.AddThisAuthorToLoopPushButton.setIcon(qta.icon('mdi.file-table-outline'))
298
302
  with suppress(Exception):
299
303
  self.ui.DeleteLoopAuthorTemplateButton.setIcon(qta.icon('mdi.delete-forever-outline'))
304
+ with suppress(Exception):
305
+ self.ui.cifOrderWidget.ui.deleteKeyPushButton.setIcon(qta.icon('mdi.delete-forever-outline'))
306
+ with suppress(Exception):
307
+ self.ui.cifOrderWidget.ui.moveUpPushButton.setIcon(qta.icon('mdi.chevron-up'))
308
+ self.ui.cifOrderWidget.ui.moveDownPushButton.setIcon(qta.icon('mdi.chevron-down'))
309
+ self.ui.cifOrderWidget.ui.saveSettingPushButton.setIcon(qta.icon('mdi.content-save'))
310
+ self.ui.cifOrderWidget.ui.addKeyPushButton.setIcon(qta.icon('mdi.plus'))
311
+
300
312
 
301
313
  def connect_signals_and_slots(self) -> None:
302
314
  """
@@ -1113,7 +1125,7 @@ class AppWindow(QMainWindow):
1113
1125
  """
1114
1126
  Generates a report document.
1115
1127
  """
1116
- from finalcif.report.tables import make_report_from, make_multi_tables
1128
+ from finalcif.report.tables import make_multi_tables
1117
1129
  from finalcif.report.templated_report import TemplatedReport
1118
1130
  current_block = self.ui.datanameComboBox.currentIndex()
1119
1131
  if self.cif.doc[current_block].name == 'global':
@@ -1189,6 +1201,10 @@ class AppWindow(QMainWindow):
1189
1201
  with suppress(Exception):
1190
1202
  multitable = self.cif.finalcif_file_prefixed(prefix='', suffix='-multitable.docx')
1191
1203
  arc.zip.write(filename=multitable, arcname=multitable.name)
1204
+ with suppress(Exception):
1205
+ prp_list = self.cif.finalcif_file.parent.glob('*.prp')
1206
+ sorted_prp = sorted(prp_list, key=lambda x: x.stat().st_mtime)
1207
+ arc.zip.write(filename=sorted_prp[-1], arcname=sorted_prp[-1].name)
1192
1208
 
1193
1209
  def open_report_document(self, report_filename: Path, multi_table_document: Path) -> None:
1194
1210
  if self.cif.is_multi_cif:
@@ -1238,10 +1254,12 @@ class AppWindow(QMainWindow):
1238
1254
  self.cif.rename_data_name(''.join(self.ui.datanameComboBox.currentText().split(' ')))
1239
1255
  self.store_data_from_table_rows()
1240
1256
  self.save_ccdc_number()
1257
+ self.cif.set_order_keys(self.ui.cifOrderWidget.order_keys)
1241
1258
  try:
1242
1259
  self.cif.save()
1243
1260
  self.status_bar.show_message(f' File Saved: {self.cif.finalcif_file}', 10)
1244
- print('File saved ...')
1261
+ if DEBUG:
1262
+ print('File saved ...')
1245
1263
  return True
1246
1264
  except Exception as e:
1247
1265
  print('Unable to save file:')
@@ -1473,6 +1491,7 @@ class AppWindow(QMainWindow):
1473
1491
  self.ui.datanameComboBox.blockSignals(False)
1474
1492
  if self.cif.is_multi_cif:
1475
1493
  self._flash_block_combobox()
1494
+ self.cif.set_order_keys(self.ui.cifOrderWidget.order_keys)
1476
1495
  # Enable to find widgets without parent:
1477
1496
  # QtCore.QTimer(self).singleShot(1000, self.find_widgets_without_parent)
1478
1497
 
@@ -1604,8 +1623,9 @@ class AppWindow(QMainWindow):
1604
1623
 
1605
1624
  def fill_space_group_lineedit(self) -> None:
1606
1625
  try:
1607
- self.ui.Spacegroup_top_LineEdit.setText(
1608
- SpaceGroups().to_html(self.cif.space_group))
1626
+ txt = (f'<body style="">{SpaceGroups().to_html(self.cif.space_group)} '
1627
+ f'&thinsp;({spgrps[self.cif.space_group][1].get("itnumber")})</body>')
1628
+ self.ui.Spacegroup_top_LineEdit.setText(txt)
1609
1629
  except Exception as e:
1610
1630
  print('Space group error:', str(e))
1611
1631
  self.ui.Spacegroup_top_LineEdit.setText(self.cif.space_group)
@@ -1910,6 +1930,7 @@ class AppWindow(QMainWindow):
1910
1930
  """
1911
1931
  Adds the cif content to the main table. also add reference to FinalCif.
1912
1932
  """
1933
+ self.cif.set_essential_keys(self.ui.cifOrderWidget.essential_keys)
1913
1934
  for key, value in self.cif.key_value_pairs():
1914
1935
  if not value or value == '?' or value == "'?'":
1915
1936
  self.missing_data.add(key)
@@ -16,11 +16,11 @@ from gemmi.cif import as_string, Document, Loop
16
16
  from packaging.version import Version
17
17
  from shelxfile import Shelxfile
18
18
 
19
- from finalcif.cif.cif_order import order, special_keys
19
+ from finalcif.cif.cif_order import order, special_keys, essential_keys, non_centrosymm_keys
20
20
  from finalcif.cif.hkl import HKL, Limit
21
21
  from finalcif.cif.text import utf8_to_str, quote, retranslate_delimiter
22
22
  from finalcif.datafiles.utils import DSRFind
23
- from finalcif.tools.misc import essential_keys, non_centrosymm_keys, isnumeric, grouper, strip_finalcif_of_name, \
23
+ from finalcif.tools.misc import isnumeric, grouper, strip_finalcif_of_name, \
24
24
  _angstrom_to_x
25
25
 
26
26
 
@@ -42,7 +42,6 @@ class CifContainer():
42
42
  the existing document is opened.
43
43
  """
44
44
  self.picometer: bool = False
45
- self._hkl_file: str = ''
46
45
  self._abs_hkl_details: Dict[str, str] = {}
47
46
  if isinstance(file, str):
48
47
  self.fileobj = Path(file)
@@ -67,6 +66,16 @@ class CifContainer():
67
66
  self.shx = Shelxfile(verbose=True)
68
67
  self.shx.read_string(self.res_file_data[1:-1])
69
68
  self._on_load()
69
+ self.order = []
70
+ self.essential_keys = []
71
+ self.set_order_keys(order)
72
+ self.set_essential_keys(essential_keys)
73
+
74
+ def set_order_keys(self, keys: List[str]) -> None:
75
+ self.order = keys
76
+
77
+ def set_essential_keys(self, keys: List[str]) -> None:
78
+ self.essential_keys = keys
70
79
 
71
80
  @property
72
81
  def path_base(self) -> Path:
@@ -139,7 +148,6 @@ class CifContainer():
139
148
  self.doc.check_for_duplicates()
140
149
  except RuntimeError as e:
141
150
  raise GemmiError(e)
142
- self.order = order
143
151
  self.dsr_used = DSRFind(self.res_file_data).dsr_used
144
152
  self.atomic_struct: gemmi.SmallStructure = gemmi.make_small_structure_from_block(self.block)
145
153
  # A dictionary to convert Atom names like 'C1_2' or 'Ga3' into Element names like 'C' or 'Ga'
@@ -272,6 +280,8 @@ class CifContainer():
272
280
  if self.is_empty():
273
281
  print(f'File {filename} is empty.')
274
282
  return
283
+ if not self.is_writable(filename):
284
+ raise PermissionError(f'Failed to open {filename.resolve()} for writing: Operation not permitted')
275
285
  self.order_cif_keys()
276
286
  print('Saving CIF to', Path(filename).resolve())
277
287
  if Version(gemmi.__version__) > Version('0.6.3'):
@@ -285,6 +295,18 @@ class CifContainer():
285
295
  else:
286
296
  self.doc.write_file(str(filename), gemmi.cif.Style.Indent35)
287
297
 
298
+ def is_writable(self, filepath: Union[Path, str]) -> bool:
299
+ filepath = Path(filepath)
300
+ try:
301
+ exist = filepath.exists()
302
+ with open(filepath, 'wb+') as f:
303
+ f.write(b'')
304
+ if not exist:
305
+ Path(filepath).unlink(missing_ok=True)
306
+ return True
307
+ except Exception:
308
+ return False
309
+
288
310
  def order_cif_keys(self) -> None:
289
311
  """
290
312
  Brings the current CIF in the specific order of the order list.
@@ -324,14 +346,10 @@ class CifContainer():
324
346
  def hkl_file(self) -> str:
325
347
  hkl_loop: Loop = self.get_loop('_diffrn_refln_index_h')
326
348
  if hkl_loop and hkl_loop.width() > 4:
327
- if not self._hkl_file:
328
- self._hkl_file = self._hkl_from_cif_format(hkl_loop)
329
- return self._hkl_file
349
+ return self._hkl_from_cif_format(hkl_loop)
330
350
  else:
331
351
  # returns an empty string if no cif hkl was found:
332
- if not self._hkl_file:
333
- self._hkl_file = self._hkl_from_shelx()
334
- return self._hkl_file
352
+ return self._hkl_from_shelx()
335
353
 
336
354
  def _hkl_from_cif_format(self, hkl_loop: Loop) -> str:
337
355
  hkl_list = []
@@ -377,7 +395,7 @@ class CifContainer():
377
395
  return HKL(self.hkl_file, self.block.name, hklf_type=self.hklf_number).get_hkl_min_max()
378
396
 
379
397
  def _hklf_number_from_shelxl_file(self) -> int:
380
- if self.shx.hklf:
398
+ if self.shx and self.shx.hklf:
381
399
  return self.shx.hklf.n if self.shx.hklf.n != 0 else 4
382
400
  else:
383
401
  return 4
@@ -501,8 +519,11 @@ class CifContainer():
501
519
 
502
520
  def add_loop_from_columns(self, loop_tags: List[str],
503
521
  column_values: Union[List[List[str]], Tuple[Tuple[str]]]) -> gemmi.cif.Loop:
504
- gemmi_loop = self.init_loop(loop_tags)
505
- gemmi_loop.set_all_values(column_values)
522
+ try:
523
+ gemmi_loop = self.init_loop(loop_tags)
524
+ gemmi_loop.set_all_values(column_values)
525
+ except RuntimeError as err:
526
+ raise Exception('Gemmi raised an exception') from err
506
527
  return gemmi_loop
507
528
 
508
529
  @property
@@ -829,7 +850,7 @@ class CifContainer():
829
850
  publ_loop = self.block.find_loop('_geom_angle_publ_flag')
830
851
  angle = namedtuple('angle', ('label1', 'label2', 'label3', 'angle_val', 'symm1', 'symm2'))
831
852
  for label1, label2, label3, angle_val, symm1, symm2, publ in \
832
- zip(label1, label2, label3, angle_val, symm1, symm2, publ_loop):
853
+ zip(label1, label2, label3, angle_val, symm1, symm2, publ_loop):
833
854
  if (without_H and (self.ishydrogen(label1) or self.ishydrogen(label2) or
834
855
  self.ishydrogen(label3)) or (self.yes_not_set(publ))):
835
856
  continue
@@ -901,7 +922,7 @@ class CifContainer():
901
922
  hydr = namedtuple('HydrogenBond', ('label_d', 'label_h', 'label_a', 'dist_dh', 'dist_ha', 'dist_da',
902
923
  'angle_dha', 'symm'))
903
924
  for label_d, label_h, label_a, dist_dh, dist_ha, dist_da, angle_dha, symm, publ in (
904
- zip(label_d, label_h, label_a, dist_dh, dist_ha, dist_da, angle_dha, symm, publ_loop)):
925
+ zip(label_d, label_h, label_a, dist_dh, dist_ha, dist_da, angle_dha, symm, publ_loop)):
905
926
  if self.yes_not_set(publ):
906
927
  continue
907
928
  if self.picometer:
@@ -957,7 +978,7 @@ class CifContainer():
957
978
  """
958
979
  Check if there are keys not in the cif but in essential_keys and append them if so.
959
980
  """
960
- for key in essential_keys:
981
+ for key in self.essential_keys:
961
982
  if key not in all_keys:
962
983
  if self._is_centrokey(key):
963
984
  continue
@@ -212,6 +212,8 @@ order = [
212
212
  '_diffrn_radiation_monochromator',
213
213
  '_diffrn_radiation_polarisn_norm',
214
214
  '_diffrn_radiation_collimation',
215
+ '_bruker_diffrn_measurement_temperature_device',
216
+ '_bruker_diffrn_measurement_temperature_device_make',
215
217
  '_olex2_diffrn_ambient_temperature_device',
216
218
  '_diffrn_measurement_ambient_temperature_device_make',
217
219
  '_diffrn_radiation_probe',
@@ -361,3 +363,129 @@ order = [
361
363
  '_bruker_diffrn_runs_theta',
362
364
  '_bruker_diffrn_runs_distance',
363
365
  ]
366
+
367
+
368
+ # These keywords will end up in the CIF in any case:
369
+ essential_keys = (
370
+ # '_atom_sites_solution_secondary'
371
+ # '_diffrn_measurement_specimen_adhesive'
372
+ # '_diffrn_source_power'
373
+ # '_diffrn_source_target'
374
+ # '_olex2_diffrn_ambient_temperature_device'
375
+ '_diffrn_measurement_ambient_temperature_device_make',
376
+ '_atom_sites_solution_hydrogens',
377
+ '_atom_sites_solution_primary',
378
+ # '_audit_contact_author_name',
379
+ # '_audit_contact_author_address',
380
+ # '_audit_contact_author_email',
381
+ # '_audit_contact_author_phone',
382
+ '_audit_creation_method',
383
+ '_publ_section_references',
384
+ '_cell_angle_alpha',
385
+ '_cell_angle_beta',
386
+ '_cell_angle_gamma',
387
+ '_cell_formula_units_Z',
388
+ '_cell_length_a',
389
+ '_cell_length_b',
390
+ '_cell_length_c',
391
+ '_cell_measurement_reflns_used',
392
+ '_cell_measurement_temperature',
393
+ '_cell_measurement_theta_max',
394
+ '_cell_measurement_theta_min',
395
+ '_cell_volume',
396
+ '_chemical_absolute_configuration',
397
+ '_chemical_formula_moiety',
398
+ '_chemical_formula_sum',
399
+ '_chemical_formula_weight',
400
+ '_chemical_melting_point',
401
+ '_chemical_name_common',
402
+ '_chemical_name_systematic',
403
+ '_computing_cell_refinement',
404
+ '_computing_data_collection',
405
+ '_computing_data_reduction',
406
+ '_computing_molecular_graphics',
407
+ # '_computing_publication_material',
408
+ '_computing_structure_refinement',
409
+ '_computing_structure_solution',
410
+ # '_diffrn_ambient_environment',
411
+ '_diffrn_ambient_temperature',
412
+ '_diffrn_detector',
413
+ '_diffrn_detector_area_resol_mean',
414
+ '_diffrn_detector_type',
415
+ '_diffrn_measured_fraction_theta_full',
416
+ '_diffrn_measured_fraction_theta_max',
417
+ '_diffrn_measurement_device',
418
+ '_diffrn_measurement_device_type',
419
+ '_diffrn_measurement_method',
420
+ '_diffrn_measurement_specimen_support',
421
+ '_diffrn_radiation_monochromator',
422
+ '_diffrn_radiation_probe',
423
+ '_diffrn_radiation_type',
424
+ '_diffrn_radiation_wavelength',
425
+ '_diffrn_reflns_Laue_measured_fraction_full',
426
+ '_diffrn_reflns_Laue_measured_fraction_max',
427
+ '_diffrn_reflns_av_R_equivalents',
428
+ '_diffrn_reflns_av_unetI/netI',
429
+ '_diffrn_reflns_number',
430
+ '_diffrn_reflns_point_group_measured_fraction_full',
431
+ '_diffrn_reflns_point_group_measured_fraction_max',
432
+ '_diffrn_reflns_theta_full',
433
+ '_diffrn_reflns_theta_max',
434
+ '_diffrn_reflns_theta_min',
435
+ '_diffrn_source',
436
+ '_diffrn_source_current',
437
+ '_diffrn_source_type',
438
+ '_diffrn_source_voltage',
439
+ '_exptl_absorpt_coefficient_mu',
440
+ '_exptl_absorpt_correction_T_max',
441
+ '_exptl_absorpt_correction_T_min',
442
+ '_exptl_absorpt_correction_type',
443
+ '_exptl_absorpt_process_details',
444
+ '_exptl_crystal_F_000',
445
+ '_exptl_crystal_colour',
446
+ '_exptl_crystal_density_diffrn',
447
+ '_exptl_crystal_density_meas',
448
+ '_exptl_crystal_density_method',
449
+ '_exptl_crystal_description',
450
+ '_exptl_crystal_recrystallization_method',
451
+ '_exptl_crystal_size_max',
452
+ '_exptl_crystal_size_mid',
453
+ '_exptl_crystal_size_min',
454
+ '_exptl_special_details',
455
+ '_geom_special_details',
456
+ '_refine_ls_R_factor_all',
457
+ '_refine_ls_R_factor_gt',
458
+ '_refine_ls_abs_structure_Flack',
459
+ '_refine_ls_abs_structure_details',
460
+ '_refine_ls_extinction_coef',
461
+ '_refine_ls_extinction_method',
462
+ '_refine_ls_goodness_of_fit_ref',
463
+ '_refine_ls_hydrogen_treatment',
464
+ '_refine_ls_matrix_type',
465
+ '_refine_ls_number_parameters',
466
+ '_refine_ls_number_reflns',
467
+ '_refine_ls_number_restraints',
468
+ '_refine_ls_restrained_S_all',
469
+ '_refine_ls_shift/su_max',
470
+ '_refine_ls_shift/su_mean',
471
+ '_refine_ls_structure_factor_coef',
472
+ '_refine_ls_wR_factor_gt',
473
+ '_refine_ls_wR_factor_ref',
474
+ '_refine_ls_weighting_details',
475
+ '_refine_ls_weighting_scheme',
476
+ '_refine_special_details',
477
+ '_reflns_Friedel_coverage',
478
+ '_reflns_Friedel_fraction_full',
479
+ '_reflns_Friedel_fraction_max',
480
+ '_reflns_number_gt',
481
+ '_reflns_number_total',
482
+ '_reflns_special_details',
483
+ '_reflns_threshold_expression',
484
+ '_space_group_IT_number',
485
+ '_space_group_crystal_system',
486
+ '_space_group_name_H-M_alt',
487
+ '_space_group_name_Hall',
488
+ # '_space_group_symop_operation_xyz'
489
+ )
490
+ non_centrosymm_keys = ('_chemical_absolute_configuration', '_refine_ls_abs_structure_Flack',
491
+ '_refine_ls_abs_structure_details')
@@ -0,0 +1,229 @@
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+ import sys
5
+ from pathlib import Path
6
+ from typing import List, TYPE_CHECKING
7
+
8
+ import gemmi
9
+ from PyQt5 import QtWidgets, QtCore
10
+
11
+ from finalcif.cif import cif_order
12
+ from finalcif.ciforder.order_ui import Ui_CifOrderForm
13
+ from finalcif.gui import dialogs
14
+ from finalcif.gui.custom_classes import CifOrderItem
15
+ from finalcif.gui.dialogs import cif_file_save_dialog, show_general_warning
16
+ from finalcif.gui.new_key_dialog import NewKey
17
+
18
+ if TYPE_CHECKING:
19
+ from finalcif.tools.settings import FinalCifSettings
20
+
21
+
22
+ class Column(enum.IntEnum):
23
+ key = 0
24
+
25
+
26
+ class CifOrder(QtWidgets.QGroupBox):
27
+ def __init__(self, parent=None, cif_file: Path = None):
28
+ super().__init__(parent)
29
+ self.ui = Ui_CifOrderForm()
30
+ self.ui.setupUi(self)
31
+ self.cif = None
32
+ self.settings = None
33
+ self.essential_keys = cif_order.essential_keys
34
+ self.set_keys(cif_order.order)
35
+ if cif_file is not None:
36
+ self.set_keys_from_cif(cif_file)
37
+ self.connect_signals_and_slots()
38
+
39
+ def set_order_from_settings(self, settings: FinalCifSettings = None):
40
+ self.settings = settings
41
+ if settings is not None:
42
+ order = settings.load_settings_list('cif_order', 'order')
43
+ essentials = settings.load_settings_list('cif_order', 'essentials')
44
+ if order:
45
+ self.essential_keys = essentials
46
+ self.set_keys(order)
47
+
48
+ def connect_signals_and_slots(self):
49
+ self.ui.importCifPushButton.clicked.connect(self.import_cif)
50
+ self.ui.restoreDefaultPushButton.clicked.connect(self.restore_default)
51
+ self.ui.addKeyPushButton.clicked.connect(self.open_add_cif_key)
52
+ self.ui.saveSettingPushButton.clicked.connect(self.save_setting)
53
+ self.ui.deleteKeyPushButton.clicked.connect(self.delete_keys)
54
+ self.ui.exportToCifPushButton.clicked.connect(self.export_cif)
55
+ # Connect signals for moveUpPushButton
56
+ self.ui.moveUpPushButton.pressed.connect(lambda: self.start_repeated_action(self.move_row_up))
57
+ self.ui.moveUpPushButton.released.connect(self.stop_repeated_action)
58
+ # Connect signals for moveDownPushButton
59
+ self.ui.moveDownPushButton.pressed.connect(lambda: self.start_repeated_action(self.move_row_down))
60
+ self.ui.moveDownPushButton.released.connect(self.stop_repeated_action)
61
+ # Timer for repeated actions
62
+ self.timer = QtCore.QTimer(self)
63
+ self.timer.setInterval(170)
64
+ self.timer.timeout.connect(self.perform_repeated_action)
65
+ self.current_action = None
66
+
67
+ def start_repeated_action(self, action):
68
+ """Start the timer with the given action."""
69
+ self.current_action = action
70
+ action() # Perform the action immediately
71
+ self.timer.start()
72
+
73
+ def stop_repeated_action(self):
74
+ """Stop the timer and reset the current action."""
75
+ self.timer.stop()
76
+ self.current_action = None
77
+
78
+ def perform_repeated_action(self):
79
+ """Perform the currently set action."""
80
+ if self.current_action:
81
+ self.current_action()
82
+
83
+ def move_row_up(self):
84
+ current_row = self.ui.cifOrderTableWidget.currentRow()
85
+ if current_row > 0:
86
+ self.swap_rows(current_row, current_row - 1)
87
+ self.ui.cifOrderTableWidget.setCurrentCell(current_row - 1, Column.key)
88
+
89
+ def move_row_down(self):
90
+ table = self.ui.cifOrderTableWidget
91
+ current_row = table.currentRow()
92
+ if current_row < table.rowCount() - 1:
93
+ self.swap_rows(current_row, current_row + 1)
94
+ table.setCurrentCell(current_row + 1, Column.key)
95
+
96
+ def swap_rows(self, row1, row2):
97
+ table = self.ui.cifOrderTableWidget
98
+ item1 = table.item(row1, Column.key)
99
+ item2 = table.item(row2, Column.key)
100
+ if item1 and item2:
101
+ text1 = item1.text()
102
+ text2 = item2.text()
103
+ check_state1 = item1.checkState()
104
+ check_state2 = item2.checkState()
105
+ item1.setText(text2)
106
+ item1.setCheckState(check_state2)
107
+ item2.setText(text1)
108
+ item2.setCheckState(check_state1)
109
+
110
+ @property
111
+ def order_keys(self):
112
+ keys = []
113
+ for row in range(self.ui.cifOrderTableWidget.rowCount()):
114
+ key = self.ui.cifOrderTableWidget.item(row, Column.key).text()
115
+ keys.append(key)
116
+ return keys
117
+
118
+ @property
119
+ def order_essentials(self):
120
+ essentials = []
121
+ for row in range(self.ui.cifOrderTableWidget.rowCount()):
122
+ item = self.ui.cifOrderTableWidget.item(row, Column.key)
123
+ essential = item.checkState()
124
+ if essential:
125
+ essentials.append(item.text())
126
+ return essentials
127
+
128
+ def set_keys_from_cif(self, cif_file) -> None:
129
+ try:
130
+ self.cif = gemmi.cif.read_file(cif_file)
131
+ block = self.cif.sole_block()
132
+ except Exception:
133
+ show_general_warning(self, 'Unable to load file')
134
+ return
135
+ keys = [x.pair[0] for x in block if x.pair is not None]
136
+ essentials = [x.pair[0] for x in block if x.pair is not None and x.pair[1] == 'True']
137
+ self.set_essentials(essentials)
138
+ self.set_keys(keys)
139
+
140
+ def set_essentials(self, essentials):
141
+ self.essential_keys = essentials
142
+
143
+ def set_keys(self, order_keys: List[str] = None):
144
+ self.ui.cifOrderTableWidget.setRowCount(0)
145
+ row = 0
146
+ for key_text in order_keys:
147
+ if not key_text:
148
+ continue
149
+ self.ui.cifOrderTableWidget.insertRow(row)
150
+ self.set_row_text(key_text, row)
151
+ row += 1
152
+ # self.ui.cifOrderTableWidget.resizeColumnsToContents()
153
+
154
+ def set_row_text(self, key_text: str, row: int) -> None:
155
+ item1 = CifOrderItem(key_text)
156
+ item1.setFlags(item1.flags() | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled)
157
+ item1.setText(key_text)
158
+ if key_text in self.essential_keys:
159
+ item1.setEssential(True)
160
+ else:
161
+ item1.setEssential(False)
162
+ self.ui.cifOrderTableWidget.setItem(row, Column.key, item1)
163
+
164
+ def set_keys_from_settings(self, keys: List[str]):
165
+ self.set_keys(keys)
166
+
167
+ def import_cif(self):
168
+ cif_file = dialogs.cif_file_open_dialog()
169
+ if self._file_is_there(cif_file):
170
+ self.set_keys_from_cif(cif_file)
171
+
172
+ def _file_is_there(self, cif_file: str) -> bool:
173
+ return cif_file is not None and Path(cif_file).is_file() and Path(cif_file).exists()
174
+
175
+ def restore_default(self):
176
+ self.essential_keys = cif_order.essential_keys
177
+ self.settings.save_settings_list('cif_order', 'order', [])
178
+ self.settings.save_settings_list('cif_order', 'essentials', [])
179
+ self.set_keys(cif_order.order)
180
+
181
+ def open_add_cif_key(self):
182
+ new_key = NewKey(self)
183
+ new_key.show()
184
+ new_key.new_key_added.connect(self.add_key)
185
+
186
+ def add_key(self, key: str) -> None:
187
+ if key not in self.order_keys:
188
+ self.ui.cifOrderTableWidget.insertRow(0)
189
+ self.set_row_text(key, 0)
190
+
191
+ def save_setting(self):
192
+ self.settings.save_settings_list('cif_order', 'order', self.order_keys)
193
+ self.settings.save_settings_list('cif_order', 'essentials', self.order_essentials)
194
+ self.essential_keys = self.order_essentials
195
+
196
+ def delete_keys(self):
197
+ items = self.ui.cifOrderTableWidget.selectedItems()
198
+ for item in items:
199
+ self.ui.cifOrderTableWidget.removeRow(item.row())
200
+
201
+ def export_cif(self):
202
+ doc = gemmi.cif.Document()
203
+ blockname = 'FinalCif_keys_order'
204
+ block = doc.add_new_block(blockname)
205
+ for key in self.order_keys:
206
+ value = 'True' if key in self.order_essentials else 'False'
207
+ block.set_pair(key, value)
208
+ filename = cif_file_save_dialog(blockname.lower() + '.cif')
209
+ if not filename.strip():
210
+ return
211
+ try:
212
+ doc.write_file(filename, style=gemmi.cif.Style.Indent35)
213
+ except PermissionError:
214
+ if Path(filename).is_dir():
215
+ return
216
+ show_general_warning(self, 'No permission to write file to {}'.format(Path(filename).resolve()))
217
+
218
+
219
+ if __name__ == "__main__":
220
+ from finalcif.tools.settings import FinalCifSettings
221
+
222
+ app = QtWidgets.QApplication(sys.argv)
223
+ # form = CifOrder(parent=None, cif_file=Path('test-data/1000007.cif').resolve())
224
+ settings = FinalCifSettings()
225
+ form = CifOrder(parent=None)
226
+ form.set_order_from_settings(settings)
227
+ form.show()
228
+ form.raise_()
229
+ sys.exit(app.exec_())