finalcif 175__tar.gz → 177__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.
- {finalcif-175 → finalcif-177}/PKG-INFO +1 -1
- finalcif-177/finalcif/__init__.py +3 -0
- {finalcif-175 → finalcif-177}/finalcif/appwindow.py +9 -9
- {finalcif-175 → finalcif-177}/finalcif/cif/hkl.py +1 -4
- {finalcif-175 → finalcif-177}/finalcif/gui/finalcif_gui_ui.py +1 -1
- {finalcif-175 → finalcif-177}/finalcif/gui/finalcif_gui_ui.ui +1 -1
- {finalcif-175 → finalcif-177}/finalcif/gui/squeeze_dialog.py +53 -9
- {finalcif-175 → finalcif-177}/finalcif/gui/validators.py +10 -1
- {finalcif-175 → finalcif-177}/finalcif/tools/shred.py +1 -1
- finalcif-177/finalcif/tools/solvents.py +183 -0
- {finalcif-175 → finalcif-177}/finalcif/tools/squeeze.py +41 -3
- finalcif-177/uv.lock +1096 -0
- finalcif-175/finalcif/__init__.py +0 -3
- finalcif-175/uv.lock +0 -1056
- {finalcif-175 → finalcif-177}/.gitattributes +0 -0
- {finalcif-175 → finalcif-177}/.githooks/pre-push +0 -0
- {finalcif-175 → finalcif-177}/.gitignore +0 -0
- {finalcif-175 → finalcif-177}/.python-version +0 -0
- {finalcif-175 → finalcif-177}/.ruff.toml +0 -0
- {finalcif-175 → finalcif-177}/.run/Make Documentation MAC.run.xml +0 -0
- {finalcif-175 → finalcif-177}/.run/compile_ui_files.run.xml +0 -0
- {finalcif-175 → finalcif-177}/.run/make documentation Windows.run.xml +0 -0
- {finalcif-175 → finalcif-177}/AGENTS.md +0 -0
- {finalcif-175 → finalcif-177}/CODE_OF_CONDUCT.md +0 -0
- {finalcif-175 → finalcif-177}/CONTRIBUTING.md +0 -0
- {finalcif-175 → finalcif-177}/Finalcif_installer_win.spec +0 -0
- {finalcif-175 → finalcif-177}/Finalcif_linux_onefile.spec +0 -0
- {finalcif-175 → finalcif-177}/Finalcif_mac.spec +0 -0
- {finalcif-175 → finalcif-177}/LICENSE +0 -0
- {finalcif-175 → finalcif-177}/README.md +0 -0
- {finalcif-175 → finalcif-177}/finalcif/app_path.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/__init__.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/all_cif_dicts.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/atoms.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/checkcif/__init__.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/checkcif/checkcif.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/cif_dict_foo.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/cif_file_io.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/cif_keyword_mapping.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/cif_order.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/cod/__init__.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/cod/deposit.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/cod/deposit_check.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/cod/deposition_list.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/cod/doi.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/cod/upload.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/cod/website_parser.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/core_dict.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/modulation_dict.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/powder_dict.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/reference.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/restraints_dict.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/text.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/twin_dict.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/cif/vrf_entry.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/ciforder/__init__.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/ciforder/order.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/ciforder/order_ui.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/ciforder/order_ui.ui +0 -0
- {finalcif-175 → finalcif-177}/finalcif/datafiles/__init__.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/datafiles/bruker_data.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/datafiles/bruker_frame.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/datafiles/ccdc_mail.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/datafiles/data.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/datafiles/p4p_reader.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/datafiles/sadabs.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/datafiles/saint.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/datafiles/shelx_lst.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/datafiles/utils.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/equip_property/__init__.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/equip_property/author_loop_templates.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/equip_property/equipment.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/equip_property/properties.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/equip_property/tools.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/finalcif_start.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/__init__.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/block_combobox.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/checkcif_browser.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/cif_table_model.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/cif_table_view.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/combobox.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/custom_classes.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/dialogs.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/edit_button.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/equipmenttable.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/file_editor.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/import_selector.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/import_selector_ui.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/import_selector_ui.ui +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/loop_creator.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/loop_creator_ui.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/loop_creator_ui.ui +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/loops.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/mainstackwidget.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/mixins.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/new_key_dialog.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/new_key_dialog_ui.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/new_key_dialog_ui.ui +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/plaintextedit.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/playground.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/propertytable.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/shelx_navigation.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/single_line_textedit.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/spell_check_edit.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/syntax_highlighter.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/text_templates_ui.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/text_templates_ui.ui +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/text_value_editor.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/timers.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/vrf_classes.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/gui/vzs_viewer.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/icon/__init__.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/icon/finalcif.png +0 -0
- {finalcif-175 → finalcif-177}/finalcif/icon/finalcif2.ico +0 -0
- {finalcif-175 → finalcif-177}/finalcif/icon/finalcif2.png +0 -0
- {finalcif-175 → finalcif-177}/finalcif/icon/multitable.ico +0 -0
- {finalcif-175 → finalcif-177}/finalcif/icon/multitable.png +0 -0
- {finalcif-175 → finalcif-177}/finalcif/icon/multitable.xcf +0 -0
- {finalcif-175 → finalcif-177}/finalcif/report/__init__.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/report/archive_report.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/report/gui/__init__.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/report/gui/mainwindow.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/report/gui/mainwindow.ui +0 -0
- {finalcif-175 → finalcif-177}/finalcif/report/mtools.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/report/references.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/report/report_text.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/report/symm.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/report/tables.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/report/templated_report.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/__init__.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/bootstrap/bootstrap.min.css +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/machines/D8_VENTURE.cif +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/mathml2omml.xsl +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/miew/LICENSES +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/miew/Miew.css +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/miew/Miew.min.js +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/miew/lodash.min.js +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/miew/three.js +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/report.tex +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/report.tmpl +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/report2.tex +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/report_default.docx +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/report_fastmolwidget.tmpl +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/template1.docx +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/template_for_multitable.docx +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/template_text.docx +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/template_without_text.docx +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/templates.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/unicode2latex/FakePlasTeX/FakeContext.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/unicode2latex/FakePlasTeX/FakeTokenizer.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/unicode2latex/FakePlasTeX/README.md +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/unicode2latex/FakePlasTeX/TokenizerPassThru.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/unicode2latex/LICENSE +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/unicode2latex/__init__.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/unicode2latex/u2l.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/unicode2latex/unicode-math-table.tex +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/unicode2latex/unicode-math-xetex.sty +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/xsl/convert.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/xsl/xmltohtml.xsl +0 -0
- {finalcif-175 → finalcif-177}/finalcif/template/xsl/xmltolatex.xsl +0 -0
- {finalcif-175 → finalcif-177}/finalcif/tools/__init__.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/tools/chemparse.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/tools/download.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/tools/dsrmath.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/tools/formal_charge.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/tools/misc.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/tools/mol_file_writer.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/tools/options.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/tools/platon.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/tools/pupdate.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/tools/selfupdate.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/tools/settings.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/tools/space_groups.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/tools/spgr_format.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/tools/statusbar.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/tools/sumformula.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif/tools/z_from_packing.py +0 -0
- {finalcif-175 → finalcif-177}/finalcif.exe +0 -0
- {finalcif-175 → finalcif-177}/platon/README.txt +0 -0
- {finalcif-175 → finalcif-177}/platon/compile_platon.bat +0 -0
- {finalcif-175 → finalcif-177}/platon/platon_special.exe +0 -0
- {finalcif-175 → finalcif-177}/platon/platon_special.f +0 -0
- {finalcif-175 → finalcif-177}/pylock.toml +0 -0
- {finalcif-175 → finalcif-177}/pyproject.toml +0 -0
- {finalcif-175 → finalcif-177}/run_and_capture.py +0 -0
- {finalcif-175 → finalcif-177}/run_finalcif +0 -0
- {finalcif-175 → finalcif-177}/run_finalcif.bat +0 -0
- {finalcif-175 → finalcif-177}/screenshots/finalcif_checkcif.png +0 -0
- {finalcif-175 → finalcif-177}/screenshots/finalcif_details.png +0 -0
- {finalcif-175 → finalcif-177}/screenshots/finalcif_loops.png +0 -0
- {finalcif-175 → finalcif-177}/screenshots/finalcif_loops2.png +0 -0
- {finalcif-175 → finalcif-177}/screenshots/finalcif_main.png +0 -0
- {finalcif-175 → finalcif-177}/screenshots/finalcif_report.png +0 -0
- {finalcif-175 → finalcif-177}/screenshots/finalcif_responses.png +0 -0
- {finalcif-175 → finalcif-177}/screenshots/loops_column_buttons.png +0 -0
- {finalcif-175 → finalcif-177}/work/compare_z_platon.py +0 -0
- {finalcif-175 → finalcif-177}/work/diagnose_1519506.py +0 -0
- {finalcif-175 → finalcif-177}/work/diagnose_z.py +0 -0
- {finalcif-175 → finalcif-177}/work/investigate_failures.py +0 -0
- {finalcif-175 → finalcif-177}/work/validate_cod.py +0 -0
- {finalcif-175 → finalcif-177}/work/validate_zprime.py +0 -0
|
@@ -1311,15 +1311,15 @@ class AppWindow(QMainWindow):
|
|
|
1311
1311
|
self.validation_response_forms_list.append(vrf)
|
|
1312
1312
|
table.place_vrf_widget(insert_pos, vrf)
|
|
1313
1313
|
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1314
|
+
self.ui.CheckCifLogPlainTextEdit.appendHtml(self._vrf_summary_message(len(html_entries)))
|
|
1315
|
+
|
|
1316
|
+
def _vrf_summary_message(self, count: int) -> str:
|
|
1317
|
+
"""Return the log message summarizing how many VRFs a CheckCIF run produced."""
|
|
1318
|
+
if count:
|
|
1319
|
+
form_noun = 'form' if count == 1 else 'forms'
|
|
1320
|
+
return f'<br><b>{count} validation response {form_noun} added to the main table.</b>'
|
|
1321
|
+
levels = 'A, B or C' if self.ui.explainCAlertsCheckBox.isChecked() else 'A or B'
|
|
1322
|
+
return f'<br><b>No level {levels} alerts require a response.</b>'
|
|
1323
1323
|
|
|
1324
1324
|
def _vrf_insert_position(self) -> int:
|
|
1325
1325
|
"""Return the row index just after the last existing ``_vrf_*`` row in the main table.
|
|
@@ -34,10 +34,7 @@ class HKL:
|
|
|
34
34
|
|
|
35
35
|
@property
|
|
36
36
|
def hkl_as_cif(self) -> str:
|
|
37
|
-
|
|
38
|
-
return self._doc.as_string(style=Style.Simple)
|
|
39
|
-
else:
|
|
40
|
-
return self._doc.as_string(options=gemmi.cif.WriteOptions(Style.Simple))
|
|
37
|
+
return self._doc.as_string(options=gemmi.cif.WriteOptions(Style.Simple))
|
|
41
38
|
|
|
42
39
|
def _add_hkl_as_loop(self) -> None:
|
|
43
40
|
"""
|
|
@@ -2881,7 +2881,7 @@ class Ui_FinalCifWindow(object):
|
|
|
2881
2881
|
self.CancelPropertiesButton.setText(QCoreApplication.translate("FinalCifWindow", u"Cancel", None))
|
|
2882
2882
|
self.ExportPropertyButton.setText(QCoreApplication.translate("FinalCifWindow", u"Export", None))
|
|
2883
2883
|
self.BackFromLoopsPushButton.setText(QCoreApplication.translate("FinalCifWindow", u"Back to CIF Table", None))
|
|
2884
|
-
self.add_squeeze_pushButton.setText(QCoreApplication.translate("FinalCifWindow", u"Add Squeeze Info", None))
|
|
2884
|
+
self.add_squeeze_pushButton.setText(QCoreApplication.translate("FinalCifWindow", u"Add/Edit Squeeze Info", None))
|
|
2885
2885
|
self.deleteLoopButton.setText(QCoreApplication.translate("FinalCifWindow", u"Delete Loop", None))
|
|
2886
2886
|
self.newLoopPushButton.setText(QCoreApplication.translate("FinalCifWindow", u"Add New Loop", None))
|
|
2887
2887
|
self.revertLoopsPushButton.setText(QCoreApplication.translate("FinalCifWindow", u"Revert Changes", None))
|
|
@@ -2861,7 +2861,7 @@ The data source will be ignored until next program restart.</string>
|
|
|
2861
2861
|
<item row="0" column="2">
|
|
2862
2862
|
<widget class="QPushButton" name="add_squeeze_pushButton">
|
|
2863
2863
|
<property name="text">
|
|
2864
|
-
<string>Add Squeeze Info</string>
|
|
2864
|
+
<string>Add/Edit Squeeze Info</string>
|
|
2865
2865
|
</property>
|
|
2866
2866
|
</widget>
|
|
2867
2867
|
</item>
|
|
@@ -18,14 +18,15 @@ from pathlib import Path
|
|
|
18
18
|
from qtpy import QtCore, compat
|
|
19
19
|
from qtpy.QtGui import QColor
|
|
20
20
|
from qtpy.QtWidgets import (
|
|
21
|
-
QDialog, QDialogButtonBox, QHBoxLayout, QLabel, QMessageBox,
|
|
22
|
-
QPlainTextEdit, QPushButton, QSizePolicy, QTableWidget, QTableWidgetItem,
|
|
21
|
+
QAbstractItemView, QCompleter, QDialog, QDialogButtonBox, QHBoxLayout, QLabel, QLineEdit, QMessageBox,
|
|
22
|
+
QPlainTextEdit, QPushButton, QSizePolicy, QStyledItemDelegate, QTableWidget, QTableWidgetItem,
|
|
23
23
|
QVBoxLayout, QWidget, QApplication
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
from finalcif.cif.cif_file_io import CifContainer
|
|
27
|
-
from finalcif.cif.text import quote
|
|
28
|
-
from finalcif.tools.
|
|
27
|
+
from finalcif.cif.text import quote, retranslate_delimiter
|
|
28
|
+
from finalcif.tools.solvents import solvent_names
|
|
29
|
+
from finalcif.tools.squeeze import build_details_text, electrons_from_formula, resolve_formula
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
class SqueezeMode(Enum):
|
|
@@ -78,7 +79,9 @@ _MODE_CONFIG: dict[SqueezeMode, dict] = {
|
|
|
78
79
|
'details_key' : '_platon_squeeze_details',
|
|
79
80
|
'title' : 'PLATON SQUEEZE \u2013 Assign Solvent Content',
|
|
80
81
|
'info' : ('PLATON/SQUEEZE was used. '
|
|
81
|
-
'Assign the solvent formula per void <b>per unit cell</b>, e.g. <tt>2(H2O)</tt
|
|
82
|
+
'Assign the solvent formula per void <b>per unit cell</b>, e.g. <tt>2(H2O)</tt>.\n'
|
|
83
|
+
'Common solvent names like <tt>2 thf</tt> or <tt>toluene</tt> are '
|
|
84
|
+
'converted to their formula.'),
|
|
82
85
|
'details_label' : 'SQUEEZE details (<i>_platon_squeeze_details</i>):',
|
|
83
86
|
'electrons_col_header': 'Electrons\n(PLATON)',
|
|
84
87
|
},
|
|
@@ -91,7 +94,9 @@ _MODE_CONFIG: dict[SqueezeMode, dict] = {
|
|
|
91
94
|
'details_key' : '_smtbx_masks_special_details',
|
|
92
95
|
'title' : 'Olex2/SMTBX Masks \u2013 Assign Solvent Content',
|
|
93
96
|
'info' : ('Olex2/SMTBX solvent masks were used. '
|
|
94
|
-
'Assign the solvent formula per void <b>per unit cell</b>, e.g. <tt>2(H2O)</tt
|
|
97
|
+
'Assign the solvent formula per void <b>per unit cell</b>, e.g. <tt>2(H2O)</tt>.\n'
|
|
98
|
+
'Common solvent names like <tt>2 thf</tt> or <tt>toluene</tt> are '
|
|
99
|
+
'converted to their formula.'),
|
|
95
100
|
'details_label' : 'Masks details (<i>_smtbx_masks_special_details</i>):',
|
|
96
101
|
'electrons_col_header': 'Electrons\n(Masks)',
|
|
97
102
|
},
|
|
@@ -102,6 +107,19 @@ _COLOR_WARN = QColor(255, 200, 200) # light red
|
|
|
102
107
|
_COLOR_NEUTRAL = QColor(255, 255, 255) # white
|
|
103
108
|
|
|
104
109
|
|
|
110
|
+
class SolventNameDelegate(QStyledItemDelegate):
|
|
111
|
+
"""Line edit delegate that autocompletes common solvent names."""
|
|
112
|
+
|
|
113
|
+
def createEditor(self, parent: QWidget, option, index) -> QWidget:
|
|
114
|
+
editor = QLineEdit(parent)
|
|
115
|
+
completer = QCompleter(solvent_names(), editor)
|
|
116
|
+
completer.setCaseSensitivity(QtCore.Qt.CaseSensitivity.CaseInsensitive)
|
|
117
|
+
completer.setFilterMode(QtCore.Qt.MatchFlag.MatchContains)
|
|
118
|
+
completer.setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
|
|
119
|
+
editor.setCompleter(completer)
|
|
120
|
+
return editor
|
|
121
|
+
|
|
122
|
+
|
|
105
123
|
class SqueezeSolventDialog(QDialog):
|
|
106
124
|
"""
|
|
107
125
|
Modal dialog for entering PLATON SQUEEZE or Olex2/SMTBX solvent mask content.
|
|
@@ -212,6 +230,15 @@ class SqueezeSolventDialog(QDialog):
|
|
|
212
230
|
self.table.setColumnWidth(_COL_FORMULA, 160)
|
|
213
231
|
self.table.setColumnWidth(_COL_ELEC_CALC, 85)
|
|
214
232
|
self.table.verticalHeader().setVisible(False)
|
|
233
|
+
self.table.setEditTriggers(
|
|
234
|
+
QAbstractItemView.EditTrigger.CurrentChanged
|
|
235
|
+
| QAbstractItemView.EditTrigger.SelectedClicked
|
|
236
|
+
| QAbstractItemView.EditTrigger.DoubleClicked
|
|
237
|
+
| QAbstractItemView.EditTrigger.AnyKeyPressed
|
|
238
|
+
| QAbstractItemView.EditTrigger.EditKeyPressed
|
|
239
|
+
)
|
|
240
|
+
self._solvent_delegate = SolventNameDelegate(self.table)
|
|
241
|
+
self.table.setItemDelegateForColumn(_COL_FORMULA, self._solvent_delegate)
|
|
215
242
|
self.table.itemChanged.connect(self._on_formula_changed)
|
|
216
243
|
layout.addWidget(self.table)
|
|
217
244
|
|
|
@@ -242,7 +269,7 @@ class SqueezeSolventDialog(QDialog):
|
|
|
242
269
|
existing_details = self.cif[self._cfg['details_key']]
|
|
243
270
|
if existing_details:
|
|
244
271
|
self._details_user_modified = True
|
|
245
|
-
self.details_edit.setPlainText(existing_details)
|
|
272
|
+
self.details_edit.setPlainText(retranslate_delimiter(existing_details))
|
|
246
273
|
self.details_edit.textChanged.connect(self._on_details_text_changed)
|
|
247
274
|
|
|
248
275
|
# button box
|
|
@@ -408,9 +435,25 @@ class SqueezeSolventDialog(QDialog):
|
|
|
408
435
|
def _on_formula_changed(self, item: QTableWidgetItem) -> None:
|
|
409
436
|
if item.column() != _COL_FORMULA:
|
|
410
437
|
return
|
|
438
|
+
self._resolve_item_formula(item)
|
|
411
439
|
self._update_row_calculations(item.row())
|
|
412
440
|
self._regenerate_details()
|
|
413
441
|
|
|
442
|
+
@staticmethod
|
|
443
|
+
def _resolve_item_formula(item: QTableWidgetItem) -> None:
|
|
444
|
+
"""Replace a solvent name in *item* by its sum formula."""
|
|
445
|
+
text = item.text()
|
|
446
|
+
resolved = resolve_formula(text)
|
|
447
|
+
if resolved == text:
|
|
448
|
+
return
|
|
449
|
+
table = item.tableWidget()
|
|
450
|
+
blocked = table.signalsBlocked() if table else True
|
|
451
|
+
if table:
|
|
452
|
+
table.blockSignals(True)
|
|
453
|
+
item.setText(resolved)
|
|
454
|
+
if table:
|
|
455
|
+
table.blockSignals(blocked)
|
|
456
|
+
|
|
414
457
|
def _update_row_calculations(self, row_idx: int) -> None:
|
|
415
458
|
"""Recalculate Electrons (calc.) and Δ for a given row."""
|
|
416
459
|
formula_item = self.table.item(row_idx, _COL_FORMULA)
|
|
@@ -458,6 +501,7 @@ class SqueezeSolventDialog(QDialog):
|
|
|
458
501
|
item = self.table.item(row_idx, _COL_FORMULA)
|
|
459
502
|
if item:
|
|
460
503
|
item.setText(formula)
|
|
504
|
+
self._resolve_item_formula(item)
|
|
461
505
|
self._update_row_calculations(row_idx)
|
|
462
506
|
self.table.blockSignals(False)
|
|
463
507
|
self._regenerate_details()
|
|
@@ -475,7 +519,7 @@ class SqueezeSolventDialog(QDialog):
|
|
|
475
519
|
'nr' : row_idx + 1,
|
|
476
520
|
'volume' : vol_item.text() if vol_item else '?',
|
|
477
521
|
'electrons_platon': elec_item.text() if elec_item else '?',
|
|
478
|
-
'formula' : formula_item.text() if formula_item else '',
|
|
522
|
+
'formula' : resolve_formula(formula_item.text()) if formula_item else '',
|
|
479
523
|
})
|
|
480
524
|
text = build_details_text(void_rows, method=self._loop_mode.value)
|
|
481
525
|
# Block textChanged so our programmatic update doesn't set _details_user_modified
|
|
@@ -513,7 +557,7 @@ class SqueezeSolventDialog(QDialog):
|
|
|
513
557
|
new_formulae: list[str] = []
|
|
514
558
|
for row_idx in range(n_rows):
|
|
515
559
|
item = self.table.item(row_idx, _COL_FORMULA)
|
|
516
|
-
formula = item.text().strip() if item else '?'
|
|
560
|
+
formula = resolve_formula(item.text()).strip() if item else '?'
|
|
517
561
|
new_formulae.append(quote(formula) if formula else '?')
|
|
518
562
|
|
|
519
563
|
# Reconstruct column data with the updated content column
|
|
@@ -147,6 +147,15 @@ class Textlimits:
|
|
|
147
147
|
return False
|
|
148
148
|
|
|
149
149
|
|
|
150
|
+
class AnyTextLimits:
|
|
151
|
+
def __init__(self, help_text: str | None = None):
|
|
152
|
+
self.valid = self.validate_cif_key
|
|
153
|
+
self.help_text = help_text or 'Any text is allowed.'
|
|
154
|
+
|
|
155
|
+
def validate_cif_key(self, value: str) -> bool:
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
|
|
150
159
|
validators: dict[str, BaseLimits] = {
|
|
151
160
|
'_database_code_depnum_ccdc_archive' : Integerlimits(lower=0, upper=inf),
|
|
152
161
|
'_cell_measurement_reflns_used' : Integerlimits(lower=0, upper=inf),
|
|
@@ -279,7 +288,7 @@ validators: dict[str, BaseLimits] = {
|
|
|
279
288
|
'DGRS']),
|
|
280
289
|
'_atom_site_refinement_flags_adp' : Textlimits(options=['None', 'T', 'U', 'TU']),
|
|
281
290
|
'_atom_site_refinement_flags_occupancy': Textlimits(options=['None', 'P']),
|
|
282
|
-
'_atom_site_disorder_assembly' :
|
|
291
|
+
'_atom_site_disorder_assembly' : AnyTextLimits(),
|
|
283
292
|
'_atom_site_aniso_U_11' : Floatlimits(),
|
|
284
293
|
'_atom_site_aniso_U_22' : Floatlimits(),
|
|
285
294
|
'_atom_site_aniso_U_33' : Floatlimits(),
|
|
@@ -68,7 +68,7 @@ class ShredCIF:
|
|
|
68
68
|
if line.startswith(')'):
|
|
69
69
|
line = ';' + line[1:]
|
|
70
70
|
lines.append(line)
|
|
71
|
-
return '\n'.join(lines).lstrip('\n')
|
|
71
|
+
return '\n'.join(lines).lstrip('\n\r')
|
|
72
72
|
|
|
73
73
|
def _show_info(self, resname: Path, hklname: Path, resdata: str | None, hkldata: str | None) -> None:
|
|
74
74
|
if resdata and not hkldata:
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# ----------------------------------------------------------------------------
|
|
2
|
+
# "THE BEER-WARE LICENSE" (Revision 42):
|
|
3
|
+
# <dkratzert@gmx.de> wrote this file. As long as you retain
|
|
4
|
+
# this notice you can do whatever you want with this stuff. If we meet some day,
|
|
5
|
+
# and you think this stuff is worth it, you can buy me a beer in return.
|
|
6
|
+
# Dr. Daniel Kratzert
|
|
7
|
+
# ----------------------------------------------------------------------------
|
|
8
|
+
"""
|
|
9
|
+
Lookup table that translates common solvent names into chemical formulae.
|
|
10
|
+
|
|
11
|
+
This allows users to type ``thf``, ``water`` or ``2 toluene`` instead of the
|
|
12
|
+
corresponding sum formula when assigning solvent content to voids.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import re
|
|
17
|
+
|
|
18
|
+
# Canonical solvent name -> sum formula.
|
|
19
|
+
SOLVENTS: dict[str, str] = {
|
|
20
|
+
'water' : 'H2O',
|
|
21
|
+
'methanol' : 'CH4O',
|
|
22
|
+
'ethanol' : 'C2H6O',
|
|
23
|
+
'1-propanol' : 'C3H8O',
|
|
24
|
+
'2-propanol' : 'C3H8O',
|
|
25
|
+
'1-butanol' : 'C4H10O',
|
|
26
|
+
'tert-butanol' : 'C4H10O',
|
|
27
|
+
'ethylene glycol' : 'C2H6O2',
|
|
28
|
+
'acetone' : 'C3H6O',
|
|
29
|
+
'acetonitrile' : 'C2H3N',
|
|
30
|
+
'acetic acid' : 'C2H4O2',
|
|
31
|
+
'ethyl acetate' : 'C4H8O2',
|
|
32
|
+
'formic acid' : 'CH2O2',
|
|
33
|
+
'tetrahydrofuran' : 'C4H8O',
|
|
34
|
+
'2-methyltetrahydrofuran': 'C5H10O',
|
|
35
|
+
'tetrahydropyran' : 'C5H10O',
|
|
36
|
+
'1,4-dioxane' : 'C4H8O2',
|
|
37
|
+
'diethyl ether' : 'C4H10O',
|
|
38
|
+
'diisopropyl ether' : 'C6H14O',
|
|
39
|
+
'tert-butyl methyl ether': 'C5H12O',
|
|
40
|
+
'dimethoxyethane' : 'C4H10O2',
|
|
41
|
+
'diglyme' : 'C6H14O3',
|
|
42
|
+
'anisole' : 'C7H8O',
|
|
43
|
+
'dichloromethane' : 'CH2Cl2',
|
|
44
|
+
'chloroform' : 'CHCl3',
|
|
45
|
+
'carbon tetrachloride' : 'CCl4',
|
|
46
|
+
'1,2-dichloroethane' : 'C2H4Cl2',
|
|
47
|
+
'carbon disulfide' : 'CS2',
|
|
48
|
+
'benzene' : 'C6H6',
|
|
49
|
+
'toluene' : 'C7H8',
|
|
50
|
+
'xylene' : 'C8H10',
|
|
51
|
+
'mesitylene' : 'C9H12',
|
|
52
|
+
'chlorobenzene' : 'C6H5Cl',
|
|
53
|
+
'1,2-dichlorobenzene' : 'C6H4Cl2',
|
|
54
|
+
'bromobenzene' : 'C6H5Br',
|
|
55
|
+
'fluorobenzene' : 'C6H5F',
|
|
56
|
+
'1,2-difluorobenzene' : 'C6H4F2',
|
|
57
|
+
'nitrobenzene' : 'C6H5NO2',
|
|
58
|
+
'nitromethane' : 'CH3NO2',
|
|
59
|
+
'pentane' : 'C5H12',
|
|
60
|
+
'hexane' : 'C6H14',
|
|
61
|
+
'heptane' : 'C7H16',
|
|
62
|
+
'octane' : 'C8H18',
|
|
63
|
+
'cyclohexane' : 'C6H12',
|
|
64
|
+
'cyclopentane' : 'C5H10',
|
|
65
|
+
'pyridine' : 'C5H5N',
|
|
66
|
+
'triethylamine' : 'C6H15N',
|
|
67
|
+
'dimethylformamide' : 'C3H7NO',
|
|
68
|
+
'dimethylacetamide' : 'C4H9NO',
|
|
69
|
+
'dimethyl sulfoxide' : 'C2H6OS',
|
|
70
|
+
'n-methylpyrrolidone' : 'C5H9NO',
|
|
71
|
+
'hexamethylphosphoramide': 'C6H18N3OP',
|
|
72
|
+
'sulfolane' : 'C4H8O2S',
|
|
73
|
+
'aniline' : 'C6H7N',
|
|
74
|
+
'ammonia' : 'H3N',
|
|
75
|
+
'hydrazine' : 'H4N2',
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Alias -> canonical solvent name. Aliases must not look like a valid sum
|
|
79
|
+
# formula, otherwise a formula would silently be reinterpreted as a name.
|
|
80
|
+
ALIASES: dict[str, str] = {
|
|
81
|
+
'h2o' : 'water',
|
|
82
|
+
'aqua' : 'water',
|
|
83
|
+
'meoh' : 'methanol',
|
|
84
|
+
'etoh' : 'ethanol',
|
|
85
|
+
'cyclohexan' : 'cyclohexane',
|
|
86
|
+
'n-propanol' : '1-propanol',
|
|
87
|
+
'propanol' : '2-propanol',
|
|
88
|
+
'isopropanol' : '2-propanol',
|
|
89
|
+
'iso-propanol' : '2-propanol',
|
|
90
|
+
'ipa' : '2-propanol',
|
|
91
|
+
'iproh' : '2-propanol',
|
|
92
|
+
'n-butanol' : '1-butanol',
|
|
93
|
+
'butanol' : '1-butanol',
|
|
94
|
+
't-butanol' : 'tert-butanol',
|
|
95
|
+
'tert-butyl alcohol' : 'tert-butanol',
|
|
96
|
+
'tbuoh' : 'tert-butanol',
|
|
97
|
+
'glycol' : 'ethylene glycol',
|
|
98
|
+
'aceton' : 'acetone',
|
|
99
|
+
'mecn' : 'acetonitrile',
|
|
100
|
+
'ch3cn' : 'acetonitrile',
|
|
101
|
+
'etoac' : 'ethyl acetate',
|
|
102
|
+
'ethylacetate' : 'ethyl acetate',
|
|
103
|
+
'thf' : 'tetrahydrofuran',
|
|
104
|
+
'me-thf' : '2-methyltetrahydrofuran',
|
|
105
|
+
'2-me-thf' : '2-methyltetrahydrofuran',
|
|
106
|
+
'2-methyl-thf' : '2-methyltetrahydrofuran',
|
|
107
|
+
'methf' : '2-methyltetrahydrofuran',
|
|
108
|
+
'thp' : 'tetrahydropyran',
|
|
109
|
+
'dioxane' : '1,4-dioxane',
|
|
110
|
+
'ether' : 'diethyl ether',
|
|
111
|
+
'et2o' : 'diethyl ether',
|
|
112
|
+
'diethylether' : 'diethyl ether',
|
|
113
|
+
'ipr2o' : 'diisopropyl ether',
|
|
114
|
+
'mtbe' : 'tert-butyl methyl ether',
|
|
115
|
+
'tbme' : 'tert-butyl methyl ether',
|
|
116
|
+
'dme' : 'dimethoxyethane',
|
|
117
|
+
'glyme' : 'dimethoxyethane',
|
|
118
|
+
'1,2-dimethoxyethane' : 'dimethoxyethane',
|
|
119
|
+
'methoxybenzene' : 'anisole',
|
|
120
|
+
'dcm' : 'dichloromethane',
|
|
121
|
+
'methylene chloride' : 'dichloromethane',
|
|
122
|
+
'methylenechloride' : 'dichloromethane',
|
|
123
|
+
'dce' : '1,2-dichloroethane',
|
|
124
|
+
'benzol' : 'benzene',
|
|
125
|
+
'toluol' : 'toluene',
|
|
126
|
+
'tol' : 'toluene',
|
|
127
|
+
'p-xylene' : 'xylene',
|
|
128
|
+
'o-xylene' : 'xylene',
|
|
129
|
+
'm-xylene' : 'xylene',
|
|
130
|
+
'xylol' : 'xylene',
|
|
131
|
+
'1,3,5-trimethylbenzene': 'mesitylene',
|
|
132
|
+
'odcb' : '1,2-dichlorobenzene',
|
|
133
|
+
'dfb' : '1,2-difluorobenzene',
|
|
134
|
+
'n-pentane' : 'pentane',
|
|
135
|
+
'n-hexane' : 'hexane',
|
|
136
|
+
'n-heptane' : 'heptane',
|
|
137
|
+
'n-octane' : 'octane',
|
|
138
|
+
'pyr' : 'pyridine',
|
|
139
|
+
'py' : 'pyridine',
|
|
140
|
+
'net3' : 'triethylamine',
|
|
141
|
+
'et3n' : 'triethylamine',
|
|
142
|
+
'dmf' : 'dimethylformamide',
|
|
143
|
+
'n,n-dimethylformamide' : 'dimethylformamide',
|
|
144
|
+
'dma' : 'dimethylacetamide',
|
|
145
|
+
'n,n-dimethylacetamide' : 'dimethylacetamide',
|
|
146
|
+
'dmso' : 'dimethyl sulfoxide',
|
|
147
|
+
'dimethylsulfoxide' : 'dimethyl sulfoxide',
|
|
148
|
+
'nmp' : 'n-methylpyrrolidone',
|
|
149
|
+
'n-methyl-2-pyrrolidone': 'n-methylpyrrolidone',
|
|
150
|
+
'hmpa' : 'hexamethylphosphoramide',
|
|
151
|
+
'tetramethylene sulfone': 'sulfolane',
|
|
152
|
+
'aminobenzene' : 'aniline',
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
_NORMALIZE_RE = re.compile(r'[\s_]+')
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _norm(name: str) -> str:
|
|
159
|
+
"""Normalize a solvent name for case- and whitespace-insensitive lookup."""
|
|
160
|
+
return _NORMALIZE_RE.sub(' ', name.strip().lower()).replace(' ', '')
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
_LOOKUP: dict[str, str] = {_norm(name): formula for name, formula in SOLVENTS.items()}
|
|
164
|
+
_LOOKUP.update({_norm(alias): SOLVENTS[canonical] for alias, canonical in ALIASES.items()})
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def resolve_solvent(name: str) -> str | None:
|
|
168
|
+
"""Return the sum formula for a solvent name or alias.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
name: Solvent name such as ``'thf'``, ``'Water'`` or ``'diethyl ether'``.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
The sum formula, or ``None`` when *name* is not a known solvent.
|
|
175
|
+
"""
|
|
176
|
+
if not name:
|
|
177
|
+
return None
|
|
178
|
+
return _LOOKUP.get(_norm(name))
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def solvent_names() -> list[str]:
|
|
182
|
+
"""Return all canonical solvent names and aliases, sorted alphabetically."""
|
|
183
|
+
return sorted(set(SOLVENTS) | set(ALIASES))
|
|
@@ -15,6 +15,7 @@ from typing import Literal, TYPE_CHECKING
|
|
|
15
15
|
|
|
16
16
|
from finalcif.cif.atoms import element2num
|
|
17
17
|
from finalcif.tools.chemparse import parse_formula, ChemparseError
|
|
18
|
+
from finalcif.tools.solvents import resolve_solvent
|
|
18
19
|
|
|
19
20
|
if TYPE_CHECKING:
|
|
20
21
|
from finalcif.cif.cif_file_io import CifContainer
|
|
@@ -22,6 +23,42 @@ if TYPE_CHECKING:
|
|
|
22
23
|
# Atomic numbers (= electron count for neutral atoms) for elements common in crystallography.
|
|
23
24
|
ATOMIC_NUMBERS: dict[str, int] = element2num
|
|
24
25
|
|
|
26
|
+
# Splits an optional leading multiplier from the rest, e.g. '2(thf)' or '2 thf'.
|
|
27
|
+
_MULTIPLIER_RE = re.compile(r'^(\d+(?:\.\d+)?)\s*(.*)$', re.DOTALL)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def resolve_formula(text: str) -> str:
|
|
31
|
+
"""Translate a solvent name into its sum formula.
|
|
32
|
+
|
|
33
|
+
Accepts an optional leading multiplier, so ``'thf'``, ``'2 thf'`` and
|
|
34
|
+
``'2(thf)'`` all resolve. Text that does not name a known solvent is
|
|
35
|
+
returned unchanged, which keeps ordinary formulae such as ``'2(H2O)'``
|
|
36
|
+
working.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
text: User input, e.g. ``'2 toluene'``.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
A formula string such as ``'2(C7H8)'``, or *text* unchanged.
|
|
43
|
+
"""
|
|
44
|
+
stripped = text.strip()
|
|
45
|
+
if not stripped:
|
|
46
|
+
return text
|
|
47
|
+
multiplier = ''
|
|
48
|
+
remainder = stripped
|
|
49
|
+
match = _MULTIPLIER_RE.match(stripped)
|
|
50
|
+
if match:
|
|
51
|
+
multiplier, remainder = match.group(1), match.group(2).strip()
|
|
52
|
+
remainder = remainder.strip()
|
|
53
|
+
if remainder.startswith('(') and remainder.endswith(')'):
|
|
54
|
+
remainder = remainder[1:-1].strip()
|
|
55
|
+
formula = resolve_solvent(remainder)
|
|
56
|
+
if formula is None:
|
|
57
|
+
return stripped
|
|
58
|
+
if not multiplier or float(multiplier) == 1:
|
|
59
|
+
return formula
|
|
60
|
+
return f'{multiplier}({formula})'
|
|
61
|
+
|
|
25
62
|
|
|
26
63
|
def _normalize_squeeze_formula(formula_str: str) -> str:
|
|
27
64
|
"""
|
|
@@ -80,11 +117,12 @@ def electrons_from_formula(formula_str: str) -> int:
|
|
|
80
117
|
"""
|
|
81
118
|
Returns the total electron count for a chemical formula string.
|
|
82
119
|
|
|
83
|
-
Supports standard crystallographic formulae such as 'C4H8O', '2(H2O)', 'CHCl3'
|
|
120
|
+
Supports standard crystallographic formulae such as 'C4H8O', '2(H2O)', 'CHCl3'
|
|
121
|
+
as well as common solvent names like 'thf' or '2 toluene'.
|
|
84
122
|
Unknown elements contribute 0 electrons. Returns 0 for empty / placeholder strings.
|
|
85
123
|
|
|
86
124
|
Args:
|
|
87
|
-
formula_str: Chemical formula, e.g. '2(C4H8O)' or '
|
|
125
|
+
formula_str: Chemical formula, e.g. '2(C4H8O)', 'H2O' or '2 thf'.
|
|
88
126
|
|
|
89
127
|
Returns:
|
|
90
128
|
Integer electron count, or 0 when the formula cannot be parsed.
|
|
@@ -92,7 +130,7 @@ def electrons_from_formula(formula_str: str) -> int:
|
|
|
92
130
|
if not formula_str or formula_str.strip() in ('?', '.', ''):
|
|
93
131
|
return 0
|
|
94
132
|
try:
|
|
95
|
-
normalised = _normalize_squeeze_formula(formula_str
|
|
133
|
+
normalised = _normalize_squeeze_formula(resolve_formula(formula_str))
|
|
96
134
|
atom_dict = parse_formula(normalised)
|
|
97
135
|
return round(sum(ATOMIC_NUMBERS.get(el, 0) * count for el, count in atom_dict.items()))
|
|
98
136
|
except ChemparseError:
|