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.
Files changed (69) hide show
  1. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/PKG-INFO +1 -1
  2. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/pyproject.toml +1 -1
  3. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/MoleditPy_linux.egg-info/PKG-INFO +1 -1
  4. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/__init__.py +0 -2
  5. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/__main__.py +1 -3
  6. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/main.py +3 -3
  7. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/__init__.py +3 -2
  8. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/about_dialog.py +15 -7
  9. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/align_plane_dialog.py +47 -21
  10. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/alignment_dialog.py +35 -19
  11. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/analysis_window.py +20 -10
  12. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/angle_dialog.py +64 -26
  13. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/atom_item.py +122 -71
  14. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/bond_item.py +150 -73
  15. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/bond_length_dialog.py +39 -18
  16. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/calculation_worker.py +287 -136
  17. moleditpy_linux-2.6.2/src/moleditpy_linux/modules/color_settings_dialog.py +524 -0
  18. moleditpy_linux-2.6.2/src/moleditpy_linux/modules/constants.py +170 -0
  19. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/constrained_optimization_dialog.py +140 -49
  20. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/custom_interactor_style.py +223 -105
  21. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/custom_qt_interactor.py +5 -5
  22. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/dialog3_d_picking_mixin.py +53 -37
  23. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/dihedral_dialog.py +92 -33
  24. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window.py +45 -23
  25. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_app_state.py +260 -145
  26. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_compute.py +503 -331
  27. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_dialog_manager.py +147 -69
  28. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_edit_3d.py +67 -49
  29. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_edit_actions.py +306 -199
  30. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_export.py +275 -157
  31. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_main_init.py +782 -528
  32. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_molecular_parsers.py +336 -186
  33. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_project_io.py +119 -86
  34. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_string_importers.py +52 -29
  35. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_ui_manager.py +125 -99
  36. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_view_3d.py +596 -293
  37. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/main_window_view_loaders.py +75 -48
  38. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/mirror_dialog.py +10 -4
  39. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/mol_geometry.py +10 -8
  40. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/molecular_data.py +74 -42
  41. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/molecule_scene.py +666 -386
  42. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/move_group_dialog.py +157 -86
  43. moleditpy_linux-2.6.2/src/moleditpy_linux/modules/periodic_table_dialog.py +196 -0
  44. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/planarize_dialog.py +47 -16
  45. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/plugin_interface.py +51 -17
  46. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/plugin_manager.py +192 -146
  47. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/plugin_manager_window.py +95 -57
  48. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/settings_dialog.py +867 -412
  49. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/sip_isdeleted_safe.py +1 -0
  50. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/template_preview_item.py +32 -10
  51. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/template_preview_view.py +20 -5
  52. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/translation_dialog.py +55 -26
  53. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/user_template_dialog.py +197 -124
  54. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/modules/zoomable_view.py +17 -14
  55. moleditpy_linux-2.6.1/src/moleditpy_linux/modules/color_settings_dialog.py +0 -325
  56. moleditpy_linux-2.6.1/src/moleditpy_linux/modules/constants.py +0 -88
  57. moleditpy_linux-2.6.1/src/moleditpy_linux/modules/periodic_table_dialog.py +0 -86
  58. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/LICENSE +0 -0
  59. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/README.md +0 -0
  60. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/setup.cfg +0 -0
  61. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/MoleditPy_linux.egg-info/SOURCES.txt +0 -0
  62. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/MoleditPy_linux.egg-info/dependency_links.txt +0 -0
  63. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/MoleditPy_linux.egg-info/entry_points.txt +0 -0
  64. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/MoleditPy_linux.egg-info/requires.txt +0 -0
  65. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/MoleditPy_linux.egg-info/top_level.txt +0 -0
  66. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/assets/file_icon.ico +0 -0
  67. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/assets/icon.icns +0 -0
  68. {moleditpy_linux-2.6.1 → moleditpy_linux-2.6.2}/src/moleditpy_linux/assets/icon.ico +0 -0
  69. {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.1
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
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "MoleditPy-linux"
7
7
 
8
- version = "2.6.1"
8
+ version = "2.6.2"
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: 2.6.1
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,8 +10,6 @@ Repo: https://github.com/HiroYokoyama/python_molecular_editor
10
10
  DOI: 10.5281/zenodo.17268532
11
11
  """
12
12
 
13
-
14
13
  """Top-level package for moleditpy.
15
14
 
16
15
  """
17
-
@@ -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__ == '__main__':
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 == 'win32':
27
- myappid = 'hyoko.moleditpy.1.0' # アプリケーション固有のID(任意)
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
- _sip_isdeleted = getattr(_sip, 'isdeleted', None)
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
- class AboutDialog(QDialog): # pragma: no cover
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__), '..', 'assets', 'icon.png')
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(160, 160, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)
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
- pass
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
- pass
110
+ except Exception: # pragma: no cover
111
+ import traceback
112
+ traceback.print_exc()
@@ -26,7 +26,8 @@ try:
26
26
  except Exception:
27
27
  from modules.dialog3_d_picking_mixin import Dialog3DPickingMixin
28
28
 
29
- class AlignPlaneDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
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 = {'xy': 'XY', 'xz': 'XZ', 'yz': 'YZ'}
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(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.")
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("Select all atoms in the molecule for alignment")
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, 'picking_enabled') and self.picking_enabled:
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, 'mol') and self.mol is not None:
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 = set(self.main_window.data.atoms.keys()) if hasattr(self.main_window, 'data') else set()
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 = set(self.main_window.data.atoms.keys()) if hasattr(self.main_window, 'data') else set()
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("Click atoms to select for align (minimum 3 required)")
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(f"Selected {count} atoms: {', '.join(atom_display)}")
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='blue')
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(self, "Warning", "Please select at least 3 atoms for align.")
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[selected_indices].copy()
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 == 'xy':
224
+ if self.plane == "xy":
205
225
  target_normal = np.array([0, 0, 1]) # Z軸方向
206
- elif self.plane == 'xz':
226
+ elif self.plane == "xz":
207
227
  target_normal = np.array([0, 1, 0]) # Y軸方向
208
- elif self.plane == 'yz':
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 v * cos_a + np.cross(axis, v) * sin_a + axis * np.dot(axis, v) * (1 - cos_a)
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(centered_pos, rotation_axis, rotation_angle)
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
@@ -25,7 +25,8 @@ try:
25
25
  except Exception:
26
26
  from modules.dialog3_d_picking_mixin import Dialog3DPickingMixin
27
27
 
28
- class AlignmentDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
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 = {'x': 'X-axis', 'y': 'Y-axis', 'z': 'Z-axis'}
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(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]}.")
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("Click atoms to select for alignment (exactly 2 required)")
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(f"Selected 1 atom: {atom.GetSymbol()}{selected_list[0]+1}")
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(f"Selected 2 atoms: {atom1.GetSymbol()}{selected_list[0]+1}, {atom2.GetSymbol()}{selected_list[1]+1}")
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(self, "Warning", "Please select exactly 2 atoms for alignment.")
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
- 'x': np.array([1.0, 0.0, 0.0]),
176
- 'y': np.array([0.0, 1.0, 0.0]),
177
- 'z': np.array([0.0, 0.0, 1.0])
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 (v * cos_theta +
203
- np.cross(k, v) * sin_theta +
204
- k * np.dot(k, v) * (1 - cos_theta))
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(current_pos, rotation_axis, rotation_angle)
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(self, "Success", f"Alignment to {self.axis.upper()}-axis completed.")
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)}")
@@ -47,11 +47,13 @@ class AnalysisWindow(QDialog):
47
47
  # (結合推定の影響を受けない)
48
48
 
49
49
  # XYZファイルから読み込んだ元の原子情報を取得
50
- if hasattr(self.mol, '_xyz_atom_data'):
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 = [(atom.GetSymbol(), 0, 0, 0) for atom in self.mol.GetAtoms()]
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 != 'H': # 水素以外
65
+ if symbol != "H": # 水素以外
64
66
  num_heavy_atoms += 1
65
67
 
66
68
  # 化学式を手動で構築(元素順序を考慮)
67
- element_order = ['C', 'H', 'N', 'O', 'P', 'S', 'F', 'Cl', 'Br', 'I']
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 = ''.join(formula_parts)
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(f"Warning: Unknown element {symbol}, skipping in mass calculation")
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("<i>Note: SMILES and structure-dependent properties are not available for XYZ-derived structures due to potential bond estimation inaccuracies.</i>")
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(lambda _, v=value: self.copy_to_clipboard(v.text()))
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(), 'statusBar'):
215
- self.parent().statusBar().showMessage(f"Copied '{text}' to clipboard.", 2000)
222
+ if self.parent() and hasattr(self.parent(), "statusBar"):
223
+ self.parent().statusBar().showMessage(
224
+ f"Copied '{text}' to clipboard.", 2000
225
+ )