MoleditPy-linux 3.0.2__tar.gz → 3.0.3__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 (79) hide show
  1. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/PKG-INFO +1 -1
  2. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/pyproject.toml +1 -1
  3. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/MoleditPy_linux.egg-info/PKG-INFO +1 -1
  4. moleditpy_linux-3.0.3/src/moleditpy_linux/ui/translation_dialog.py +340 -0
  5. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/utils/constants.py +1 -1
  6. moleditpy_linux-3.0.2/src/moleditpy_linux/ui/translation_dialog.py +0 -193
  7. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/LICENSE +0 -0
  8. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/README.md +0 -0
  9. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/setup.cfg +0 -0
  10. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/MoleditPy_linux.egg-info/SOURCES.txt +0 -0
  11. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/MoleditPy_linux.egg-info/dependency_links.txt +0 -0
  12. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/MoleditPy_linux.egg-info/entry_points.txt +0 -0
  13. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/MoleditPy_linux.egg-info/requires.txt +0 -0
  14. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/MoleditPy_linux.egg-info/top_level.txt +0 -0
  15. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/__init__.py +0 -0
  16. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/__main__.py +0 -0
  17. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/assets/file_icon.ico +0 -0
  18. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/assets/icon.icns +0 -0
  19. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/assets/icon.ico +0 -0
  20. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/assets/icon.png +0 -0
  21. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/core/__init__.py +0 -0
  22. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/core/mol_geometry.py +0 -0
  23. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/core/molecular_data.py +0 -0
  24. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/main.py +0 -0
  25. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/plugins/__init__.py +0 -0
  26. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/plugins/plugin_interface.py +0 -0
  27. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/plugins/plugin_manager.py +0 -0
  28. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/plugins/plugin_manager_window.py +0 -0
  29. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/__init__.py +0 -0
  30. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/about_dialog.py +0 -0
  31. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/align_plane_dialog.py +0 -0
  32. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/alignment_dialog.py +0 -0
  33. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/analysis_window.py +0 -0
  34. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/angle_dialog.py +0 -0
  35. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/app_state.py +0 -0
  36. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/atom_item.py +0 -0
  37. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/base_picking_dialog.py +0 -0
  38. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/bond_item.py +0 -0
  39. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/bond_length_dialog.py +0 -0
  40. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/calculation_worker.py +0 -0
  41. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/color_settings_dialog.py +0 -0
  42. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/compute_logic.py +0 -0
  43. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/constrained_optimization_dialog.py +0 -0
  44. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/custom_interactor_style.py +0 -0
  45. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/custom_qt_interactor.py +0 -0
  46. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/dialog_3d_picking_mixin.py +0 -0
  47. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/dialog_logic.py +0 -0
  48. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/dihedral_dialog.py +0 -0
  49. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/edit_3d_logic.py +0 -0
  50. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/edit_actions_logic.py +0 -0
  51. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/export_logic.py +0 -0
  52. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/geometry_base_dialog.py +0 -0
  53. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/io_logic.py +0 -0
  54. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/main_window.py +0 -0
  55. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/main_window_init.py +0 -0
  56. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/mirror_dialog.py +0 -0
  57. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/molecular_scene_handler.py +0 -0
  58. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/molecule_scene.py +0 -0
  59. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/move_group_dialog.py +0 -0
  60. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/periodic_table_dialog.py +0 -0
  61. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/planarize_dialog.py +0 -0
  62. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/settings_dialog.py +0 -0
  63. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/settings_tabs/__init__.py +0 -0
  64. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/settings_tabs/settings_2d_tab.py +0 -0
  65. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/settings_tabs/settings_3d_tabs.py +0 -0
  66. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/settings_tabs/settings_other_tab.py +0 -0
  67. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/settings_tabs/settings_tab_base.py +0 -0
  68. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/sip_isdeleted_safe.py +0 -0
  69. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/string_importers.py +0 -0
  70. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/template_preview_item.py +0 -0
  71. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/template_preview_view.py +0 -0
  72. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/ui_manager.py +0 -0
  73. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/user_template_dialog.py +0 -0
  74. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/view_3d_logic.py +0 -0
  75. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/ui/zoomable_view.py +0 -0
  76. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/utils/__init__.py +0 -0
  77. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/utils/default_settings.py +0 -0
  78. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/utils/sip_isdeleted_safe.py +0 -0
  79. {moleditpy_linux-3.0.2 → moleditpy_linux-3.0.3}/src/moleditpy_linux/utils/system_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy-linux
3
- Version: 3.0.2
3
+ Version: 3.0.3
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
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "MoleditPy-linux"
7
7
 
8
- version = "3.0.2"
8
+ version = "3.0.3"
9
9
 
10
10
  license = {file = "LICENSE"}
11
11
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy-linux
3
- Version: 3.0.2
3
+ Version: 3.0.3
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
@@ -0,0 +1,340 @@
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
+ import numpy as np
14
+ from PyQt6.QtWidgets import (
15
+ QCheckBox,
16
+ QHBoxLayout,
17
+ QLabel,
18
+ QLineEdit,
19
+ QMessageBox,
20
+ QPushButton,
21
+ QTabWidget,
22
+ QVBoxLayout,
23
+ QWidget,
24
+ )
25
+
26
+ try:
27
+ from .base_picking_dialog import BasePickingDialog
28
+ except ImportError:
29
+ from moleditpy_linux.ui.base_picking_dialog import BasePickingDialog
30
+
31
+ _TAB_ABSOLUTE = 0
32
+ _TAB_DELTA = 1
33
+
34
+
35
+ class TranslationDialog(BasePickingDialog):
36
+ def __init__(self, mol, main_window, preselected_atoms=None, parent=None):
37
+ super().__init__(mol, main_window, parent)
38
+ self.selected_atoms = set()
39
+
40
+ if preselected_atoms:
41
+ self.selected_atoms.update(preselected_atoms)
42
+
43
+ self.init_ui()
44
+
45
+ if self.selected_atoms:
46
+ self.show_atom_labels()
47
+ self.update_display()
48
+
49
+ # ------------------------------------------------------------------
50
+ # UI construction
51
+ # ------------------------------------------------------------------
52
+
53
+ def init_ui(self):
54
+ self.setWindowTitle("Translate Atoms")
55
+ self.setModal(False)
56
+ layout = QVBoxLayout(self)
57
+
58
+ self.tabs = QTabWidget()
59
+ self.tabs.addTab(self._build_absolute_tab(), "Absolute")
60
+ self.tabs.addTab(self._build_delta_tab(), "Delta")
61
+ self.tabs.currentChanged.connect(self._on_tab_changed)
62
+ layout.addWidget(self.tabs)
63
+
64
+ # Shared Close button row
65
+ close_row = QHBoxLayout()
66
+ close_row.addStretch()
67
+ close_btn = QPushButton("Close")
68
+ close_btn.clicked.connect(self.reject)
69
+ close_row.addWidget(close_btn)
70
+ layout.addLayout(close_row)
71
+
72
+ self.enable_picking()
73
+
74
+ def _build_absolute_tab(self):
75
+ widget = QWidget()
76
+ layout = QVBoxLayout(widget)
77
+
78
+ instr = QLabel(
79
+ "Click one atom to select it, then specify its target absolute coordinates (Å)."
80
+ )
81
+ instr.setWordWrap(True)
82
+ layout.addWidget(instr)
83
+
84
+ self.abs_selection_label = QLabel("No atom selected")
85
+ layout.addWidget(self.abs_selection_label)
86
+
87
+ coord_row = QHBoxLayout()
88
+ coord_row.addWidget(QLabel("X:"))
89
+ self.abs_x_input = QLineEdit("0.000")
90
+ coord_row.addWidget(self.abs_x_input)
91
+ coord_row.addWidget(QLabel("Y:"))
92
+ self.abs_y_input = QLineEdit("0.000")
93
+ coord_row.addWidget(self.abs_y_input)
94
+ coord_row.addWidget(QLabel("Z:"))
95
+ self.abs_z_input = QLineEdit("0.000")
96
+ coord_row.addWidget(self.abs_z_input)
97
+ layout.addLayout(coord_row)
98
+
99
+ self.move_mol_checkbox = QCheckBox("Move entire molecule")
100
+ self.move_mol_checkbox.setChecked(True)
101
+ self.move_mol_checkbox.stateChanged.connect(self._on_move_mol_toggled)
102
+ layout.addWidget(self.move_mol_checkbox)
103
+
104
+ btn_row = QHBoxLayout()
105
+ abs_clear_btn = QPushButton("Clear Selection")
106
+ abs_clear_btn.clicked.connect(self._abs_clear_selection)
107
+ btn_row.addWidget(abs_clear_btn)
108
+ origin_btn = QPushButton("Set to Origin")
109
+ origin_btn.setToolTip("Set target coordinates to the origin")
110
+ origin_btn.clicked.connect(self._set_origin)
111
+ btn_row.addWidget(origin_btn)
112
+ btn_row.addStretch()
113
+ self.abs_apply_btn = QPushButton("Move Molecule")
114
+ self.abs_apply_btn.clicked.connect(self.apply_absolute)
115
+ self.abs_apply_btn.setEnabled(False)
116
+ btn_row.addWidget(self.abs_apply_btn)
117
+ layout.addLayout(btn_row)
118
+
119
+ layout.addStretch()
120
+ return widget
121
+
122
+ def _build_delta_tab(self):
123
+ widget = QWidget()
124
+ layout = QVBoxLayout(widget)
125
+
126
+ instr = QLabel(
127
+ "Click atoms in the 3D view to select them, then specify the translation vector (Å)."
128
+ )
129
+ instr.setWordWrap(True)
130
+ layout.addWidget(instr)
131
+
132
+ self.delta_selection_label = QLabel("No atoms selected")
133
+ layout.addWidget(self.delta_selection_label)
134
+
135
+ vector_row = QHBoxLayout()
136
+ vector_row.addWidget(QLabel("dX:"))
137
+ self.dx_input = QLineEdit("0.0")
138
+ vector_row.addWidget(self.dx_input)
139
+ vector_row.addWidget(QLabel("dY:"))
140
+ self.dy_input = QLineEdit("0.0")
141
+ vector_row.addWidget(self.dy_input)
142
+ vector_row.addWidget(QLabel("dZ:"))
143
+ self.dz_input = QLineEdit("0.0")
144
+ vector_row.addWidget(self.dz_input)
145
+ layout.addLayout(vector_row)
146
+
147
+ btn_row = QHBoxLayout()
148
+ clear_btn = QPushButton("Clear Selection")
149
+ clear_btn.clicked.connect(self.clear_selection)
150
+ btn_row.addWidget(clear_btn)
151
+
152
+ select_all_btn = QPushButton("Select All Atoms")
153
+ select_all_btn.setToolTip("Select all atoms in the molecule")
154
+ select_all_btn.clicked.connect(self.select_all_atoms)
155
+ btn_row.addWidget(select_all_btn)
156
+
157
+ btn_row.addStretch()
158
+ self.apply_button = QPushButton("Apply Translation")
159
+ self.apply_button.clicked.connect(self.apply_translation)
160
+ self.apply_button.setEnabled(False)
161
+ btn_row.addWidget(self.apply_button)
162
+ layout.addLayout(btn_row)
163
+
164
+ layout.addStretch()
165
+ return widget
166
+
167
+ # ------------------------------------------------------------------
168
+ # Tab switching
169
+ # ------------------------------------------------------------------
170
+
171
+ def _on_tab_changed(self, index):
172
+ self.selected_atoms.clear()
173
+ self.clear_atom_labels()
174
+ self.update_display()
175
+
176
+ # ------------------------------------------------------------------
177
+ # Atom picking dispatch
178
+ # ------------------------------------------------------------------
179
+
180
+ def on_atom_picked(self, atom_idx):
181
+ if self.tabs.currentIndex() == _TAB_ABSOLUTE:
182
+ self._abs_on_atom_picked(atom_idx)
183
+ else:
184
+ self._delta_on_atom_picked(atom_idx)
185
+
186
+ def _abs_on_atom_picked(self, atom_idx):
187
+ # Enforce single selection: replace previous atom
188
+ self.selected_atoms = {atom_idx}
189
+ self._populate_abs_inputs_from_atom(atom_idx)
190
+ self.show_atom_labels()
191
+ self.update_display()
192
+
193
+ def _delta_on_atom_picked(self, atom_idx):
194
+ if atom_idx in self.selected_atoms:
195
+ self.selected_atoms.remove(atom_idx)
196
+ else:
197
+ self.selected_atoms.add(atom_idx)
198
+ self.show_atom_labels()
199
+ self.update_display()
200
+
201
+ # ------------------------------------------------------------------
202
+ # Absolute tab helpers
203
+ # ------------------------------------------------------------------
204
+
205
+ def _populate_abs_inputs_from_atom(self, atom_idx):
206
+ pos = self.main_window.view_3d_manager.current_mol.GetConformer().GetPositions()[atom_idx]
207
+ self.abs_x_input.setText(f"{pos[0]:.4f}")
208
+ self.abs_y_input.setText(f"{pos[1]:.4f}")
209
+ self.abs_z_input.setText(f"{pos[2]:.4f}")
210
+
211
+ def _abs_clear_selection(self):
212
+ self.selected_atoms.clear()
213
+ self.clear_atom_labels()
214
+ self.abs_x_input.setText("0.000")
215
+ self.abs_y_input.setText("0.000")
216
+ self.abs_z_input.setText("0.000")
217
+ self.update_display()
218
+
219
+ def _set_origin(self):
220
+ self.abs_x_input.setText("0.0000")
221
+ self.abs_y_input.setText("0.0000")
222
+ self.abs_z_input.setText("0.0000")
223
+
224
+ def _on_move_mol_toggled(self, state):
225
+ label = "Move Molecule" if self.move_mol_checkbox.isChecked() else "Move Atom"
226
+ self.abs_apply_btn.setText(label)
227
+
228
+ def apply_absolute(self):
229
+ self.mol = self.main_window.view_3d_manager.current_mol
230
+ if len(self.selected_atoms) != 1:
231
+ QMessageBox.warning(self, "Warning", "Please select exactly one atom.")
232
+ return
233
+
234
+ try:
235
+ tx = float(self.abs_x_input.text())
236
+ ty = float(self.abs_y_input.text())
237
+ tz = float(self.abs_z_input.text())
238
+ except ValueError:
239
+ QMessageBox.warning(self, "Warning", "Please enter valid numbers for X, Y, Z.")
240
+ return
241
+
242
+ atom_idx = next(iter(self.selected_atoms))
243
+ positions = self.mol.GetConformer().GetPositions()
244
+ current = positions[atom_idx]
245
+ delta = np.array([tx, ty, tz]) - current
246
+
247
+ if np.allclose(delta, 0):
248
+ return
249
+
250
+ if self.move_mol_checkbox.isChecked():
251
+ positions += delta
252
+ else:
253
+ positions[atom_idx] += delta
254
+
255
+ self._update_molecule_geometry(positions)
256
+ self._push_undo()
257
+ self.show_atom_labels()
258
+
259
+ # ------------------------------------------------------------------
260
+ # Delta tab methods (unchanged logic)
261
+ # ------------------------------------------------------------------
262
+
263
+ def clear_selection(self):
264
+ self.selected_atoms.clear()
265
+ self.clear_atom_labels()
266
+ self.update_display()
267
+
268
+ def select_all_atoms(self):
269
+ try:
270
+ if hasattr(self, "mol") and self.mol is not None:
271
+ self.selected_atoms = set(range(self.mol.GetNumAtoms()))
272
+ else:
273
+ self.selected_atoms = (
274
+ set(self.main_window.state_manager.data.atoms.keys())
275
+ if hasattr(self.main_window.state_manager, "data")
276
+ else set()
277
+ )
278
+ self.show_atom_labels()
279
+ self.update_display()
280
+ except (AttributeError, RuntimeError, TypeError, KeyError) as e:
281
+ QMessageBox.warning(self, "Warning", f"Failed to select all atoms: {e}")
282
+
283
+ def apply_translation(self):
284
+ self.mol = self.main_window.view_3d_manager.current_mol
285
+ if not self.selected_atoms:
286
+ QMessageBox.warning(self, "Warning", "Please select at least one atom.")
287
+ return
288
+
289
+ try:
290
+ dx = float(self.dx_input.text())
291
+ dy = float(self.dy_input.text())
292
+ dz = float(self.dz_input.text())
293
+ except ValueError:
294
+ QMessageBox.warning(self, "Warning", "Please enter valid numbers for dx, dy, dz.")
295
+ return
296
+
297
+ if dx == 0 and dy == 0 and dz == 0:
298
+ return
299
+
300
+ translation_vec = np.array([dx, dy, dz])
301
+ positions = self.mol.GetConformer().GetPositions()
302
+ for atom_idx in self.selected_atoms:
303
+ positions[atom_idx] += translation_vec
304
+
305
+ self._update_molecule_geometry(positions)
306
+ self._push_undo()
307
+ self.show_atom_labels()
308
+
309
+ # ------------------------------------------------------------------
310
+ # Shared display update
311
+ # ------------------------------------------------------------------
312
+
313
+ def update_display(self):
314
+ tab = self.tabs.currentIndex()
315
+ count = len(self.selected_atoms)
316
+
317
+ if tab == _TAB_ABSOLUTE:
318
+ if count == 0:
319
+ self.abs_selection_label.setText("Click one atom to select it")
320
+ self.abs_apply_btn.setEnabled(False)
321
+ else:
322
+ atom_idx = next(iter(self.selected_atoms))
323
+ sym = self.mol.GetAtomWithIdx(atom_idx).GetSymbol()
324
+ self.abs_selection_label.setText(f"Selected: atom {atom_idx} ({sym})")
325
+ self.abs_apply_btn.setEnabled(True)
326
+ else:
327
+ if count == 0:
328
+ self.delta_selection_label.setText("Click atoms to select (minimum 1 required)")
329
+ self.apply_button.setEnabled(False)
330
+ else:
331
+ self.delta_selection_label.setText(f"Selected {count} atom{'s' if count != 1 else ''}")
332
+ self.apply_button.setEnabled(True)
333
+
334
+ def show_atom_labels(self):
335
+ if self.selected_atoms:
336
+ sorted_atoms = sorted(self.selected_atoms)
337
+ pairs = [(idx, str(i + 1)) for i, idx in enumerate(sorted_atoms)]
338
+ self.show_atom_labels_for(pairs)
339
+ else:
340
+ self.clear_atom_labels()
@@ -16,7 +16,7 @@ from PyQt6.QtGui import QColor, QFont
16
16
  from rdkit import Chem
17
17
 
18
18
  # Version
19
- VERSION = "3.0.2"
19
+ VERSION = "3.0.3"
20
20
 
21
21
  ATOM_RADIUS = 18
22
22
  BOND_OFFSET = 3.5
@@ -1,193 +0,0 @@
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
- import numpy as np
14
- from PyQt6.QtWidgets import (
15
- QHBoxLayout,
16
- QLabel,
17
- QLineEdit,
18
- QMessageBox,
19
- QPushButton,
20
- QVBoxLayout,
21
- )
22
-
23
- try:
24
- from .base_picking_dialog import BasePickingDialog
25
- except ImportError:
26
- from moleditpy_linux.ui.base_picking_dialog import BasePickingDialog
27
-
28
-
29
- class TranslationDialog(BasePickingDialog):
30
- def __init__(self, mol, main_window, preselected_atoms=None, parent=None):
31
- super().__init__(mol, main_window, parent)
32
- self.selected_atoms = set()
33
-
34
- # Add preselected atoms
35
- if preselected_atoms:
36
- self.selected_atoms.update(preselected_atoms)
37
-
38
- self.init_ui()
39
-
40
- # Add labels to preselected atoms
41
- if self.selected_atoms:
42
- self.show_atom_labels()
43
- self.update_display()
44
-
45
- def init_ui(self):
46
- self.setWindowTitle("Translate Atoms")
47
- self.setModal(False)
48
- layout = QVBoxLayout(self)
49
-
50
- # Instructions
51
- instruction_label = QLabel(
52
- "Click atoms in the 3D view to select them for translation. Specify the translation vector in Å."
53
- )
54
- instruction_label.setWordWrap(True)
55
- layout.addWidget(instruction_label)
56
-
57
- # Selected atoms display
58
- self.selection_label = QLabel("No atoms selected")
59
- layout.addWidget(self.selection_label)
60
-
61
- # Translation vector input
62
- vector_layout = QHBoxLayout()
63
- vector_layout.addWidget(QLabel("dX:"))
64
- self.dx_input = QLineEdit("0.0")
65
- vector_layout.addWidget(self.dx_input)
66
-
67
- vector_layout.addWidget(QLabel("dY:"))
68
- self.dy_input = QLineEdit("0.0")
69
- vector_layout.addWidget(self.dy_input)
70
-
71
- vector_layout.addWidget(QLabel("dZ:"))
72
- self.dz_input = QLineEdit("0.0")
73
- vector_layout.addWidget(self.dz_input)
74
-
75
- layout.addLayout(vector_layout)
76
-
77
- # Buttons
78
- button_layout = QHBoxLayout()
79
- self.clear_button = QPushButton("Clear Selection")
80
- self.clear_button.clicked.connect(self.clear_selection)
81
- button_layout.addWidget(self.clear_button)
82
-
83
- # Select all atoms button
84
- self.select_all_button = QPushButton("Select All Atoms")
85
- self.select_all_button.setToolTip("Select all atoms in the molecule")
86
- self.select_all_button.clicked.connect(self.select_all_atoms)
87
- button_layout.addWidget(self.select_all_button)
88
-
89
- button_layout.addStretch()
90
-
91
- self.apply_button = QPushButton("Apply Translation")
92
- self.apply_button.clicked.connect(self.apply_translation)
93
- self.apply_button.setEnabled(False)
94
- button_layout.addWidget(self.apply_button)
95
-
96
- close_button = QPushButton("Close")
97
- close_button.clicked.connect(self.reject)
98
- button_layout.addWidget(close_button)
99
-
100
- layout.addLayout(button_layout)
101
-
102
- # Connect to main window's picker
103
- self.picker_connection = None
104
- self.enable_picking()
105
-
106
- def on_atom_picked(self, atom_idx):
107
- """Handle the event when an atom is picked in the 3D view."""
108
- if atom_idx in self.selected_atoms:
109
- self.selected_atoms.remove(atom_idx)
110
- else:
111
- self.selected_atoms.add(atom_idx)
112
-
113
- # Display labels on the atoms
114
- self.show_atom_labels()
115
- self.update_display()
116
-
117
- def clear_selection(self):
118
- """Clear the current atom selection."""
119
- self.selected_atoms.clear()
120
- self.clear_atom_labels()
121
- self.update_display()
122
-
123
- def select_all_atoms(self):
124
- """Select all atoms in the current molecule."""
125
- try:
126
- if hasattr(self, "mol") and self.mol is not None:
127
- n = self.mol.GetNumAtoms()
128
- self.selected_atoms = set(range(n))
129
- else:
130
- self.selected_atoms = (
131
- set(self.main_window.state_manager.data.atoms.keys())
132
- if hasattr(self.main_window.state_manager, "data")
133
- else set()
134
- )
135
-
136
- self.show_atom_labels()
137
- self.update_display()
138
- except (AttributeError, RuntimeError, TypeError, KeyError) as e:
139
- QMessageBox.warning(self, "Warning", f"Failed to select all atoms: {e}")
140
-
141
- def update_display(self):
142
- """Update the UI display with current selection info."""
143
- count = len(self.selected_atoms)
144
- if count == 0:
145
- self.selection_label.setText("Click atoms to select (minimum 1 required)")
146
- self.apply_button.setEnabled(False)
147
- else:
148
- self.selection_label.setText(f"Selected {count} atoms")
149
- self.apply_button.setEnabled(True)
150
-
151
- def show_atom_labels(self):
152
- """Show numeric labels for the selected atoms."""
153
- if self.selected_atoms:
154
- sorted_atoms = sorted(self.selected_atoms)
155
- pairs = [(idx, str(i + 1)) for i, idx in enumerate(sorted_atoms)]
156
- self.show_atom_labels_for(pairs)
157
- else:
158
- self.clear_atom_labels()
159
-
160
- def apply_translation(self):
161
- """Apply the translation to selected atoms."""
162
- if not self.selected_atoms:
163
- QMessageBox.warning(self, "Warning", "Please select at least one atom.")
164
- return
165
-
166
- try:
167
- dx = float(self.dx_input.text())
168
- dy = float(self.dy_input.text())
169
- dz = float(self.dz_input.text())
170
- except ValueError:
171
- QMessageBox.warning(
172
- self, "Warning", "Please enter valid numbers for dx, dy, dz."
173
- )
174
- return
175
-
176
- if dx == 0 and dy == 0 and dz == 0:
177
- return
178
-
179
- translation_vec = np.array([dx, dy, dz])
180
-
181
- # Update positions
182
- positions = self.mol.GetConformer().GetPositions()
183
- for atom_idx in self.selected_atoms:
184
- positions[atom_idx] += translation_vec
185
-
186
- # Write updated positions back using inherited helper
187
- self._update_molecule_geometry(positions)
188
-
189
- # Push Undo state AFTER modification
190
- self._push_undo()
191
-
192
- # Update labels
193
- self.show_atom_labels()
File without changes