MoleditPy-linux 2.6.1__tar.gz → 2.6.2__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_linux-2.6.1 → moleditpy_linux-2.6.2}/PKG-INFO +1 -1
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/pyproject.toml +1 -1
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/MoleditPy_linux.egg-info/PKG-INFO +1 -1
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/__init__.py +0 -2
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/__main__.py +1 -3
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/main.py +3 -3
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/__init__.py +3 -2
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/about_dialog.py +15 -7
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/align_plane_dialog.py +47 -21
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/alignment_dialog.py +35 -19
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/analysis_window.py +20 -10
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/angle_dialog.py +64 -26
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/atom_item.py +122 -71
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/bond_item.py +150 -73
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/bond_length_dialog.py +39 -18
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/calculation_worker.py +287 -136
- moleditpy_linux-2.6.2/src/moleditpy_linux/modules/color_settings_dialog.py +524 -0
- moleditpy_linux-2.6.2/src/moleditpy_linux/modules/constants.py +170 -0
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/constrained_optimization_dialog.py +140 -49
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/custom_interactor_style.py +223 -105
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/custom_qt_interactor.py +5 -5
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/dialog3_d_picking_mixin.py +53 -37
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/dihedral_dialog.py +92 -33
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window.py +45 -23
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_app_state.py +260 -145
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_compute.py +503 -331
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_dialog_manager.py +147 -69
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_edit_3d.py +67 -49
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_edit_actions.py +306 -199
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_export.py +275 -157
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_main_init.py +782 -528
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_molecular_parsers.py +336 -186
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_project_io.py +119 -86
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_string_importers.py +52 -29
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_ui_manager.py +125 -99
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_view_3d.py +596 -293
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_view_loaders.py +75 -48
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/mirror_dialog.py +10 -4
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/mol_geometry.py +10 -8
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/molecular_data.py +74 -42
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/molecule_scene.py +666 -386
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/move_group_dialog.py +157 -86
- moleditpy_linux-2.6.2/src/moleditpy_linux/modules/periodic_table_dialog.py +196 -0
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/planarize_dialog.py +47 -16
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/plugin_interface.py +51 -17
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/plugin_manager.py +192 -146
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/plugin_manager_window.py +95 -57
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/settings_dialog.py +867 -412
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/sip_isdeleted_safe.py +1 -0
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/template_preview_item.py +32 -10
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/template_preview_view.py +20 -5
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/translation_dialog.py +55 -26
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/user_template_dialog.py +197 -124
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/zoomable_view.py +17 -14
- moleditpy_linux-2.6.1/src/moleditpy_linux/modules/color_settings_dialog.py +0 -325
- moleditpy_linux-2.6.1/src/moleditpy_linux/modules/constants.py +0 -88
- moleditpy_linux-2.6.1/src/moleditpy_linux/modules/periodic_table_dialog.py +0 -86
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/LICENSE +0 -0
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/README.md +0 -0
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/setup.cfg +0 -0
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/MoleditPy_linux.egg-info/SOURCES.txt +0 -0
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/MoleditPy_linux.egg-info/dependency_links.txt +0 -0
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/MoleditPy_linux.egg-info/entry_points.txt +0 -0
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/MoleditPy_linux.egg-info/requires.txt +0 -0
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/MoleditPy_linux.egg-info/top_level.txt +0 -0
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/assets/file_icon.ico +0 -0
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/assets/icon.icns +0 -0
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/assets/icon.ico +0 -0
- {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/assets/icon.png +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy-linux
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.2
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy-linux
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.2
|
|
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
|
|
@@ -10,7 +10,6 @@ Repo: https://github.com/HiroYokoyama/python_molecular_editor
|
|
|
10
10
|
DOI: 10.5281/zenodo.17268532
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
|
|
14
13
|
print("-----------------------------------------------------")
|
|
15
14
|
print("MoleditPy — A Python-based molecular editing software")
|
|
16
15
|
print("-----------------------------------------------------\n")
|
|
@@ -21,6 +20,5 @@ except Exception:
|
|
|
21
20
|
from main import main
|
|
22
21
|
|
|
23
22
|
# --- Application Execution ---
|
|
24
|
-
if __name__ ==
|
|
23
|
+
if __name__ == "__main__":
|
|
25
24
|
main()
|
|
26
|
-
|
|
@@ -10,7 +10,6 @@ Repo: https://github.com/HiroYokoyama/python_molecular_editor
|
|
|
10
10
|
DOI: 10.5281/zenodo.17268532
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
|
|
14
13
|
import ctypes
|
|
15
14
|
import sys
|
|
16
15
|
|
|
@@ -21,10 +20,11 @@ try:
|
|
|
21
20
|
except Exception:
|
|
22
21
|
from modules.main_window import MainWindow
|
|
23
22
|
|
|
23
|
+
|
|
24
24
|
def main():
|
|
25
25
|
# --- Windows タスクバーアイコンのための追加処理 ---
|
|
26
|
-
if sys.platform ==
|
|
27
|
-
myappid =
|
|
26
|
+
if sys.platform == "win32":
|
|
27
|
+
myappid = "hyoko.moleditpy.1.0" # アプリケーション固有のID(任意)
|
|
28
28
|
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
|
|
29
29
|
|
|
30
30
|
app = QApplication(sys.argv)
|
|
@@ -15,11 +15,13 @@ OBABEL_AVAILABLE = False
|
|
|
15
15
|
|
|
16
16
|
try:
|
|
17
17
|
from PyQt6 import sip as _sip # type: ignore
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
_sip_isdeleted = getattr(_sip, "isdeleted", None)
|
|
19
20
|
except Exception:
|
|
20
21
|
_sip = None
|
|
21
22
|
_sip_isdeleted = None
|
|
22
23
|
|
|
24
|
+
|
|
23
25
|
def sip_isdeleted_safe(obj) -> bool:
|
|
24
26
|
"""Return True if sip reports the given wrapper object as deleted.
|
|
25
27
|
|
|
@@ -33,4 +35,3 @@ def sip_isdeleted_safe(obj) -> bool:
|
|
|
33
35
|
return bool(_sip_isdeleted(obj))
|
|
34
36
|
except Exception:
|
|
35
37
|
return False
|
|
36
|
-
|
|
@@ -21,7 +21,8 @@ try:
|
|
|
21
21
|
except Exception:
|
|
22
22
|
from modules.constants import VERSION
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
|
|
25
|
+
class AboutDialog(QDialog): # pragma: no cover
|
|
25
26
|
def __init__(self, main_window, parent=None):
|
|
26
27
|
super().__init__(parent)
|
|
27
28
|
self.main_window = main_window
|
|
@@ -37,11 +38,16 @@ class AboutDialog(QDialog): # pragma: no cover
|
|
|
37
38
|
self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
38
39
|
|
|
39
40
|
# Load the original icon image
|
|
40
|
-
icon_path = os.path.join(os.path.dirname(__file__),
|
|
41
|
+
icon_path = os.path.join(os.path.dirname(__file__), "..", "assets", "icon.png")
|
|
41
42
|
if os.path.exists(icon_path):
|
|
42
43
|
original_pixmap = QPixmap(icon_path)
|
|
43
44
|
# Scale to 2x size (160x160)
|
|
44
|
-
pixmap = original_pixmap.scaled(
|
|
45
|
+
pixmap = original_pixmap.scaled(
|
|
46
|
+
160,
|
|
47
|
+
160,
|
|
48
|
+
Qt.AspectRatioMode.KeepAspectRatio,
|
|
49
|
+
Qt.TransformationMode.SmoothTransformation,
|
|
50
|
+
)
|
|
45
51
|
else:
|
|
46
52
|
# Fallback: create a simple placeholder if icon.png not found
|
|
47
53
|
pixmap = QPixmap(160, 160)
|
|
@@ -54,8 +60,9 @@ class AboutDialog(QDialog): # pragma: no cover
|
|
|
54
60
|
self.image_label.setPixmap(pixmap)
|
|
55
61
|
try:
|
|
56
62
|
self.image_label.setCursor(QCursor(Qt.CursorShape.ArrowCursor))
|
|
57
|
-
except Exception:
|
|
58
|
-
|
|
63
|
+
except Exception: # pragma: no cover
|
|
64
|
+
import traceback
|
|
65
|
+
traceback.print_exc()
|
|
59
66
|
|
|
60
67
|
self.image_label.mousePressEvent = self.image_mouse_press_event
|
|
61
68
|
|
|
@@ -100,5 +107,6 @@ class AboutDialog(QDialog): # pragma: no cover
|
|
|
100
107
|
except Exception:
|
|
101
108
|
try:
|
|
102
109
|
event.ignore()
|
|
103
|
-
except Exception:
|
|
104
|
-
|
|
110
|
+
except Exception: # pragma: no cover
|
|
111
|
+
import traceback
|
|
112
|
+
traceback.print_exc()
|
{moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/align_plane_dialog.py
RENAMED
|
@@ -26,7 +26,8 @@ try:
|
|
|
26
26
|
except Exception:
|
|
27
27
|
from modules.dialog3_d_picking_mixin import Dialog3DPickingMixin
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
class AlignPlaneDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
30
31
|
def __init__(self, mol, main_window, plane, preselected_atoms=None, parent=None):
|
|
31
32
|
QDialog.__init__(self, parent)
|
|
32
33
|
Dialog3DPickingMixin.__init__(self)
|
|
@@ -47,13 +48,15 @@ class AlignPlaneDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
47
48
|
self.update_display()
|
|
48
49
|
|
|
49
50
|
def init_ui(self):
|
|
50
|
-
plane_names = {
|
|
51
|
+
plane_names = {"xy": "XY", "xz": "XZ", "yz": "YZ"}
|
|
51
52
|
self.setWindowTitle(f"Align to {plane_names[self.plane]} Plane")
|
|
52
53
|
self.setModal(False)
|
|
53
54
|
layout = QVBoxLayout(self)
|
|
54
55
|
|
|
55
56
|
# Instructions
|
|
56
|
-
instruction_label = QLabel(
|
|
57
|
+
instruction_label = QLabel(
|
|
58
|
+
f"Click atoms in the 3D view to select them for align to the {plane_names[self.plane]} plane. At least 3 atoms are required."
|
|
59
|
+
)
|
|
57
60
|
instruction_label.setWordWrap(True)
|
|
58
61
|
layout.addWidget(instruction_label)
|
|
59
62
|
|
|
@@ -69,7 +72,9 @@ class AlignPlaneDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
69
72
|
|
|
70
73
|
# Select all atoms button
|
|
71
74
|
self.select_all_button = QPushButton("Select All Atoms")
|
|
72
|
-
self.select_all_button.setToolTip(
|
|
75
|
+
self.select_all_button.setToolTip(
|
|
76
|
+
"Select all atoms in the molecule for alignment"
|
|
77
|
+
)
|
|
73
78
|
self.select_all_button.clicked.connect(self.select_all_atoms)
|
|
74
79
|
button_layout.addWidget(self.select_all_button)
|
|
75
80
|
|
|
@@ -97,7 +102,7 @@ class AlignPlaneDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
97
102
|
|
|
98
103
|
def disable_picking(self):
|
|
99
104
|
"""3Dビューでの原子選択を無効にする"""
|
|
100
|
-
if hasattr(self,
|
|
105
|
+
if hasattr(self, "picking_enabled") and self.picking_enabled:
|
|
101
106
|
self.main_window.plotter.interactor.removeEventFilter(self)
|
|
102
107
|
self.picking_enabled = False
|
|
103
108
|
|
|
@@ -131,17 +136,25 @@ class AlignPlaneDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
131
136
|
"""Select all atoms in the current molecule and update labels/UI."""
|
|
132
137
|
try:
|
|
133
138
|
# Prefer RDKit molecule if available
|
|
134
|
-
if hasattr(self,
|
|
139
|
+
if hasattr(self, "mol") and self.mol is not None:
|
|
135
140
|
try:
|
|
136
141
|
n = self.mol.GetNumAtoms()
|
|
137
142
|
# create a set of indices [0..n-1]
|
|
138
143
|
self.selected_atoms = set(range(n))
|
|
139
144
|
except Exception:
|
|
140
145
|
# fallback to main_window data map
|
|
141
|
-
self.selected_atoms =
|
|
146
|
+
self.selected_atoms = (
|
|
147
|
+
set(self.main_window.data.atoms.keys())
|
|
148
|
+
if hasattr(self.main_window, "data")
|
|
149
|
+
else set()
|
|
150
|
+
)
|
|
142
151
|
else:
|
|
143
152
|
# fallback to main_window data map
|
|
144
|
-
self.selected_atoms =
|
|
153
|
+
self.selected_atoms = (
|
|
154
|
+
set(self.main_window.data.atoms.keys())
|
|
155
|
+
if hasattr(self.main_window, "data")
|
|
156
|
+
else set()
|
|
157
|
+
)
|
|
145
158
|
|
|
146
159
|
# Update labels and display
|
|
147
160
|
self.show_atom_labels()
|
|
@@ -154,37 +167,44 @@ class AlignPlaneDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
154
167
|
"""表示を更新"""
|
|
155
168
|
count = len(self.selected_atoms)
|
|
156
169
|
if count == 0:
|
|
157
|
-
self.selection_label.setText(
|
|
170
|
+
self.selection_label.setText(
|
|
171
|
+
"Click atoms to select for align (minimum 3 required)"
|
|
172
|
+
)
|
|
158
173
|
self.apply_button.setEnabled(False)
|
|
159
174
|
else:
|
|
160
175
|
atom_list = sorted(self.selected_atoms)
|
|
161
176
|
atom_display = []
|
|
162
177
|
for i, atom_idx in enumerate(atom_list):
|
|
163
178
|
symbol = self.mol.GetAtomWithIdx(atom_idx).GetSymbol()
|
|
164
|
-
atom_display.append(f"#{i+1}: {symbol}({atom_idx})")
|
|
179
|
+
atom_display.append(f"#{i + 1}: {symbol}({atom_idx})")
|
|
165
180
|
|
|
166
|
-
self.selection_label.setText(
|
|
181
|
+
self.selection_label.setText(
|
|
182
|
+
f"Selected {count} atoms: {', '.join(atom_display)}"
|
|
183
|
+
)
|
|
167
184
|
self.apply_button.setEnabled(count >= 3)
|
|
168
185
|
|
|
169
186
|
def show_atom_labels(self):
|
|
170
187
|
"""選択された原子にラベルを表示"""
|
|
171
188
|
if self.selected_atoms:
|
|
172
189
|
sorted_atoms = sorted(self.selected_atoms)
|
|
173
|
-
pairs = [(idx, f"#{i+1}") for i, idx in enumerate(sorted_atoms)]
|
|
174
|
-
self.show_atom_labels_for(pairs, color=
|
|
190
|
+
pairs = [(idx, f"#{i + 1}") for i, idx in enumerate(sorted_atoms)]
|
|
191
|
+
self.show_atom_labels_for(pairs, color="blue")
|
|
175
192
|
else:
|
|
176
193
|
self.clear_atom_labels()
|
|
177
194
|
|
|
178
195
|
def apply_PlaneAlign(self):
|
|
179
196
|
"""alignを適用(回転ベース)"""
|
|
180
197
|
if len(self.selected_atoms) < 3:
|
|
181
|
-
QMessageBox.warning(
|
|
198
|
+
QMessageBox.warning(
|
|
199
|
+
self, "Warning", "Please select at least 3 atoms for align."
|
|
200
|
+
)
|
|
182
201
|
return
|
|
183
202
|
try:
|
|
184
|
-
|
|
185
203
|
# 選択された原子の位置を取得
|
|
186
204
|
selected_indices = list(self.selected_atoms)
|
|
187
|
-
selected_positions = self.main_window.atom_positions_3d[
|
|
205
|
+
selected_positions = self.main_window.atom_positions_3d[
|
|
206
|
+
selected_indices
|
|
207
|
+
].copy()
|
|
188
208
|
|
|
189
209
|
# 重心を計算
|
|
190
210
|
centroid = np.mean(selected_positions, axis=0)
|
|
@@ -201,11 +221,11 @@ class AlignPlaneDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
201
221
|
normal_vector = eigenvectors[:, 0] # 最小固有値に対応する固有ベクトル
|
|
202
222
|
|
|
203
223
|
# 目標の平面の法線ベクトルを定義
|
|
204
|
-
if self.plane ==
|
|
224
|
+
if self.plane == "xy":
|
|
205
225
|
target_normal = np.array([0, 0, 1]) # Z軸方向
|
|
206
|
-
elif self.plane ==
|
|
226
|
+
elif self.plane == "xz":
|
|
207
227
|
target_normal = np.array([0, 1, 0]) # Y軸方向
|
|
208
|
-
elif self.plane ==
|
|
228
|
+
elif self.plane == "yz":
|
|
209
229
|
target_normal = np.array([1, 0, 0]) # X軸方向
|
|
210
230
|
else:
|
|
211
231
|
target_normal = np.array([0, 0, 1]) # Default to Z-axis (XY plane)
|
|
@@ -228,7 +248,11 @@ class AlignPlaneDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
228
248
|
def rodrigues_rotation(v, axis, angle):
|
|
229
249
|
cos_a = np.cos(angle)
|
|
230
250
|
sin_a = np.sin(angle)
|
|
231
|
-
return
|
|
251
|
+
return (
|
|
252
|
+
v * cos_a
|
|
253
|
+
+ np.cross(axis, v) * sin_a
|
|
254
|
+
+ axis * np.dot(axis, v) * (1 - cos_a)
|
|
255
|
+
)
|
|
232
256
|
|
|
233
257
|
# 分子全体を回転させる
|
|
234
258
|
conf = self.mol.GetConformer()
|
|
@@ -236,7 +260,9 @@ class AlignPlaneDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
236
260
|
current_pos = np.array(conf.GetAtomPosition(i))
|
|
237
261
|
# 重心基準で回転
|
|
238
262
|
centered_pos = current_pos - centroid
|
|
239
|
-
rotated_pos = rodrigues_rotation(
|
|
263
|
+
rotated_pos = rodrigues_rotation(
|
|
264
|
+
centered_pos, rotation_axis, rotation_angle
|
|
265
|
+
)
|
|
240
266
|
new_pos = rotated_pos + centroid
|
|
241
267
|
conf.SetAtomPosition(i, new_pos.tolist())
|
|
242
268
|
self.main_window.atom_positions_3d[i] = new_pos
|
{moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/alignment_dialog.py
RENAMED
|
@@ -25,7 +25,8 @@ try:
|
|
|
25
25
|
except Exception:
|
|
26
26
|
from modules.dialog3_d_picking_mixin import Dialog3DPickingMixin
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
|
|
29
|
+
class AlignmentDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
29
30
|
def __init__(self, mol, main_window, axis, preselected_atoms=None, parent=None):
|
|
30
31
|
QDialog.__init__(self, parent)
|
|
31
32
|
Dialog3DPickingMixin.__init__(self)
|
|
@@ -47,13 +48,15 @@ class AlignmentDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
47
48
|
self.update_display()
|
|
48
49
|
|
|
49
50
|
def init_ui(self):
|
|
50
|
-
axis_names = {
|
|
51
|
+
axis_names = {"x": "X-axis", "y": "Y-axis", "z": "Z-axis"}
|
|
51
52
|
self.setWindowTitle(f"Align to {axis_names[self.axis]}")
|
|
52
53
|
self.setModal(False)
|
|
53
54
|
layout = QVBoxLayout(self)
|
|
54
55
|
|
|
55
56
|
# Instructions
|
|
56
|
-
instruction_label = QLabel(
|
|
57
|
+
instruction_label = QLabel(
|
|
58
|
+
f"Click atoms in the 3D view to select them for alignment to the {axis_names[self.axis]}. Exactly 2 atoms are required. The first atom will be moved to the origin, and the second atom will be positioned on the {axis_names[self.axis]}."
|
|
59
|
+
)
|
|
57
60
|
instruction_label.setWordWrap(True)
|
|
58
61
|
layout.addWidget(instruction_label)
|
|
59
62
|
|
|
@@ -116,18 +119,24 @@ class AlignmentDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
116
119
|
def update_display(self):
|
|
117
120
|
"""選択状態の表示を更新"""
|
|
118
121
|
if len(self.selected_atoms) == 0:
|
|
119
|
-
self.selection_label.setText(
|
|
122
|
+
self.selection_label.setText(
|
|
123
|
+
"Click atoms to select for alignment (exactly 2 required)"
|
|
124
|
+
)
|
|
120
125
|
self.apply_button.setEnabled(False)
|
|
121
126
|
elif len(self.selected_atoms) == 1:
|
|
122
127
|
selected_list = list(self.selected_atoms)
|
|
123
128
|
atom = self.mol.GetAtomWithIdx(selected_list[0])
|
|
124
|
-
self.selection_label.setText(
|
|
129
|
+
self.selection_label.setText(
|
|
130
|
+
f"Selected 1 atom: {atom.GetSymbol()}{selected_list[0] + 1}"
|
|
131
|
+
)
|
|
125
132
|
self.apply_button.setEnabled(False)
|
|
126
133
|
elif len(self.selected_atoms) == 2:
|
|
127
134
|
selected_list = sorted(list(self.selected_atoms))
|
|
128
135
|
atom1 = self.mol.GetAtomWithIdx(selected_list[0])
|
|
129
136
|
atom2 = self.mol.GetAtomWithIdx(selected_list[1])
|
|
130
|
-
self.selection_label.setText(
|
|
137
|
+
self.selection_label.setText(
|
|
138
|
+
f"Selected 2 atoms: {atom1.GetSymbol()}{selected_list[0] + 1}, {atom2.GetSymbol()}{selected_list[1] + 1}"
|
|
139
|
+
)
|
|
131
140
|
self.apply_button.setEnabled(True)
|
|
132
141
|
|
|
133
142
|
def clear_selection(self):
|
|
@@ -147,10 +156,11 @@ class AlignmentDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
147
156
|
def apply_alignment(self):
|
|
148
157
|
"""アライメントを適用"""
|
|
149
158
|
if len(self.selected_atoms) != 2:
|
|
150
|
-
QMessageBox.warning(
|
|
159
|
+
QMessageBox.warning(
|
|
160
|
+
self, "Warning", "Please select exactly 2 atoms for alignment."
|
|
161
|
+
)
|
|
151
162
|
return
|
|
152
163
|
try:
|
|
153
|
-
|
|
154
164
|
selected_list = sorted(list(self.selected_atoms))
|
|
155
165
|
atom1_idx, atom2_idx = selected_list[0], selected_list[1]
|
|
156
166
|
|
|
@@ -172,9 +182,9 @@ class AlignmentDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
172
182
|
|
|
173
183
|
# atom2を選択した軸上に配置するための回転を計算
|
|
174
184
|
axis_vectors = {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
185
|
+
"x": np.array([1.0, 0.0, 0.0]),
|
|
186
|
+
"y": np.array([0.0, 1.0, 0.0]),
|
|
187
|
+
"z": np.array([0.0, 0.0, 1.0]),
|
|
178
188
|
}
|
|
179
189
|
target_axis = axis_vectors[self.axis]
|
|
180
190
|
|
|
@@ -199,20 +209,24 @@ class AlignmentDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
199
209
|
def rodrigues_rotation(v, k, theta):
|
|
200
210
|
cos_theta = np.cos(theta)
|
|
201
211
|
sin_theta = np.sin(theta)
|
|
202
|
-
return (
|
|
203
|
-
|
|
204
|
-
|
|
212
|
+
return (
|
|
213
|
+
v * cos_theta
|
|
214
|
+
+ np.cross(k, v) * sin_theta
|
|
215
|
+
+ k * np.dot(k, v) * (1 - cos_theta)
|
|
216
|
+
)
|
|
205
217
|
|
|
206
218
|
# 全ての原子に回転を適用
|
|
207
219
|
for i in range(self.mol.GetNumAtoms()):
|
|
208
220
|
current_pos = np.array(conf.GetAtomPosition(i))
|
|
209
|
-
rotated_pos = rodrigues_rotation(
|
|
221
|
+
rotated_pos = rodrigues_rotation(
|
|
222
|
+
current_pos, rotation_axis, rotation_angle
|
|
223
|
+
)
|
|
210
224
|
conf.SetAtomPosition(i, rotated_pos.tolist())
|
|
211
225
|
|
|
212
226
|
# 3D座標を更新
|
|
213
|
-
self.main_window.atom_positions_3d = np.array(
|
|
214
|
-
list(conf.GetAtomPosition(i)) for i in range(self.mol.GetNumAtoms())
|
|
215
|
-
|
|
227
|
+
self.main_window.atom_positions_3d = np.array(
|
|
228
|
+
[list(conf.GetAtomPosition(i)) for i in range(self.mol.GetNumAtoms())]
|
|
229
|
+
)
|
|
216
230
|
|
|
217
231
|
# 3Dビューを更新
|
|
218
232
|
self.main_window.draw_molecule_3d(self.mol)
|
|
@@ -223,7 +237,9 @@ class AlignmentDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
223
237
|
# Undo状態を保存
|
|
224
238
|
self.main_window.push_undo_state()
|
|
225
239
|
|
|
226
|
-
QMessageBox.information(
|
|
240
|
+
QMessageBox.information(
|
|
241
|
+
self, "Success", f"Alignment to {self.axis.upper()}-axis completed."
|
|
242
|
+
)
|
|
227
243
|
|
|
228
244
|
except Exception as e:
|
|
229
245
|
QMessageBox.critical(self, "Error", f"Failed to apply alignment: {str(e)}")
|
{moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/analysis_window.py
RENAMED
|
@@ -47,11 +47,13 @@ class AnalysisWindow(QDialog):
|
|
|
47
47
|
# (結合推定の影響を受けない)
|
|
48
48
|
|
|
49
49
|
# XYZファイルから読み込んだ元の原子情報を取得
|
|
50
|
-
if hasattr(self.mol,
|
|
50
|
+
if hasattr(self.mol, "_xyz_atom_data"):
|
|
51
51
|
xyz_atoms = self.mol._xyz_atom_data
|
|
52
52
|
else:
|
|
53
53
|
# フォールバック: RDKitオブジェクトから取得
|
|
54
|
-
xyz_atoms = [
|
|
54
|
+
xyz_atoms = [
|
|
55
|
+
(atom.GetSymbol(), 0, 0, 0) for atom in self.mol.GetAtoms()
|
|
56
|
+
]
|
|
55
57
|
|
|
56
58
|
# 原子数と元素種を集計
|
|
57
59
|
atom_counts = {}
|
|
@@ -60,11 +62,11 @@ class AnalysisWindow(QDialog):
|
|
|
60
62
|
|
|
61
63
|
for symbol, x, y, z in xyz_atoms:
|
|
62
64
|
atom_counts[symbol] = atom_counts.get(symbol, 0) + 1
|
|
63
|
-
if symbol !=
|
|
65
|
+
if symbol != "H": # 水素以外
|
|
64
66
|
num_heavy_atoms += 1
|
|
65
67
|
|
|
66
68
|
# 化学式を手動で構築(元素順序を考慮)
|
|
67
|
-
element_order = [
|
|
69
|
+
element_order = ["C", "H", "N", "O", "P", "S", "F", "Cl", "Br", "I"]
|
|
68
70
|
formula_parts = []
|
|
69
71
|
|
|
70
72
|
# 定義された順序で元素を追加
|
|
@@ -86,7 +88,7 @@ class AnalysisWindow(QDialog):
|
|
|
86
88
|
else:
|
|
87
89
|
formula_parts.append(f"{element}{count}")
|
|
88
90
|
|
|
89
|
-
mol_formula =
|
|
91
|
+
mol_formula = "".join(formula_parts)
|
|
90
92
|
|
|
91
93
|
# 分子量と精密質量をRDKitから取得
|
|
92
94
|
|
|
@@ -105,7 +107,9 @@ class AnalysisWindow(QDialog):
|
|
|
105
107
|
exact_mw += exact_mass * count
|
|
106
108
|
except (ValueError, RuntimeError):
|
|
107
109
|
# 認識されない元素の場合はスキップ
|
|
108
|
-
print(
|
|
110
|
+
print(
|
|
111
|
+
f"Warning: Unknown element {symbol}, skipping in mass calculation"
|
|
112
|
+
)
|
|
109
113
|
continue
|
|
110
114
|
|
|
111
115
|
# 表示するプロパティを辞書にまとめる(XYZ元データから計算)
|
|
@@ -118,7 +122,9 @@ class AnalysisWindow(QDialog):
|
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
# 注意メッセージを追加
|
|
121
|
-
note_label = QLabel(
|
|
125
|
+
note_label = QLabel(
|
|
126
|
+
"<i>Note: SMILES and structure-dependent properties are not available for XYZ-derived structures due to potential bond estimation inaccuracies.</i>"
|
|
127
|
+
)
|
|
122
128
|
note_label.setWordWrap(True)
|
|
123
129
|
main_layout.addWidget(note_label)
|
|
124
130
|
|
|
@@ -192,7 +198,9 @@ class AnalysisWindow(QDialog):
|
|
|
192
198
|
value.setReadOnly(True)
|
|
193
199
|
|
|
194
200
|
copy_btn = QPushButton("Copy")
|
|
195
|
-
copy_btn.clicked.connect(
|
|
201
|
+
copy_btn.clicked.connect(
|
|
202
|
+
lambda _, v=value: self.copy_to_clipboard(v.text())
|
|
203
|
+
)
|
|
196
204
|
|
|
197
205
|
grid_layout.addWidget(label, row, 0)
|
|
198
206
|
grid_layout.addWidget(value, row, 1)
|
|
@@ -211,5 +219,7 @@ class AnalysisWindow(QDialog):
|
|
|
211
219
|
def copy_to_clipboard(self, text):
|
|
212
220
|
clipboard = QApplication.clipboard()
|
|
213
221
|
clipboard.setText(text)
|
|
214
|
-
if self.parent() and hasattr(self.parent(),
|
|
215
|
-
self.parent().statusBar().showMessage(
|
|
222
|
+
if self.parent() and hasattr(self.parent(), "statusBar"):
|
|
223
|
+
self.parent().statusBar().showMessage(
|
|
224
|
+
f"Copied '{text}' to clipboard.", 2000
|
|
225
|
+
)
|