MoleditPy 4.8.3__tar.gz → 4.9.1__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.
- {moleditpy-4.8.3 → moleditpy-4.9.1}/PKG-INFO +2 -2
- {moleditpy-4.8.3 → moleditpy-4.9.1}/README.md +1 -1
- {moleditpy-4.8.3 → moleditpy-4.9.1}/pyproject.toml +1 -1
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/MoleditPy.egg-info/PKG-INFO +2 -2
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/MoleditPy.egg-info/SOURCES.txt +1 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/core/mol_geometry.py +53 -2
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/atom_item.py +7 -7
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/bond_item.py +5 -5
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/edit_actions_logic.py +20 -1
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/io_logic.py +70 -24
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/main_window_init.py +1 -1
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/molecular_scene_handler.py +2 -2
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/settings_dialog.py +4 -0
- moleditpy-4.9.1/src/moleditpy/ui/settings_tabs/settings_2d_cleanup_tab.py +146 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/settings_tabs/settings_2d_tab.py +6 -5
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/utils/default_settings.py +11 -6
- {moleditpy-4.8.3 → moleditpy-4.9.1}/LICENSE +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/setup.cfg +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/MoleditPy.egg-info/dependency_links.txt +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/MoleditPy.egg-info/entry_points.txt +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/MoleditPy.egg-info/requires.txt +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/MoleditPy.egg-info/top_level.txt +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/__init__.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/__main__.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/assets/file_icon.ico +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/assets/icon.icns +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/assets/icon.ico +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/assets/icon.png +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/core/__init__.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/core/molecular_data.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/main.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/plugins/__init__.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/plugins/plugin_interface.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/plugins/plugin_manager.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/plugins/plugin_manager_window.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/__init__.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/about_dialog.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/align_plane_dialog.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/alignment_dialog.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/analysis_window.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/angle_dialog.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/app_state.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/atom_picking.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/base_picking_dialog.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/bond_length_dialog.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/calculation_worker.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/chain_mixin.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/color_settings_dialog.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/compute_logic.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/constrained_optimization_dialog.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/custom_interactor_style.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/custom_qt_interactor.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/dialog_3d_picking_mixin.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/dialog_logic.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/dihedral_dialog.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/edit_3d_logic.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/export_logic.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/geometry_base_dialog.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/main_window.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/mirror_dialog.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/molecule_scene.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/move_group_dialog.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/move_selected_atoms_dialog.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/periodic_table_dialog.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/planarize_dialog.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/plugin_menu_manager.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/preview_molecule.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/settings_tabs/__init__.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/settings_tabs/settings_3d_tabs.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/settings_tabs/settings_other_tab.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/settings_tabs/settings_tab_base.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/string_importers.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/template_preview_item.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/template_preview_view.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/translation_dialog.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/ui_manager.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/user_template_dialog.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/view_3d_logic.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/ui/zoomable_view.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/utils/__init__.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/utils/constants.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/utils/hit_radius.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/utils/sip_isdeleted_safe.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/utils/suppress_log.py +0 -0
- {moleditpy-4.8.3 → moleditpy-4.9.1}/src/moleditpy/utils/system_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.9.1
|
|
4
4
|
Summary: A cross-platform, simple, and intuitive molecular structure editor built in Python. It allows 2D molecular drawing and 3D structure visualization. It supports exporting structure files for input to DFT calculation software.
|
|
5
5
|
Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -710,7 +710,7 @@ Dynamic: license-file
|
|
|
710
710
|
[](https://pypi.org/project/MoleditPy/)
|
|
711
711
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
712
712
|
[](https://github.com/HiroYokoyama/python_molecular_editor/actions)
|
|
713
|
-
[](https://codecov.io/gh/HiroYokoyama/python_molecular_editor)
|
|
714
714
|
[](https://github.com/HiroYokoyama/python_molecular_editor/blob/main/tests/MANUAL_CHECKLIST.md)
|
|
715
715
|
[](https://github.com/HiroYokoyama/python_molecular_editor/blob/main/tests/pylint-score.txt)
|
|
716
716
|
[](https://pepy.tech/projects/moleditpy)
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](https://pypi.org/project/MoleditPy/)
|
|
7
7
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
8
8
|
[](https://github.com/HiroYokoyama/python_molecular_editor/actions)
|
|
9
|
-
[](https://codecov.io/gh/HiroYokoyama/python_molecular_editor)
|
|
10
10
|
[](https://github.com/HiroYokoyama/python_molecular_editor/blob/main/tests/MANUAL_CHECKLIST.md)
|
|
11
11
|
[](https://github.com/HiroYokoyama/python_molecular_editor/blob/main/tests/pylint-score.txt)
|
|
12
12
|
[](https://pepy.tech/projects/moleditpy)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.9.1
|
|
4
4
|
Summary: A cross-platform, simple, and intuitive molecular structure editor built in Python. It allows 2D molecular drawing and 3D structure visualization. It supports exporting structure files for input to DFT calculation software.
|
|
5
5
|
Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -710,7 +710,7 @@ Dynamic: license-file
|
|
|
710
710
|
[](https://pypi.org/project/MoleditPy/)
|
|
711
711
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
712
712
|
[](https://github.com/HiroYokoyama/python_molecular_editor/actions)
|
|
713
|
-
[](https://codecov.io/gh/HiroYokoyama/python_molecular_editor)
|
|
714
714
|
[](https://github.com/HiroYokoyama/python_molecular_editor/blob/main/tests/MANUAL_CHECKLIST.md)
|
|
715
715
|
[](https://github.com/HiroYokoyama/python_molecular_editor/blob/main/tests/pylint-score.txt)
|
|
716
716
|
[](https://pepy.tech/projects/moleditpy)
|
|
@@ -69,6 +69,7 @@ src/moleditpy/ui/user_template_dialog.py
|
|
|
69
69
|
src/moleditpy/ui/view_3d_logic.py
|
|
70
70
|
src/moleditpy/ui/zoomable_view.py
|
|
71
71
|
src/moleditpy/ui/settings_tabs/__init__.py
|
|
72
|
+
src/moleditpy/ui/settings_tabs/settings_2d_cleanup_tab.py
|
|
72
73
|
src/moleditpy/ui/settings_tabs/settings_2d_tab.py
|
|
73
74
|
src/moleditpy/ui/settings_tabs/settings_3d_tabs.py
|
|
74
75
|
src/moleditpy/ui/settings_tabs/settings_other_tab.py
|
|
@@ -591,12 +591,63 @@ def _identify_problems_rdkit(
|
|
|
591
591
|
return None
|
|
592
592
|
|
|
593
593
|
|
|
594
|
-
|
|
594
|
+
_TARGET_BOND_LENGTH = 1.5 # matches RDKit's default (non-CoordGen) bond length
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def _normalize_bond_length(mol: Chem.Mol, target_length: float) -> None:
|
|
598
|
+
"""Rescale a molecule's 2D conformer so its average bond length matches target_length."""
|
|
599
|
+
if mol.GetNumBonds() == 0:
|
|
600
|
+
return
|
|
601
|
+
conf = mol.GetConformer()
|
|
602
|
+
total_len = 0.0
|
|
603
|
+
for bond in mol.GetBonds():
|
|
604
|
+
p1 = conf.GetAtomPosition(bond.GetBeginAtomIdx())
|
|
605
|
+
p2 = conf.GetAtomPosition(bond.GetEndAtomIdx())
|
|
606
|
+
total_len += ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5
|
|
607
|
+
avg_len = total_len / mol.GetNumBonds()
|
|
608
|
+
if avg_len <= 1e-6:
|
|
609
|
+
return
|
|
610
|
+
factor = target_length / avg_len
|
|
611
|
+
for i in range(mol.GetNumAtoms()):
|
|
612
|
+
pos = conf.GetAtomPosition(i)
|
|
613
|
+
conf.SetAtomPosition(i, (pos.x * factor, pos.y * factor, pos.z))
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def optimize_2d_coords(
|
|
617
|
+
mol: Chem.Mol,
|
|
618
|
+
prefer_coordgen: bool = False,
|
|
619
|
+
canonical_orientation: bool = True,
|
|
620
|
+
use_ring_templates: bool = False,
|
|
621
|
+
straighten_bonds: bool = False,
|
|
622
|
+
avoid_clashes: bool = False,
|
|
623
|
+
) -> Dict[int, Tuple[float, float]]:
|
|
595
624
|
"""Generate 2D coordinates using RDKit and return a map of (x, y) tuples."""
|
|
596
625
|
from rdkit.Chem import rdDepictor
|
|
597
626
|
|
|
598
|
-
|
|
627
|
+
sample_kwargs = (
|
|
628
|
+
{"nFlipsPerSample": 3, "nSample": 200, "sampleSeed": 0xF00D}
|
|
629
|
+
if avoid_clashes
|
|
630
|
+
else {}
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
rdDepictor.SetPreferCoordGen(prefer_coordgen)
|
|
634
|
+
try:
|
|
635
|
+
rdDepictor.Compute2DCoords(
|
|
636
|
+
mol,
|
|
637
|
+
canonOrient=canonical_orientation,
|
|
638
|
+
useRingTemplates=use_ring_templates,
|
|
639
|
+
**sample_kwargs, # type: ignore[arg-type] # RDKit's generated overload differs by platform.
|
|
640
|
+
)
|
|
641
|
+
if straighten_bonds:
|
|
642
|
+
rdDepictor.StraightenDepiction(mol)
|
|
643
|
+
finally:
|
|
644
|
+
rdDepictor.SetPreferCoordGen(False)
|
|
645
|
+
# CoordGen and the default generator use different internal bond-length
|
|
646
|
+
# conventions (~1.0 vs ~1.5), so normalize here to keep the on-screen
|
|
647
|
+
# scale consistent regardless of which algorithm produced the layout.
|
|
648
|
+
_normalize_bond_length(mol, _TARGET_BOND_LENGTH)
|
|
599
649
|
conf = mol.GetConformer()
|
|
650
|
+
|
|
600
651
|
new_positions = {}
|
|
601
652
|
for rdkit_atom in mol.GetAtoms():
|
|
602
653
|
if rdkit_atom.HasProp("_original_atom_id"):
|
|
@@ -95,18 +95,18 @@ class AtomItem(QGraphicsItem):
|
|
|
95
95
|
| QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges
|
|
96
96
|
)
|
|
97
97
|
self.setZValue(1)
|
|
98
|
+
self.font: QFont = QFont(FONT_FAMILY, 22, FONT_WEIGHT_BOLD)
|
|
98
99
|
self.update_style()
|
|
99
100
|
self.setAcceptHoverEvents(True)
|
|
100
101
|
self.hovered: bool = False
|
|
101
102
|
self.has_problem: bool = False
|
|
102
103
|
self.is_visible: bool = True
|
|
103
|
-
self.font: QFont = QFont(FONT_FAMILY, 20, FONT_WEIGHT_BOLD)
|
|
104
104
|
|
|
105
105
|
def update_style(self) -> None:
|
|
106
106
|
"""Refresh font, color, and visibility based on current scene settings."""
|
|
107
107
|
if sip_isdeleted_safe(self):
|
|
108
108
|
return
|
|
109
|
-
font_size =
|
|
109
|
+
font_size = 22
|
|
110
110
|
font_family = FONT_FAMILY
|
|
111
111
|
font_bold = True
|
|
112
112
|
font_italic = False
|
|
@@ -114,7 +114,7 @@ class AtomItem(QGraphicsItem):
|
|
|
114
114
|
|
|
115
115
|
scene: Any = self.scene()
|
|
116
116
|
if scene is not None:
|
|
117
|
-
font_size = scene.get_setting("atom_font_size_2d",
|
|
117
|
+
font_size = scene.get_setting("atom_font_size_2d", 22)
|
|
118
118
|
font_family = scene.get_setting("atom_font_family_2d", FONT_FAMILY)
|
|
119
119
|
font_bold = scene.get_setting("atom_font_bold_2d", True)
|
|
120
120
|
font_italic = scene.get_setting("atom_font_italic_2d", False)
|
|
@@ -136,14 +136,14 @@ class AtomItem(QGraphicsItem):
|
|
|
136
136
|
|
|
137
137
|
def visual_rect(self) -> QRectF:
|
|
138
138
|
"""Return the rectangle the atom draws into; highlights use this."""
|
|
139
|
-
font_size =
|
|
139
|
+
font_size = 22
|
|
140
140
|
font_family = FONT_FAMILY
|
|
141
141
|
font_bold = True
|
|
142
142
|
font_italic = False
|
|
143
143
|
font_underline = False
|
|
144
144
|
scene: Any = self.scene()
|
|
145
145
|
if scene is not None:
|
|
146
|
-
font_size = scene.get_setting("atom_font_size_2d",
|
|
146
|
+
font_size = scene.get_setting("atom_font_size_2d", 22)
|
|
147
147
|
font_family = scene.get_setting("atom_font_family_2d", FONT_FAMILY)
|
|
148
148
|
font_bold = scene.get_setting("atom_font_bold_2d", True)
|
|
149
149
|
font_italic = scene.get_setting("atom_font_italic_2d", False)
|
|
@@ -265,14 +265,14 @@ class AtomItem(QGraphicsItem):
|
|
|
265
265
|
if not self.is_visible:
|
|
266
266
|
return path
|
|
267
267
|
|
|
268
|
-
font_size =
|
|
268
|
+
font_size = 22
|
|
269
269
|
font_family = FONT_FAMILY
|
|
270
270
|
font_bold = True
|
|
271
271
|
font_italic = False
|
|
272
272
|
font_underline = False
|
|
273
273
|
scene: Any = self.scene()
|
|
274
274
|
if scene is not None:
|
|
275
|
-
font_size = scene.get_setting("atom_font_size_2d",
|
|
275
|
+
font_size = scene.get_setting("atom_font_size_2d", 22)
|
|
276
276
|
font_family = scene.get_setting("atom_font_family_2d", FONT_FAMILY)
|
|
277
277
|
font_bold = scene.get_setting("atom_font_bold_2d", True)
|
|
278
278
|
font_italic = scene.get_setting("atom_font_italic_2d", False)
|
|
@@ -254,7 +254,7 @@ class BondItem(QGraphicsItem):
|
|
|
254
254
|
else "bond_spacing_double_2d"
|
|
255
255
|
)
|
|
256
256
|
bond_offset = scene.get_setting(key, 3.5)
|
|
257
|
-
wedge_width = scene.get_setting("bond_wedge_width_2d",
|
|
257
|
+
wedge_width = scene.get_setting("bond_wedge_width_2d", 8.0)
|
|
258
258
|
|
|
259
259
|
# Qt prunes clicks whose shape() escapes boundingRect().
|
|
260
260
|
extra = (getattr(self, "order", 1) - 1) * bond_offset + 50 + wedge_width
|
|
@@ -270,7 +270,7 @@ class BondItem(QGraphicsItem):
|
|
|
270
270
|
font_size = 20
|
|
271
271
|
font_family = FONT_FAMILY
|
|
272
272
|
if scene is not None:
|
|
273
|
-
font_size = scene.get_setting("atom_font_size_2d",
|
|
273
|
+
font_size = scene.get_setting("atom_font_size_2d", 22)
|
|
274
274
|
font_family = scene.get_setting("atom_font_family_2d", FONT_FAMILY)
|
|
275
275
|
|
|
276
276
|
font = QFont(font_family, font_size, FONT_WEIGHT_BOLD)
|
|
@@ -363,11 +363,11 @@ class BondItem(QGraphicsItem):
|
|
|
363
363
|
bond_color = QColor(color_hex)
|
|
364
364
|
|
|
365
365
|
# Width and Cap Style
|
|
366
|
-
bond_width = scene.get_setting("bond_width_2d",
|
|
366
|
+
bond_width = scene.get_setting("bond_width_2d", 3.0)
|
|
367
367
|
cap_style_str = scene.get_setting("bond_cap_style_2d", "Round")
|
|
368
368
|
|
|
369
369
|
# Wedge/Dash settings
|
|
370
|
-
wedge_width_half = scene.get_setting("bond_wedge_width_2d",
|
|
370
|
+
wedge_width_half = scene.get_setting("bond_wedge_width_2d", 8.0)
|
|
371
371
|
num_dashes = int(scene.get_setting("bond_dash_count_2d", 8))
|
|
372
372
|
|
|
373
373
|
# Cap Style logic
|
|
@@ -531,7 +531,7 @@ class BondItem(QGraphicsItem):
|
|
|
531
531
|
font_family = FONT_FAMILY
|
|
532
532
|
_sc: Any = self.scene()
|
|
533
533
|
if _sc is not None:
|
|
534
|
-
font_size = _sc.get_setting("atom_font_size_2d",
|
|
534
|
+
font_size = _sc.get_setting("atom_font_size_2d", 22)
|
|
535
535
|
font_family = _sc.get_setting(
|
|
536
536
|
"atom_font_family_2d", FONT_FAMILY
|
|
537
537
|
)
|
|
@@ -1154,7 +1154,26 @@ class EditActionsManager:
|
|
|
1154
1154
|
try:
|
|
1155
1155
|
from moleditpy.core.mol_geometry import optimize_2d_coords
|
|
1156
1156
|
|
|
1157
|
-
|
|
1157
|
+
scene = self.host.init_manager.scene
|
|
1158
|
+
prefer_coordgen = bool(scene.get_setting("prefer_coordgen_2d", False))
|
|
1159
|
+
canonical_orientation = bool(
|
|
1160
|
+
scene.get_setting("cleanup_canonical_orientation_2d", True)
|
|
1161
|
+
)
|
|
1162
|
+
use_ring_templates = bool(
|
|
1163
|
+
scene.get_setting("cleanup_use_ring_templates_2d", False)
|
|
1164
|
+
)
|
|
1165
|
+
straighten_bonds = bool(
|
|
1166
|
+
scene.get_setting("cleanup_straighten_bonds_2d", False)
|
|
1167
|
+
)
|
|
1168
|
+
avoid_clashes = bool(scene.get_setting("cleanup_avoid_clashes_2d", False))
|
|
1169
|
+
new_positions = optimize_2d_coords(
|
|
1170
|
+
mol,
|
|
1171
|
+
prefer_coordgen,
|
|
1172
|
+
canonical_orientation,
|
|
1173
|
+
use_ring_templates,
|
|
1174
|
+
straighten_bonds,
|
|
1175
|
+
avoid_clashes,
|
|
1176
|
+
)
|
|
1158
1177
|
|
|
1159
1178
|
if not new_positions:
|
|
1160
1179
|
self.host.statusBar().showMessage( # type: ignore[union-attr]
|
|
@@ -30,6 +30,7 @@ from PyQt6.QtWidgets import (
|
|
|
30
30
|
QPushButton,
|
|
31
31
|
QFileDialog,
|
|
32
32
|
QMessageBox,
|
|
33
|
+
QWidget,
|
|
33
34
|
)
|
|
34
35
|
|
|
35
36
|
from rdkit import Chem
|
|
@@ -109,6 +110,63 @@ class IOManager:
|
|
|
109
110
|
lines[3] = self.fix_mol_counts_line(lines[3])
|
|
110
111
|
return "\n".join(lines)
|
|
111
112
|
|
|
113
|
+
@staticmethod
|
|
114
|
+
def _first_sdf_record(sdf_text: str) -> str:
|
|
115
|
+
"""Return the MOL block of the first record in SDF text.
|
|
116
|
+
|
|
117
|
+
Chem.SDMolSupplier(file_path) reads the file itself and cannot be
|
|
118
|
+
given pre-decoded text, so it never sees the encoding fallbacks in
|
|
119
|
+
_read_text_lines_flexible and mishandles CP932/Shift-JIS SDF files.
|
|
120
|
+
Extracting the first record's MOL block (everything before the first
|
|
121
|
+
"$$$$" separator) lets the already-decoded text go through
|
|
122
|
+
MolFromMolBlock instead, matching the .mol import path. Trailing SDF
|
|
123
|
+
property tags after "M END" are harmless — MolFromMolBlock stops
|
|
124
|
+
reading once it hits the end of the connection table.
|
|
125
|
+
"""
|
|
126
|
+
record_lines = []
|
|
127
|
+
for line in sdf_text.splitlines():
|
|
128
|
+
if line.strip() == "$$$$":
|
|
129
|
+
break
|
|
130
|
+
record_lines.append(line)
|
|
131
|
+
return "\n".join(record_lines)
|
|
132
|
+
|
|
133
|
+
def _load_mol_block_text(self, file_path: str) -> str:
|
|
134
|
+
"""Read a .mol/.sdf file flexibly-encoded and return a MolFromMolBlock-ready block.
|
|
135
|
+
|
|
136
|
+
Shared by load_mol_file and load_mol_file_for_3d_viewing so the
|
|
137
|
+
flexible-encoding read, .sdf first-record extraction, and V2000
|
|
138
|
+
counts-line fix live in one place.
|
|
139
|
+
"""
|
|
140
|
+
raw = "".join(self._read_text_lines_flexible(file_path))
|
|
141
|
+
if file_path.lower().endswith(".sdf"):
|
|
142
|
+
raw = self._first_sdf_record(raw)
|
|
143
|
+
return self.fix_mol_block(raw)
|
|
144
|
+
|
|
145
|
+
@staticmethod
|
|
146
|
+
def _read_text_lines_flexible(file_path: str) -> list[str]:
|
|
147
|
+
"""Read a text file's lines, tolerating non-UTF-8 encodings.
|
|
148
|
+
|
|
149
|
+
XYZ comment lines are free-form and files authored on Japanese
|
|
150
|
+
(or other non-UTF-8) locales are commonly saved as Shift-JIS/CP932
|
|
151
|
+
rather than UTF-8, which raises UnicodeDecodeError under a strict
|
|
152
|
+
utf-8 open(). Fall back through the encodings Windows/macOS/Linux
|
|
153
|
+
editors actually produce before giving up and replacing the
|
|
154
|
+
undecodable bytes, so a mojibake comment never blocks the atoms
|
|
155
|
+
themselves from loading.
|
|
156
|
+
"""
|
|
157
|
+
# euc_jp must be tried before cp932/shift_jis: cp932 silently accepts
|
|
158
|
+
# some genuinely EUC-JP byte sequences and decodes them to the wrong
|
|
159
|
+
# (mojibake) characters instead of raising, so trying cp932 first
|
|
160
|
+
# would never let euc_jp text reach its correct decoding.
|
|
161
|
+
for encoding in ("utf-8-sig", "euc_jp", "cp932", "shift_jis"):
|
|
162
|
+
try:
|
|
163
|
+
with open(file_path, "r", encoding=encoding) as f:
|
|
164
|
+
return f.readlines()
|
|
165
|
+
except UnicodeDecodeError:
|
|
166
|
+
continue
|
|
167
|
+
with open(file_path, "r", encoding="utf-8", errors="replace") as f:
|
|
168
|
+
return f.readlines()
|
|
169
|
+
|
|
112
170
|
@staticmethod
|
|
113
171
|
def _is_numeric_token(token: str) -> bool:
|
|
114
172
|
"""True if *token* parses as a float."""
|
|
@@ -326,7 +384,8 @@ class IOManager:
|
|
|
326
384
|
if status_bar is not None:
|
|
327
385
|
status_bar.showMessage(message)
|
|
328
386
|
if not os.environ.get("MOLEDITPY_HEADLESS"):
|
|
329
|
-
|
|
387
|
+
parent = self.host if isinstance(self.host, QWidget) else None
|
|
388
|
+
QMessageBox.warning(parent, title, message)
|
|
330
389
|
|
|
331
390
|
def load_xyz_file(self, file_path: str) -> Optional[Any]:
|
|
332
391
|
"""Load XYZ file and create RDKit Mol with charge prompt and bond determination."""
|
|
@@ -334,8 +393,7 @@ class IOManager:
|
|
|
334
393
|
return None
|
|
335
394
|
|
|
336
395
|
try:
|
|
337
|
-
|
|
338
|
-
return self._mol_from_xyz_lines(f.readlines())
|
|
396
|
+
return self._mol_from_xyz_lines(self._read_text_lines_flexible(file_path))
|
|
339
397
|
except (RuntimeError, TypeError, ValueError, UnicodeDecodeError) as e:
|
|
340
398
|
self.host.statusBar().showMessage(f"Error parsing XYZ file: {e}")
|
|
341
399
|
return None
|
|
@@ -777,19 +835,13 @@ class IOManager:
|
|
|
777
835
|
return
|
|
778
836
|
|
|
779
837
|
try:
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
mol: Optional[Chem.Mol] = Chem.MolFromMolBlock(
|
|
788
|
-
fixed_block, sanitize=True, removeHs=False
|
|
789
|
-
)
|
|
790
|
-
else:
|
|
791
|
-
suppl = Chem.SDMolSupplier(file_path, removeHs=False)
|
|
792
|
-
mol = next(suppl, None)
|
|
838
|
+
fixed_block = self._load_mol_block_text(file_path)
|
|
839
|
+
# MolFromMolBlock can come back empty on a malformed file; the
|
|
840
|
+
# stub does not say so, hence the explicit declaration. The None
|
|
841
|
+
# check below covers that.
|
|
842
|
+
mol: Optional[Chem.Mol] = Chem.MolFromMolBlock(
|
|
843
|
+
fixed_block, sanitize=True, removeHs=False
|
|
844
|
+
)
|
|
793
845
|
|
|
794
846
|
if mol is None:
|
|
795
847
|
raise ValueError("Failed to read molecule from file.")
|
|
@@ -991,14 +1043,8 @@ class IOManager:
|
|
|
991
1043
|
if not file_path:
|
|
992
1044
|
return
|
|
993
1045
|
try:
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
mol = next(suppl, None)
|
|
997
|
-
else:
|
|
998
|
-
with open(file_path, "r", encoding="utf-8", errors="replace") as fh:
|
|
999
|
-
raw = fh.read()
|
|
1000
|
-
fixed_block = self.fix_mol_block(raw)
|
|
1001
|
-
mol = Chem.MolFromMolBlock(fixed_block, sanitize=True, removeHs=False)
|
|
1046
|
+
fixed_block = self._load_mol_block_text(file_path)
|
|
1047
|
+
mol = Chem.MolFromMolBlock(fixed_block, sanitize=True, removeHs=False)
|
|
1002
1048
|
|
|
1003
1049
|
if mol is None:
|
|
1004
1050
|
raise ValueError("Failed to load molecule.")
|
|
@@ -225,7 +225,7 @@ class MainInitManager:
|
|
|
225
225
|
font = None
|
|
226
226
|
if scene is not None:
|
|
227
227
|
family = scene.get_setting("atom_font_family_2d", FONT_FAMILY)
|
|
228
|
-
size = scene.get_setting("atom_font_size_2d",
|
|
228
|
+
size = scene.get_setting("atom_font_size_2d", 22)
|
|
229
229
|
bold = scene.get_setting("atom_font_bold_2d", True)
|
|
230
230
|
font = QFont(
|
|
231
231
|
family, size, QFont.Weight.Bold if bold else QFont.Weight.Normal
|
|
@@ -369,7 +369,7 @@ class TemplateMixin:
|
|
|
369
369
|
and self.get_setting("template_fusing_enabled_2d", True)
|
|
370
370
|
and not alt_pressed
|
|
371
371
|
):
|
|
372
|
-
map_threshold = self.get_setting("template_fusing_distance_2d",
|
|
372
|
+
map_threshold = self.get_setting("template_fusing_distance_2d", 5.0)
|
|
373
373
|
for i, p in enumerate(points):
|
|
374
374
|
if atom_items[i] is not None:
|
|
375
375
|
continue
|
|
@@ -548,7 +548,7 @@ class TemplateMixin:
|
|
|
548
548
|
# Snap individual preview vertices to nearby atoms to reflect template fusing visually
|
|
549
549
|
vertex_atoms: List[Optional[AtomItem]] = [None] * len(points)
|
|
550
550
|
if self.get_setting("template_fusing_enabled_2d", True) and not alt_pressed:
|
|
551
|
-
fuse_dist = self.get_setting("template_fusing_distance_2d",
|
|
551
|
+
fuse_dist = self.get_setting("template_fusing_distance_2d", 5.0)
|
|
552
552
|
mapped_atoms = set(self.template_context.get("items", []))
|
|
553
553
|
used_indices = set()
|
|
554
554
|
click_map_threshold = max(0.5 * bond_len, 8.0)
|
|
@@ -23,6 +23,7 @@ from PyQt6.QtWidgets import (
|
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
from .settings_tabs.settings_2d_tab import Settings2DTab
|
|
26
|
+
from .settings_tabs.settings_2d_cleanup_tab import Settings2DCleanupTab
|
|
26
27
|
from .settings_tabs.settings_3d_tabs import Settings3DSceneTab, SettingsModelTab
|
|
27
28
|
from .settings_tabs.settings_other_tab import SettingsOtherTab
|
|
28
29
|
|
|
@@ -35,6 +36,7 @@ class SettingsDialog(QDialog):
|
|
|
35
36
|
def __init__(self, current_settings: Any, parent: Any = None) -> None:
|
|
36
37
|
super().__init__(parent)
|
|
37
38
|
self.tab_2d: Any = None
|
|
39
|
+
self.tab_2d_cleanup: Any = None
|
|
38
40
|
self.tab_bs: Any = None
|
|
39
41
|
self.tab_cpk: Any = None
|
|
40
42
|
self.tab_other: Any = None
|
|
@@ -57,6 +59,7 @@ class SettingsDialog(QDialog):
|
|
|
57
59
|
|
|
58
60
|
# Instantiate Tabs
|
|
59
61
|
self.tab_2d = Settings2DTab(self.default_settings, self)
|
|
62
|
+
self.tab_2d_cleanup = Settings2DCleanupTab(self.default_settings, self)
|
|
60
63
|
self.tab_scene = Settings3DSceneTab(self.default_settings, self)
|
|
61
64
|
self.tab_bs = SettingsModelTab(
|
|
62
65
|
"ball_stick",
|
|
@@ -86,6 +89,7 @@ class SettingsDialog(QDialog):
|
|
|
86
89
|
|
|
87
90
|
# Add Tabs to Widget
|
|
88
91
|
self.tab_widget.addTab(self.tab_2d, "2D Settings")
|
|
92
|
+
self.tab_widget.addTab(self.tab_2d_cleanup, "2D Cleanup")
|
|
89
93
|
self.tab_widget.addTab(self.tab_scene, "3D Scene")
|
|
90
94
|
self.tab_widget.addTab(self.tab_bs, "Ball & Stick")
|
|
91
95
|
self.tab_widget.addTab(self.tab_cpk, "CPK (Space-filling)")
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
MoleditPy — A Python-based molecular editing software
|
|
6
|
+
|
|
7
|
+
Author: Hiromichi Yokoyama
|
|
8
|
+
License: GPL-3.0 license
|
|
9
|
+
Repo: https://github.com/HiroYokoyama/python_molecular_editor
|
|
10
|
+
DOI: 10.5281/zenodo.17268532
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from collections.abc import Mapping
|
|
14
|
+
from typing import Any, Optional
|
|
15
|
+
|
|
16
|
+
from PyQt6.QtWidgets import (
|
|
17
|
+
QCheckBox,
|
|
18
|
+
QFormLayout,
|
|
19
|
+
QWidget,
|
|
20
|
+
)
|
|
21
|
+
from .settings_tab_base import SettingsTabBase
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Settings2DCleanupTab(SettingsTabBase):
|
|
25
|
+
"""Settings tab for the 'Clean Up 2D Structure' layout options."""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self, default_settings: Mapping[str, Any], parent: Optional[QWidget] = None
|
|
29
|
+
) -> None:
|
|
30
|
+
super().__init__(default_settings, parent)
|
|
31
|
+
self.prefer_coordgen_2d_checkbox: Any = None
|
|
32
|
+
self.cleanup_canonical_orientation_2d_checkbox: Any = None
|
|
33
|
+
self.cleanup_use_ring_templates_2d_checkbox: Any = None
|
|
34
|
+
self.cleanup_straighten_bonds_2d_checkbox: Any = None
|
|
35
|
+
self.cleanup_avoid_clashes_2d_checkbox: Any = None
|
|
36
|
+
# (checkbox, base tooltip) pairs for options CoordGen silently ignores.
|
|
37
|
+
self._coordgen_ignored: list[tuple[Any, str]] = []
|
|
38
|
+
self._setup_ui()
|
|
39
|
+
|
|
40
|
+
def _setup_ui(self) -> None:
|
|
41
|
+
form_layout = QFormLayout(self)
|
|
42
|
+
|
|
43
|
+
self.prefer_coordgen_2d_checkbox = QCheckBox()
|
|
44
|
+
self.prefer_coordgen_2d_checkbox.setToolTip(
|
|
45
|
+
"If checked, 'Clean Up 2D Structure' uses RDKit's CoordGen "
|
|
46
|
+
"algorithm, which typically produces nicer ring layouts than "
|
|
47
|
+
"the default generator."
|
|
48
|
+
)
|
|
49
|
+
form_layout.addRow(
|
|
50
|
+
"Prefer CoordGen for Cleanup:", self.prefer_coordgen_2d_checkbox
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
canonical_tooltip = (
|
|
54
|
+
"If checked, 'Clean Up 2D Structure' rotates the result into "
|
|
55
|
+
"RDKit's canonical orientation. Note that cleanup always "
|
|
56
|
+
"regenerates coordinates, so unchecking this keeps the "
|
|
57
|
+
"generator's raw orientation, not the pre-cleanup one."
|
|
58
|
+
)
|
|
59
|
+
self.cleanup_canonical_orientation_2d_checkbox = QCheckBox()
|
|
60
|
+
self.cleanup_canonical_orientation_2d_checkbox.setToolTip(canonical_tooltip)
|
|
61
|
+
form_layout.addRow(
|
|
62
|
+
"Canonical Orientation:", self.cleanup_canonical_orientation_2d_checkbox
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
ring_templates_tooltip = (
|
|
66
|
+
"If checked, 'Clean Up 2D Structure' uses RDKit's built-in "
|
|
67
|
+
"template shapes for strained polycyclic ring systems. Only a "
|
|
68
|
+
"small set of ring systems has a template, so most structures "
|
|
69
|
+
"are unaffected."
|
|
70
|
+
)
|
|
71
|
+
self.cleanup_use_ring_templates_2d_checkbox = QCheckBox()
|
|
72
|
+
self.cleanup_use_ring_templates_2d_checkbox.setToolTip(ring_templates_tooltip)
|
|
73
|
+
form_layout.addRow(
|
|
74
|
+
"Use Ring Templates:", self.cleanup_use_ring_templates_2d_checkbox
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
self.cleanup_straighten_bonds_2d_checkbox = QCheckBox()
|
|
78
|
+
self.cleanup_straighten_bonds_2d_checkbox.setToolTip(
|
|
79
|
+
"If checked, 'Clean Up 2D Structure' rotates the result so most "
|
|
80
|
+
"bonds align to 30°/90° angles."
|
|
81
|
+
)
|
|
82
|
+
form_layout.addRow(
|
|
83
|
+
"Straighten Bonds:", self.cleanup_straighten_bonds_2d_checkbox
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
avoid_clashes_tooltip = (
|
|
87
|
+
"If checked, 'Clean Up 2D Structure' samples extra layouts to "
|
|
88
|
+
"reduce atom/bond overlap. Mainly affects flexible acyclic "
|
|
89
|
+
"chains, and can be slower on large molecules."
|
|
90
|
+
)
|
|
91
|
+
self.cleanup_avoid_clashes_2d_checkbox = QCheckBox()
|
|
92
|
+
self.cleanup_avoid_clashes_2d_checkbox.setToolTip(avoid_clashes_tooltip)
|
|
93
|
+
form_layout.addRow("Avoid Clashes:", self.cleanup_avoid_clashes_2d_checkbox)
|
|
94
|
+
|
|
95
|
+
# RDKit's CoordGen algorithm silently ignores canonOrient,
|
|
96
|
+
# useRingTemplates and the clash-avoidance sampling parameters, so
|
|
97
|
+
# these are grayed out while it is active. StraightenDepiction runs
|
|
98
|
+
# afterwards on the finished depiction, so it still applies.
|
|
99
|
+
self._coordgen_ignored = [
|
|
100
|
+
(self.cleanup_canonical_orientation_2d_checkbox, canonical_tooltip),
|
|
101
|
+
(self.cleanup_use_ring_templates_2d_checkbox, ring_templates_tooltip),
|
|
102
|
+
(self.cleanup_avoid_clashes_2d_checkbox, avoid_clashes_tooltip),
|
|
103
|
+
]
|
|
104
|
+
self.prefer_coordgen_2d_checkbox.toggled.connect(
|
|
105
|
+
self._update_coordgen_dependent_options
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def _update_coordgen_dependent_options(self, prefer_coordgen: bool) -> None:
|
|
109
|
+
suffix = " (not applicable when Prefer CoordGen is checked)"
|
|
110
|
+
for checkbox, base_tooltip in self._coordgen_ignored:
|
|
111
|
+
checkbox.setEnabled(not prefer_coordgen)
|
|
112
|
+
checkbox.setToolTip(base_tooltip + (suffix if prefer_coordgen else ""))
|
|
113
|
+
|
|
114
|
+
def update_ui(self, settings_dict: Mapping[str, Any]) -> None:
|
|
115
|
+
prefer_coordgen = settings_dict.get("prefer_coordgen_2d", False)
|
|
116
|
+
self.prefer_coordgen_2d_checkbox.setChecked(prefer_coordgen)
|
|
117
|
+
self._update_coordgen_dependent_options(prefer_coordgen)
|
|
118
|
+
self.cleanup_canonical_orientation_2d_checkbox.setChecked(
|
|
119
|
+
settings_dict.get("cleanup_canonical_orientation_2d", True)
|
|
120
|
+
)
|
|
121
|
+
self.cleanup_use_ring_templates_2d_checkbox.setChecked(
|
|
122
|
+
settings_dict.get("cleanup_use_ring_templates_2d", False)
|
|
123
|
+
)
|
|
124
|
+
self.cleanup_straighten_bonds_2d_checkbox.setChecked(
|
|
125
|
+
settings_dict.get("cleanup_straighten_bonds_2d", False)
|
|
126
|
+
)
|
|
127
|
+
self.cleanup_avoid_clashes_2d_checkbox.setChecked(
|
|
128
|
+
settings_dict.get("cleanup_avoid_clashes_2d", False)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def get_settings(self) -> dict[str, Any]:
|
|
132
|
+
return {
|
|
133
|
+
"prefer_coordgen_2d": self.prefer_coordgen_2d_checkbox.isChecked(),
|
|
134
|
+
"cleanup_canonical_orientation_2d": (
|
|
135
|
+
self.cleanup_canonical_orientation_2d_checkbox.isChecked()
|
|
136
|
+
),
|
|
137
|
+
"cleanup_use_ring_templates_2d": (
|
|
138
|
+
self.cleanup_use_ring_templates_2d_checkbox.isChecked()
|
|
139
|
+
),
|
|
140
|
+
"cleanup_straighten_bonds_2d": (
|
|
141
|
+
self.cleanup_straighten_bonds_2d_checkbox.isChecked()
|
|
142
|
+
),
|
|
143
|
+
"cleanup_avoid_clashes_2d": (
|
|
144
|
+
self.cleanup_avoid_clashes_2d_checkbox.isChecked()
|
|
145
|
+
),
|
|
146
|
+
}
|
|
@@ -170,6 +170,7 @@ class Settings2DTab(SettingsTabBase):
|
|
|
170
170
|
self.atom_font_underline_2d_btn.setFont(underline_font)
|
|
171
171
|
|
|
172
172
|
style_row = QHBoxLayout()
|
|
173
|
+
style_row.setContentsMargins(0, 0, 0, 0)
|
|
173
174
|
style_row.setSpacing(4)
|
|
174
175
|
style_row.addWidget(self.atom_font_bold_2d_btn)
|
|
175
176
|
style_row.addWidget(self.atom_font_italic_2d_btn)
|
|
@@ -301,13 +302,13 @@ class Settings2DTab(SettingsTabBase):
|
|
|
301
302
|
self._update_color_buttons()
|
|
302
303
|
|
|
303
304
|
self.bond_width_2d_slider.setValue(
|
|
304
|
-
int(settings_dict.get("bond_width_2d",
|
|
305
|
+
int(settings_dict.get("bond_width_2d", 3.0) * 10)
|
|
305
306
|
)
|
|
306
307
|
self.bond_spacing_double_2d_slider.setValue(
|
|
307
|
-
int(settings_dict.get("bond_spacing_double_2d",
|
|
308
|
+
int(settings_dict.get("bond_spacing_double_2d", 5.0) * 10)
|
|
308
309
|
)
|
|
309
310
|
self.bond_spacing_triple_2d_slider.setValue(
|
|
310
|
-
int(settings_dict.get("bond_spacing_triple_2d",
|
|
311
|
+
int(settings_dict.get("bond_spacing_triple_2d", 6.0) * 10)
|
|
311
312
|
)
|
|
312
313
|
|
|
313
314
|
cap_style = settings_dict.get("bond_cap_style_2d", "Round")
|
|
@@ -316,7 +317,7 @@ class Settings2DTab(SettingsTabBase):
|
|
|
316
317
|
self.bond_cap_style_2d_combo.setCurrentIndex(idx)
|
|
317
318
|
|
|
318
319
|
self.bond_wedge_width_2d_slider.setValue(
|
|
319
|
-
int(settings_dict.get("bond_wedge_width_2d",
|
|
320
|
+
int(settings_dict.get("bond_wedge_width_2d", 8.0) * 10)
|
|
320
321
|
)
|
|
321
322
|
self.bond_dash_count_2d_slider.setValue(
|
|
322
323
|
settings_dict.get("bond_dash_count_2d", 8)
|
|
@@ -326,7 +327,7 @@ class Settings2DTab(SettingsTabBase):
|
|
|
326
327
|
self.atom_font_family_2d_combo.setCurrentFont(QFont(font_family))
|
|
327
328
|
|
|
328
329
|
self.atom_font_size_2d_slider.setValue(
|
|
329
|
-
settings_dict.get("atom_font_size_2d",
|
|
330
|
+
settings_dict.get("atom_font_size_2d", 22)
|
|
330
331
|
)
|
|
331
332
|
self.atom_font_bold_2d_btn.setChecked(
|
|
332
333
|
settings_dict.get("atom_font_bold_2d", True)
|
|
@@ -66,22 +66,27 @@ DEFAULT_SETTINGS = {
|
|
|
66
66
|
"bond_color": "#808080",
|
|
67
67
|
"cpk_colors": {},
|
|
68
68
|
# --- 2D Settings ---
|
|
69
|
-
"bond_width_2d":
|
|
70
|
-
"bond_spacing_double_2d":
|
|
71
|
-
"bond_spacing_triple_2d":
|
|
72
|
-
"atom_font_size_2d":
|
|
69
|
+
"bond_width_2d": 3.0,
|
|
70
|
+
"bond_spacing_double_2d": 5.0,
|
|
71
|
+
"bond_spacing_triple_2d": 6.0,
|
|
72
|
+
"atom_font_size_2d": 22,
|
|
73
73
|
"background_color_2d": "#FFFFFF",
|
|
74
74
|
"bond_color_2d": "#222222",
|
|
75
75
|
"atom_use_bond_color_2d": False,
|
|
76
76
|
"bond_cap_style_2d": "Round",
|
|
77
|
-
"bond_wedge_width_2d":
|
|
77
|
+
"bond_wedge_width_2d": 8.0,
|
|
78
78
|
"bond_dash_count_2d": 8,
|
|
79
79
|
"atom_font_family_2d": "Arial",
|
|
80
80
|
"atom_font_bold_2d": True,
|
|
81
81
|
"atom_font_italic_2d": False,
|
|
82
82
|
"atom_font_underline_2d": False,
|
|
83
83
|
"template_fusing_enabled_2d": True,
|
|
84
|
-
"template_fusing_distance_2d":
|
|
84
|
+
"template_fusing_distance_2d": 5.0,
|
|
85
85
|
"template_snapping_distance_2d": 14.0,
|
|
86
86
|
"bond_snapping_distance_2d": 14.0,
|
|
87
|
+
"prefer_coordgen_2d": False,
|
|
88
|
+
"cleanup_canonical_orientation_2d": True,
|
|
89
|
+
"cleanup_use_ring_templates_2d": False,
|
|
90
|
+
"cleanup_straighten_bonds_2d": False,
|
|
91
|
+
"cleanup_avoid_clashes_2d": False,
|
|
87
92
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|