MoleditPy 2.2.5__py3-none-any.whl → 2.2.6__py3-none-any.whl

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.
@@ -45,7 +45,6 @@ class AlignPlaneDialog(Dialog3DPickingMixin, QDialog):
45
45
  plane_names = {'xy': 'XY', 'xz': 'XZ', 'yz': 'YZ'}
46
46
  self.setWindowTitle(f"Align to {plane_names[self.plane]} Plane")
47
47
  self.setModal(False) # モードレスにしてクリックを阻害しない
48
- self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowStaysOnTopHint) # 常に前面表示
49
48
  layout = QVBoxLayout(self)
50
49
 
51
50
  # Instructions
@@ -47,7 +47,6 @@ class AlignmentDialog(Dialog3DPickingMixin, QDialog):
47
47
  axis_names = {'x': 'X-axis', 'y': 'Y-axis', 'z': 'Z-axis'}
48
48
  self.setWindowTitle(f"Align to {axis_names[self.axis]}")
49
49
  self.setModal(False) # モードレスにしてクリックを阻害しない
50
- self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowStaysOnTopHint) # 常に前面表示
51
50
  layout = QVBoxLayout(self)
52
51
 
53
52
  # Instructions
@@ -45,7 +45,7 @@ class AngleDialog(Dialog3DPickingMixin, QDialog):
45
45
  def init_ui(self):
46
46
  self.setWindowTitle("Adjust Angle")
47
47
  self.setModal(False) # モードレスにしてクリックを阻害しない
48
- self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowStaysOnTopHint) # 常に前面表示
48
+ # 常に前面表示
49
49
  layout = QVBoxLayout(self)
50
50
 
51
51
  # Instructions
@@ -38,7 +38,7 @@ class BondLengthDialog(Dialog3DPickingMixin, QDialog):
38
38
  def init_ui(self):
39
39
  self.setWindowTitle("Adjust Bond Length")
40
40
  self.setModal(False) # モードレスにしてクリックを阻害しない
41
- self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowStaysOnTopHint) # 常に前面表示
41
+ # 常に前面表示
42
42
  layout = QVBoxLayout(self)
43
43
 
44
44
  # Instructions
@@ -16,7 +16,7 @@ from PyQt6.QtGui import QFont, QColor
16
16
  from rdkit import Chem
17
17
 
18
18
  #Version
19
- VERSION = '2.2.5'
19
+ VERSION = '2.2.6'
20
20
 
21
21
  ATOM_RADIUS = 18
22
22
  BOND_OFFSET = 3.5
@@ -123,7 +123,6 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
123
123
  self.setWindowTitle("Constrained Optimization")
124
124
  self.setModal(False)
125
125
  self.resize(450, 500)
126
- self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowStaysOnTopHint)
127
126
  layout = QVBoxLayout(self)
128
127
 
129
128
  # 1. 説明
@@ -42,6 +42,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
42
42
  self._mouse_press_pos = None
43
43
 
44
44
  self.AddObserver("LeftButtonPressEvent", self.on_left_button_down)
45
+ #self.AddObserver("LeftButtonDoubleClickEvent", self.on_left_button_down)
45
46
  self.AddObserver("RightButtonPressEvent", self.on_right_button_down)
46
47
  self.AddObserver("MouseMoveEvent", self.on_mouse_move)
47
48
  self.AddObserver("LeftButtonReleaseEvent", self.on_left_button_up)
@@ -85,8 +86,12 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
85
86
  if 0 <= closest_atom_idx < mw.current_mol.GetNumAtoms():
86
87
  atom = mw.current_mol.GetAtomWithIdx(int(closest_atom_idx))
87
88
  if atom:
88
- atomic_num = atom.GetAtomicNum()
89
- vdw_radius = pt.GetRvdw(atomic_num)
89
+ try:
90
+ atomic_num = atom.GetAtomicNum()
91
+ vdw_radius = pt.GetRvdw(atomic_num)
92
+ if vdw_radius < 0.1: vdw_radius = 1.5
93
+ except Exception:
94
+ vdw_radius = 1.5
90
95
  click_threshold = vdw_radius * 1.5
91
96
 
92
97
  if distances[closest_atom_idx] < click_threshold:
@@ -148,11 +153,11 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
148
153
  move_group_dialog.update_display()
149
154
  return
150
155
  else:
151
- # 原子以外をクリック - 全選択を解除
152
- move_group_dialog.group_atoms.clear()
153
- move_group_dialog.selected_atoms.clear()
154
- move_group_dialog.clear_atom_labels()
155
- move_group_dialog.update_display()
156
+ # 原子以外をクリック
157
+ # 即座に解除せず、マウスイベントを追跡して回転かクリックかを判定する
158
+ self._mouse_press_pos = self.GetInteractor().GetEventPosition()
159
+ self._mouse_moved_during_drag = False
160
+
156
161
  # カメラ回転を許可
157
162
  super(CustomInteractorStyle, self).OnLeftButtonDown()
158
163
  return
@@ -166,8 +171,9 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
166
171
  # 測定モードが有効な場合の処理
167
172
  if mw.measurement_mode and mw.current_mol:
168
173
  click_pos = self.GetInteractor().GetEventPosition()
169
- self._mouse_press_pos = click_pos # マウスプレス位置を記録
170
- self._mouse_moved_during_drag = False # 移動フラグをリセット
174
+ # Note: We do NOT set _mouse_press_pos here initially.
175
+ # We only set it if we confirm it's a background click (see below).
176
+ self._mouse_moved_during_drag = False # Reset drag flag
171
177
 
172
178
  picker = mw.plotter.picker
173
179
 
@@ -185,17 +191,24 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
185
191
  # クリック閾値チェック
186
192
  atom = mw.current_mol.GetAtomWithIdx(int(closest_atom_idx))
187
193
  if atom:
188
- atomic_num = atom.GetAtomicNum()
189
- vdw_radius = pt.GetRvdw(atomic_num)
194
+ try:
195
+ atomic_num = atom.GetAtomicNum()
196
+ vdw_radius = pt.GetRvdw(atomic_num)
197
+ if vdw_radius < 0.1: vdw_radius = 1.5
198
+ except Exception:
199
+ vdw_radius = 1.5
190
200
  click_threshold = vdw_radius * 1.5
191
201
 
192
202
  if distances[closest_atom_idx] < click_threshold:
193
203
  mw.handle_measurement_atom_selection(int(closest_atom_idx))
194
204
  return # 原子選択処理完了、カメラ回転は無効
195
205
 
206
+
196
207
  # 測定モードで原子以外をクリックした場合は計測選択をクリア
197
- # ただし、これは通常のカメラ回転も許可する
208
+ # ただし、回転操作(ドラッグ)の場合はクリアしないため、
209
+ # ここで _mouse_press_pos を記録し、Upイベントで判定する。
198
210
  self._is_dragging_atom = False
211
+ self._mouse_press_pos = click_pos
199
212
  super().OnLeftButtonDown()
200
213
  return
201
214
 
@@ -219,8 +232,12 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
219
232
  # RDKitのMolオブジェクトから原子を安全に取得
220
233
  atom = mw.current_mol.GetAtomWithIdx(int(closest_atom_idx))
221
234
  if atom:
222
- atomic_num = atom.GetAtomicNum()
223
- vdw_radius = pt.GetRvdw(atomic_num)
235
+ try:
236
+ atomic_num = atom.GetAtomicNum()
237
+ vdw_radius = pt.GetRvdw(atomic_num)
238
+ if vdw_radius < 0.1: vdw_radius = 1.5
239
+ except Exception:
240
+ vdw_radius = 1.5
224
241
  click_threshold = vdw_radius * 1.5
225
242
 
226
243
  if distances[closest_atom_idx] < click_threshold:
@@ -265,8 +282,12 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
265
282
  if 0 <= closest_atom_idx < mw.current_mol.GetNumAtoms():
266
283
  atom = mw.current_mol.GetAtomWithIdx(int(closest_atom_idx))
267
284
  if atom:
268
- atomic_num = atom.GetAtomicNum()
269
- vdw_radius = pt.GetRvdw(atomic_num)
285
+ try:
286
+ atomic_num = atom.GetAtomicNum()
287
+ vdw_radius = pt.GetRvdw(atomic_num)
288
+ if vdw_radius < 0.1: vdw_radius = 1.5
289
+ except Exception:
290
+ vdw_radius = 1.5
270
291
  click_threshold = vdw_radius * 1.5
271
292
 
272
293
  if distances[closest_atom_idx] < click_threshold:
@@ -458,37 +479,23 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
458
479
  move_group_dialog.on_atom_picked(clicked_atom)
459
480
  except Exception as e:
460
481
  print(f"Error in toggle: {e}")
461
-
462
- # 状態をリセット(完全なクリーンアップ)
463
- move_group_dialog._is_dragging_group_vtk = False
464
- move_group_dialog._drag_start_pos = None
465
- move_group_dialog._mouse_moved = False
466
- if hasattr(move_group_dialog, '_initial_positions'):
467
- delattr(move_group_dialog, '_initial_positions')
468
- if hasattr(move_group_dialog, '_drag_atom_idx'):
469
- delattr(move_group_dialog, '_drag_atom_idx')
470
-
471
- # CustomInteractorStyleの状態もクリア
472
- self._is_dragging_atom = False
473
- self.is_dragging = False
474
- self._mouse_moved_during_drag = False
475
- self._mouse_press_pos = None
476
-
477
- try:
478
- mw.plotter.setCursor(Qt.CursorShape.ArrowCursor)
479
- except Exception:
480
- pass
481
- return
482
+
483
+ # Move Groupモードでの背景クリック判定(選択解除)
484
+ # グループドラッグでなく、マウス移動もなかった(=回転操作でない)場合
485
+ # かつ、mouse_press_pos が記録されている(背景クリックで開始した)場合
486
+ if move_group_dialog and not getattr(move_group_dialog, '_is_dragging_group_vtk', False):
487
+ if not self._mouse_moved_during_drag and self._mouse_press_pos is not None:
488
+ # 背景クリック -> 選択解除
489
+ move_group_dialog.group_atoms.clear()
490
+ move_group_dialog.selected_atoms.clear()
491
+ move_group_dialog.clear_atom_labels()
492
+ move_group_dialog.update_display()
482
493
 
483
494
  # 計測モードで、マウスが動いていない場合(つまりクリック)の処理
495
+ # _mouse_press_pos が None でない = 背景をクリックしたことを意味する(Downイベントでそう設定したため)
484
496
  if mw.measurement_mode and not self._mouse_moved_during_drag and self._mouse_press_pos is not None:
485
- click_pos = self.GetInteractor().GetEventPosition()
486
- picker = mw.plotter.picker
487
- picker.Pick(click_pos[0], click_pos[1], 0, mw.plotter.renderer)
488
-
489
- # 原子がクリックされていない場合は測定選択をクリア
490
- if picker.GetActor() is not mw.atom_actor:
491
- mw.clear_measurement_selection()
497
+ # 背景クリック -> 測定選択をクリア
498
+ mw.clear_measurement_selection()
492
499
 
493
500
  if self._is_dragging_atom:
494
501
  # カスタムドラッグの後始末
@@ -594,7 +601,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
594
601
  # カメラ回転の後始末を親クラスに任せます
595
602
  super().OnLeftButtonUp()
596
603
 
597
- # 状態をリセット(完全なクリーンアップ)
604
+ # 状態をリセット(完全なクリーンアップ) - すべてのチェックの後に実行
598
605
  self._is_dragging_atom = False
599
606
  self.is_dragging = False
600
607
  self._mouse_press_pos = None
@@ -602,12 +609,6 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
602
609
 
603
610
  # Move Group関連の状態もクリア
604
611
  try:
605
- move_group_dialog = None
606
- for widget in QApplication.topLevelWidgets():
607
- if isinstance(widget, MoveGroupDialog) and widget.isVisible():
608
- move_group_dialog = widget
609
- break
610
-
611
612
  if move_group_dialog:
612
613
  move_group_dialog._is_dragging_group_vtk = False
613
614
  move_group_dialog._drag_start_pos = None
@@ -619,12 +620,11 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
619
620
  except Exception:
620
621
  pass
621
622
 
622
- # ピックリセットは測定モードで実際に問題が発生した場合のみ行う
623
- # (通常のドラッグ回転では行わない)
624
-
625
623
  # ボタンを離した後のカーソル表示を最新の状態に更新
626
- self.on_mouse_move(obj, event)
627
-
624
+ try:
625
+ mw.plotter.setCursor(Qt.CursorShape.ArrowCursor)
626
+ except Exception:
627
+ pass
628
628
  # 2Dビューにフォーカスを戻し、ショートカットキーなどが使えるようにする
629
629
  if mw and mw.view_2d:
630
630
  mw.view_2d.setFocus()
@@ -11,6 +11,7 @@ DOI: 10.5281/zenodo.17268532
11
11
  """
12
12
 
13
13
  from pyvistaqt import QtInteractor
14
+ import time
14
15
 
15
16
 
16
17
  class CustomQtInteractor(QtInteractor):
@@ -24,6 +25,7 @@ class CustomQtInteractor(QtInteractor):
24
25
  # the VTK interactor and causing unexpected behaviour in the 3D view.
25
26
  self._last_click_time = 0.0
26
27
  self._click_count = 0
28
+ self._ignore_next_release = False
27
29
 
28
30
  def wheelEvent(self, event):
29
31
  """
@@ -40,17 +42,58 @@ class CustomQtInteractor(QtInteractor):
40
42
  """
41
43
  Qtのマウスリリースイベントをオーバーライドし、
42
44
  3Dビューでの全ての操作完了後に2Dビューへフォーカスを戻す。
45
+ また、Ghost Release(対応するPressがないRelease)をフィルタリングする。
43
46
  """
47
+ if self._ignore_next_release:
48
+ self._ignore_next_release = False
49
+ event.accept()
50
+ return
51
+
44
52
  super().mouseReleaseEvent(event) # 親クラスのイベントを先に処理
45
53
  if self.main_window and hasattr(self.main_window, 'view_2d'):
46
54
  self.main_window.view_2d.setFocus()
47
55
 
56
+ def mousePressEvent(self, event):
57
+ """
58
+ Custom mouse press handling to track accumulated clicks and filter out
59
+ triple-clicks.
60
+ """
61
+ current_time = time.time()
62
+ # Reset count if too much time has passed (0.5s is standard double-click time)
63
+ if current_time - self._last_click_time > 0.5:
64
+ self._click_count = 0
65
+
66
+ self._click_count += 1
67
+ self._last_click_time = current_time
68
+
69
+ # If this is the 3rd click (or more), swallow it to prevent
70
+ # the internal state desync that happens with rapid clicking sequences.
71
+ if self._click_count >= 3:
72
+ self._ignore_next_release = True
73
+ event.accept()
74
+ return
75
+
76
+ super().mousePressEvent(event)
77
+
48
78
  def mouseDoubleClickEvent(self, event):
49
79
  """Ignore mouse double-clicks on the 3D widget to avoid accidental actions.
50
80
 
51
81
  Swallow the double-click event so it doesn't trigger selection, editing,
52
82
  or camera jumps. We intentionally do not call the superclass handler.
83
+ Crucially, we also flag the NEXT release event to be swallowed, preventing
84
+ a "Ghost Release" (Release without Press) from reaching VTK.
53
85
  """
86
+ current_time = time.time()
87
+ self._last_click_time = current_time
88
+ # Set to 2 to ensure the next click counts as 3rd
89
+ if current_time - self._last_click_time < 0.5:
90
+ self._click_count = 2
91
+ else:
92
+ self._click_count = 2 # Force sync
93
+
94
+ # We must ignore the release event that follows this double-click event
95
+ self._ignore_next_release = True
96
+
54
97
  try:
55
98
  # Accept the event to mark it handled and prevent further processing.
56
99
  event.accept()
@@ -31,6 +31,11 @@ class Dialog3DPickingMixin:
31
31
  event.type() == QEvent.Type.MouseButtonPress and
32
32
  event.button() == Qt.MouseButton.LeftButton):
33
33
 
34
+ # Start tracking for smart selection (click vs drag)
35
+ self._mouse_press_pos = event.pos()
36
+ self._mouse_moved = False
37
+
38
+
34
39
  try:
35
40
  # VTKイベント座標を取得(元のロジックと同じ)
36
41
  interactor = self.main_window.plotter.interactor
@@ -48,8 +53,12 @@ class Dialog3DPickingMixin:
48
53
  # クリック閾値チェック(元のロジックと同じ)
49
54
  atom = self.mol.GetAtomWithIdx(int(closest_atom_idx))
50
55
  if atom:
51
- atomic_num = atom.GetAtomicNum()
52
- vdw_radius = pt.GetRvdw(atomic_num)
56
+ try:
57
+ atomic_num = atom.GetAtomicNum()
58
+ vdw_radius = pt.GetRvdw(atomic_num)
59
+ if vdw_radius < 0.1: vdw_radius = 1.5
60
+ except Exception:
61
+ vdw_radius = 1.5
53
62
  click_threshold = vdw_radius * 1.5
54
63
 
55
64
  if distances[closest_atom_idx] < click_threshold:
@@ -63,15 +72,14 @@ class Dialog3DPickingMixin:
63
72
  except Exception:
64
73
  pass
65
74
  self.on_atom_picked(int(closest_atom_idx))
75
+
76
+ # We picked an atom, so stop tracking for background click
77
+ self._mouse_press_pos = None
66
78
  return True
67
79
 
68
- # 原子以外をクリックした場合は選択をクリア(Measurementモードと同じロジック)
69
- if hasattr(self, 'clear_selection'):
70
- self.clear_selection()
71
- # We did not actually pick an atom; do NOT consume the event here so
72
- # the interactor and CustomInteractorStyle can handle camera rotation
73
- # and other behaviors. Returning False (or calling the base
74
- # implementation) allows normal processing to continue.
80
+ # 原子以外をクリックした場合
81
+ # 即時には解除せず、回転操作(ドラッグ)を許可する。
82
+ # 実際の解除は MouseButtonRelease イベントで行う。
75
83
  return False
76
84
 
77
85
  except Exception as e:
@@ -80,6 +88,31 @@ class Dialog3DPickingMixin:
80
88
  # event pipeline continue so the UI remains responsive.
81
89
  return False
82
90
 
91
+ # Add movement tracking for smart selection
92
+ elif (obj == self.main_window.plotter.interactor and
93
+ event.type() == QEvent.Type.MouseMove):
94
+ if hasattr(self, '_mouse_press_pos') and self._mouse_press_pos is not None:
95
+ # Check if moved significantly
96
+ diff = event.pos() - self._mouse_press_pos
97
+ if diff.manhattanLength() > 3:
98
+ self._mouse_moved = True
99
+
100
+ # Add release handling for smart selection
101
+ elif (obj == self.main_window.plotter.interactor and
102
+ event.type() == QEvent.Type.MouseButtonRelease and
103
+ event.button() == Qt.MouseButton.LeftButton):
104
+
105
+ if hasattr(self, '_mouse_press_pos') and self._mouse_press_pos is not None:
106
+ if not getattr(self, '_mouse_moved', False):
107
+ # Pure click (no drag) on background -> Clear selection
108
+ if hasattr(self, 'clear_selection'):
109
+ self.clear_selection()
110
+
111
+ # Reset state
112
+ self._mouse_press_pos = None
113
+ self._mouse_moved = False
114
+
115
+
83
116
  return super().eventFilter(obj, event)
84
117
 
85
118
  def enable_picking(self):
@@ -47,7 +47,7 @@ class DihedralDialog(Dialog3DPickingMixin, QDialog):
47
47
  def init_ui(self):
48
48
  self.setWindowTitle("Adjust Dihedral Angle")
49
49
  self.setModal(False) # モードレスにしてクリックを阻害しない
50
- self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowStaysOnTopHint) # 常に前面表示
50
+ # 常に前面表示
51
51
  layout = QVBoxLayout(self)
52
52
 
53
53
  # Instructions
@@ -263,7 +263,7 @@ class MainWindowDialogManager(object):
263
263
  self.measurement_action.setChecked(False)
264
264
  self.toggle_measurement_mode(False)
265
265
 
266
- dialog = TranslationDialog(self.current_mol, self)
266
+ dialog = TranslationDialog(self.current_mol, self, parent=self)
267
267
  self.active_3d_dialogs.append(dialog) # 参照を保持
268
268
  dialog.show() # execではなくshowを使用してモードレス表示
269
269
  dialog.accepted.connect(lambda: self.statusBar().showMessage("Translation applied."))
@@ -279,7 +279,7 @@ class MainWindowDialogManager(object):
279
279
  self.measurement_action.setChecked(False)
280
280
  self.toggle_measurement_mode(False)
281
281
 
282
- dialog = MoveGroupDialog(self.current_mol, self)
282
+ dialog = MoveGroupDialog(self.current_mol, self, parent=self)
283
283
  self.active_3d_dialogs.append(dialog)
284
284
  dialog.show()
285
285
  dialog.accepted.connect(lambda: self.statusBar().showMessage("Group transformation applied."))
@@ -302,7 +302,7 @@ class MainWindowDialogManager(object):
302
302
  self.measurement_action.setChecked(False)
303
303
  self.toggle_measurement_mode(False)
304
304
 
305
- dialog = AlignPlaneDialog(self.current_mol, self, plane, preselected_atoms)
305
+ dialog = AlignPlaneDialog(self.current_mol, self, plane, preselected_atoms, parent=self)
306
306
  self.active_3d_dialogs.append(dialog) # 参照を保持
307
307
  dialog.show() # execではなくshowを使用してモードレス表示
308
308
  dialog.accepted.connect(lambda: self.statusBar().showMessage(f"Atoms alignd to {plane.upper()} plane."))
@@ -325,7 +325,7 @@ class MainWindowDialogManager(object):
325
325
  self.measurement_action.setChecked(False)
326
326
  self.toggle_measurement_mode(False)
327
327
 
328
- dialog = PlanarizeDialog(self.current_mol, self, preselected_atoms)
328
+ dialog = PlanarizeDialog(self.current_mol, self, preselected_atoms, parent=self)
329
329
  self.active_3d_dialogs.append(dialog)
330
330
  dialog.show()
331
331
  dialog.accepted.connect(lambda: self.statusBar().showMessage("Selection planarized to best-fit plane."))
@@ -348,7 +348,7 @@ class MainWindowDialogManager(object):
348
348
  self.measurement_action.setChecked(False)
349
349
  self.toggle_measurement_mode(False)
350
350
 
351
- dialog = AlignmentDialog(self.current_mol, self, axis, preselected_atoms)
351
+ dialog = AlignmentDialog(self.current_mol, self, axis, preselected_atoms, parent=self)
352
352
  self.active_3d_dialogs.append(dialog) # 参照を保持
353
353
  dialog.show() # execではなくshowを使用してモードレス表示
354
354
  dialog.accepted.connect(lambda: self.statusBar().showMessage(f"Atoms aligned to {axis.upper()}-axis."))
@@ -371,7 +371,7 @@ class MainWindowDialogManager(object):
371
371
  self.measurement_action.setChecked(False)
372
372
  self.toggle_measurement_mode(False)
373
373
 
374
- dialog = BondLengthDialog(self.current_mol, self, preselected_atoms)
374
+ dialog = BondLengthDialog(self.current_mol, self, preselected_atoms, parent=self)
375
375
  self.active_3d_dialogs.append(dialog) # 参照を保持
376
376
  dialog.show() # execではなくshowを使用してモードレス表示
377
377
  dialog.accepted.connect(lambda: self.statusBar().showMessage("Bond length adjusted."))
@@ -394,7 +394,7 @@ class MainWindowDialogManager(object):
394
394
  self.measurement_action.setChecked(False)
395
395
  self.toggle_measurement_mode(False)
396
396
 
397
- dialog = AngleDialog(self.current_mol, self, preselected_atoms)
397
+ dialog = AngleDialog(self.current_mol, self, preselected_atoms, parent=self)
398
398
  self.active_3d_dialogs.append(dialog) # 参照を保持
399
399
  dialog.show() # execではなくshowを使用してモードレス表示
400
400
  dialog.accepted.connect(lambda: self.statusBar().showMessage("Angle adjusted."))
@@ -417,7 +417,7 @@ class MainWindowDialogManager(object):
417
417
  self.measurement_action.setChecked(False)
418
418
  self.toggle_measurement_mode(False)
419
419
 
420
- dialog = DihedralDialog(self.current_mol, self, preselected_atoms)
420
+ dialog = DihedralDialog(self.current_mol, self, preselected_atoms, parent=self)
421
421
  self.active_3d_dialogs.append(dialog) # 参照を保持
422
422
  dialog.show() # execではなくshowを使用してモードレス表示
423
423
  dialog.accepted.connect(lambda: self.statusBar().showMessage("Dihedral angle adjusted."))
@@ -453,7 +453,7 @@ class MainWindowDialogManager(object):
453
453
  self.measurement_action.setChecked(False)
454
454
  self.toggle_measurement_mode(False)
455
455
 
456
- dialog = ConstrainedOptimizationDialog(self.current_mol, self)
456
+ dialog = ConstrainedOptimizationDialog(self.current_mol, self, parent=self)
457
457
  self.active_3d_dialogs.append(dialog) # 参照を保持
458
458
  dialog.show() # モードレス表示
459
459
  dialog.finished.connect(lambda: self.remove_dialog_from_list(dialog))
@@ -1211,7 +1211,10 @@ class MainWindowEditActions(object):
1211
1211
 
1212
1212
  atom = mol.GetAtomWithIdx(idx)
1213
1213
  # GetRvdw() はファンデルワールス半径を返す
1214
- vdw_radii.append(pt.GetRvdw(atom.GetAtomicNum()))
1214
+ try:
1215
+ vdw_radii.append(pt.GetRvdw(atom.GetAtomicNum()))
1216
+ except RuntimeError:
1217
+ vdw_radii.append(1.5)
1215
1218
 
1216
1219
  positions_np = np.array(positions)
1217
1220
  vdw_radii_np = np.array(vdw_radii)
@@ -219,7 +219,15 @@ class MainWindowView3d(object):
219
219
  if current_style == 'cpk':
220
220
  atom_scale = self.settings.get('cpk_atom_scale', 1.0)
221
221
  resolution = self.settings.get('cpk_resolution', 32)
222
- rad = np.array([pt.GetRvdw(pt.GetAtomicNumber(s)) * atom_scale for s in sym])
222
+ # Safe VDW lookup to handle custom elements like 'Bq'
223
+ def get_safe_rvdw(s):
224
+ try:
225
+ r = pt.GetRvdw(pt.GetAtomicNumber(s))
226
+ return r if r > 0.1 else 1.5
227
+ except Exception:
228
+ return 1.5
229
+
230
+ rad = np.array([get_safe_rvdw(s) * atom_scale for s in sym])
223
231
  elif current_style == 'wireframe':
224
232
  # Wireframeでは原子を描画しないので、この設定は実際には使用されない
225
233
  resolution = self.settings.get('wireframe_resolution', 6)
@@ -42,7 +42,6 @@ class MoveGroupDialog(Dialog3DPickingMixin, QDialog):
42
42
  def init_ui(self):
43
43
  self.setWindowTitle("Move Group")
44
44
  self.setModal(False)
45
- self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowStaysOnTopHint)
46
45
  self.resize(300,400) # ウィンドウサイズを設定
47
46
  layout = QVBoxLayout(self)
48
47
 
@@ -195,9 +194,12 @@ class MoveGroupDialog(Dialog3DPickingMixin, QDialog):
195
194
  if 0 <= closest_atom_idx < self.mol.GetNumAtoms():
196
195
  atom = self.mol.GetAtomWithIdx(int(closest_atom_idx))
197
196
  if atom:
198
- atomic_num = atom.GetAtomicNum()
199
- pt = Chem.GetPeriodicTable()
200
- vdw_radius = pt.GetRvdw(atomic_num)
197
+ try:
198
+ atomic_num = atom.GetAtomicNum()
199
+ vdw_radius = pt.GetRvdw(atomic_num)
200
+ if vdw_radius < 0.1: vdw_radius = 1.5
201
+ except Exception:
202
+ vdw_radius = 1.5
201
203
  click_threshold = vdw_radius * 1.5
202
204
 
203
205
  if distances[closest_atom_idx] < click_threshold:
@@ -349,7 +351,7 @@ class MoveGroupDialog(Dialog3DPickingMixin, QDialog):
349
351
  return False
350
352
 
351
353
  # その他のイベントは親クラスに渡す
352
- return False
354
+ return super().eventFilter(obj, event)
353
355
 
354
356
  def on_atom_picked(self, atom_idx):
355
357
  """原子がピックされたときに、その原子が属する連結成分全体を選択(複数グループ対応)"""
@@ -46,7 +46,6 @@ class PlanarizeDialog(Dialog3DPickingMixin, QDialog):
46
46
  def init_ui(self):
47
47
  self.setWindowTitle("Planarize")
48
48
  self.setModal(False)
49
- self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowStaysOnTopHint)
50
49
  layout = QVBoxLayout(self)
51
50
 
52
51
  instruction_label = QLabel("Click atoms in the 3D view to select them for planarization (minimum 3 required).")
@@ -31,7 +31,6 @@ class TranslationDialog(Dialog3DPickingMixin, QDialog):
31
31
  def init_ui(self):
32
32
  self.setWindowTitle("Translation")
33
33
  self.setModal(False) # モードレスにしてクリックを阻害しない
34
- self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowStaysOnTopHint) # 常に前面表示
35
34
  layout = QVBoxLayout(self)
36
35
 
37
36
  # Instructions
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy
3
- Version: 2.2.5
3
+ Version: 2.2.6
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
@@ -3,57 +3,57 @@ moleditpy/__main__.py,sha256=bYu_h7lhc6PIl1-I_VI4_Q5MdBHmVrMiO4DYsy9jbiY,829
3
3
  moleditpy/main.py,sha256=0D8_CXe4xdgqlu2TWoT2pi35K9y2b4ilFj6LWD9Qk7w,1178
4
4
  moleditpy/modules/__init__.py,sha256=BndB5rKHIcdjtctu9csFxHm14khdUdN1BRO4aSzT3IQ,1727
5
5
  moleditpy/modules/about_dialog.py,sha256=Edt09PZbsU9qd94tJCY57CDHNmRVFfa0DSPLgP7IGG0,3683
6
- moleditpy/modules/align_plane_dialog.py,sha256=A4sGfnpWSLM5v4g2ADUVmZnWtKEaQxeGQk7ZRLTRj58,11944
7
- moleditpy/modules/alignment_dialog.py,sha256=lZOWo1Y7uKt31nxZAguNDHNZxKww_aLcg-JPilM2CPQ,11419
6
+ moleditpy/modules/align_plane_dialog.py,sha256=SEC32l8z2x1W1Sf1Gu001OQEtinuvqGuCGHRELmtU5s,11834
7
+ moleditpy/modules/alignment_dialog.py,sha256=CPubSJI52mNSXmWt7oKQ2un5lXnN8_K4Z-LMM7wWTxQ,11309
8
8
  moleditpy/modules/analysis_window.py,sha256=zjP5ipSTpKw8oLr1eKdoxW8Bk1SslGlPqsVucD-x_5w,9403
9
- moleditpy/modules/angle_dialog.py,sha256=ytuE5VqT0dskgob1x08B6VW06qnslgCdAs_dwS2ANgY,17837
9
+ moleditpy/modules/angle_dialog.py,sha256=uc2WbvSfRe892xoEirqpZ78pf2Smwzkinkso6zLWr0Y,17751
10
10
  moleditpy/modules/atom_item.py,sha256=u8ge6B1M9sOGobfzg3tp1-EGXtEUmvdee7Fx6msg8Wk,15566
11
11
  moleditpy/modules/bond_item.py,sha256=eVkEeKvM4igYI67DYxpey3FllqDyt_iWDo4VPYMhaPk,19137
12
- moleditpy/modules/bond_length_dialog.py,sha256=k5x_DhK9Q8CSwouKhEo_kLRRdaYHDaK84KDNmuDNLvY,14868
12
+ moleditpy/modules/bond_length_dialog.py,sha256=6bFPGssnqlgINuqpxLv-OhjMH3_hspnaH8QtorAyu2M,14782
13
13
  moleditpy/modules/calculation_worker.py,sha256=KiGQY7i-QCQofEoE0r65KoQgpEGFcbhmxWv6egfkUdc,42324
14
14
  moleditpy/modules/color_settings_dialog.py,sha256=Ow44BhCOLo0AFb6klO001k6B4drOgKX9DeNBQhZLp5o,15474
15
- moleditpy/modules/constants.py,sha256=DVxFJrPqhASSlFj9fRpsOe8RiJc2lJqTJMQtmnUCIqg,4702
16
- moleditpy/modules/constrained_optimization_dialog.py,sha256=IEdNVhFoNSEMeA5ABpUH9Q88-YzDXFloQM2gwnPwnHY,30150
17
- moleditpy/modules/custom_interactor_style.py,sha256=NjsXE2a43IDNEanZBlcG9eR4ZIERT1MsQC6lbfesapQ,38453
18
- moleditpy/modules/custom_qt_interactor.py,sha256=MFaTuDh-FPeFBS4303CqxsxmsOIOW4QXUz6USwI8PHQ,2451
19
- moleditpy/modules/dialog3_d_picking_mixin.py,sha256=2Sut0J5ltXMtrUJ9R3o1oZ4ysed27mdSIqLpWxmGdyM,5037
20
- moleditpy/modules/dihedral_dialog.py,sha256=rgry7LqyX9JMAR7d82QSroTPoKT3xz18EgKN1GzYZx4,18088
15
+ moleditpy/modules/constants.py,sha256=_LJDuqJFfR_t_84zzl3p5dhK7C2LB-ZyFuBE4K0dUE4,4702
16
+ moleditpy/modules/constrained_optimization_dialog.py,sha256=REsk4ePsqNmAGPMTS_jckeM7jexrU3krwun8sKqKUCs,30062
17
+ moleditpy/modules/custom_interactor_style.py,sha256=LDNODMJoNHGe1AUSrvqv6PdeJm-hpPmSpWINppnJLt0,38942
18
+ moleditpy/modules/custom_qt_interactor.py,sha256=vCZsDfRO-FtphD5cTP7Ps-5rpHZMIGloaoe6EaKzrsw,4139
19
+ moleditpy/modules/dialog3_d_picking_mixin.py,sha256=z4udbkiX9PYmIGazPXsbftkk_oRRwZhcvlCqbyJzr24,6493
20
+ moleditpy/modules/dihedral_dialog.py,sha256=bOTDO6-b74vEDn_z6OyuBr5cRz3RnRj83PiaEBUyWJA,18002
21
21
  moleditpy/modules/main_window.py,sha256=IL8dH3qPx2TkPgO7amuDgjlFoadh5J59xYUEVhlNZqA,36338
22
22
  moleditpy/modules/main_window_app_state.py,sha256=8YDcGNCSpLTO1NGL9tEvNkXpUcS7JW-uK7TdUGvEqnk,35189
23
23
  moleditpy/modules/main_window_compute.py,sha256=ipIkhH_DONXDnPzh7xeym9X-Yfx8EhsvXYOdyxsAj4c,53347
24
- moleditpy/modules/main_window_dialog_manager.py,sha256=9ZVy5-dlk8SjGU5R6NtbaVZuBQfVK5Nwrc633pEzjq8,19955
24
+ moleditpy/modules/main_window_dialog_manager.py,sha256=QR96LqHAPSOShXbc9cK-Ffq8a16JrXAoMKB0pHjESrQ,20072
25
25
  moleditpy/modules/main_window_edit_3d.py,sha256=CUArB5wcsgq1C7LygAEC6URlbnn4RhRYDa5n-Y-etWI,19731
26
- moleditpy/modules/main_window_edit_actions.py,sha256=-SDLoMQ7S-3u3eBUb-w7BU7OAsFkhQ9ZBjF3Y2jGgZc,64708
26
+ moleditpy/modules/main_window_edit_actions.py,sha256=yEc0Nw-VpN0P4e4neUu7pDuUHPGEcu6eFmwWFrSBIQ8,64815
27
27
  moleditpy/modules/main_window_export.py,sha256=dSVfylsybDDboDuXU9Inotf6YkrKJwgBTqGYSfq1lRE,38241
28
28
  moleditpy/modules/main_window_main_init.py,sha256=Shxs8_vVa7PhfhVZdHYtPniItLHKJ3i9wcamcVofvso,91172
29
29
  moleditpy/modules/main_window_molecular_parsers.py,sha256=KR6vzuqc3nutOcorpYr0QOyX3MFBcxTwDhZX96VgJ9Q,48291
30
30
  moleditpy/modules/main_window_project_io.py,sha256=TWwtuKDuvgcvPZ9IGmW8r1EJJOrgxrIJRnxe_f4C1oM,17149
31
31
  moleditpy/modules/main_window_string_importers.py,sha256=v47wOd4RtjKYcF-aLP-mogGGdYTpTEo3dDyAu79_5MM,10782
32
32
  moleditpy/modules/main_window_ui_manager.py,sha256=HofI6T9EvcSSzPbsdPqkYEEDoB6Hui1Uj2Ll-wwczGA,24016
33
- moleditpy/modules/main_window_view_3d.py,sha256=6eN3_NdpNJrCzMSp17t9_Omm71MwfgqDC8A5DSYHhUo,73942
33
+ moleditpy/modules/main_window_view_3d.py,sha256=CxZxyJHl2isF7KtyVWSI9f8LVbvdZM5H9Gnhm_8ovBM,74227
34
34
  moleditpy/modules/main_window_view_loaders.py,sha256=gklTMo27QnyJ8Gd0ampPdbm9d0Gi-oHWkIqQuGADHmI,14352
35
35
  moleditpy/modules/mirror_dialog.py,sha256=c3v4qY6R4FAljzk4EPaDjL9ZdZMjLQSFLqDMXz2fBUk,4696
36
36
  moleditpy/modules/molecular_data.py,sha256=8gE9ByYg3kSBfb1zANsyad_BVBTm6WOLF7NsZIYuG2E,13250
37
37
  moleditpy/modules/molecule_scene.py,sha256=khdt7h9Mk_D1cMbYeHGtq7P9aFXo0xG-hcShU_H2Y-Q,95911
38
- moleditpy/modules/move_group_dialog.py,sha256=65HVXTJSaQ9lp03XFhI1l7OzUsXmH_aqd8OgwjpjfGg,27174
38
+ moleditpy/modules/move_group_dialog.py,sha256=Fyuy3Uq1KsFsk9qR96r_FxPbAM_-zSfW2dsMQGv7btc,27276
39
39
  moleditpy/modules/periodic_table_dialog.py,sha256=ItEZUts1XCietz9paY-spvbzxh6SXak3GnikwqkHZCw,4006
40
- moleditpy/modules/planarize_dialog.py,sha256=yY8o-SxT8vGEHVWnjDTXecRv5NUaEejEsXH-836Xk8g,8681
40
+ moleditpy/modules/planarize_dialog.py,sha256=eaqI1MpF35e-VUMpJATt-EtGG5FhcSUlbAenUaFGabY,8593
41
41
  moleditpy/modules/plugin_interface.py,sha256=srzPZ3a_aRTx28NAvWNKRVUDYQNfQOTcjzx-5YW2Pb4,8164
42
42
  moleditpy/modules/plugin_manager.py,sha256=cxbqIE7Rb_KeBd-cG1vF2ySY2qNx8IJVRXjVPyQMFDc,13457
43
43
  moleditpy/modules/plugin_manager_window.py,sha256=UeoPQWTxmckoFIQAuv9jVBzok_9gEKEm9c7JsKRX0P4,10135
44
44
  moleditpy/modules/settings_dialog.py,sha256=Nr7yE8UmYRi3VObWvRlrnv0DnjSjmYXbvqryZ02O12k,65348
45
45
  moleditpy/modules/template_preview_item.py,sha256=djdq3tz73d_fJGOvai3E-V9Hk9q9ZW7skx7BV59mooA,6556
46
46
  moleditpy/modules/template_preview_view.py,sha256=4OCHZDO51BvJpKdfrBWJ4_4WfLfFSKxsVIyf7I-Kj2E,3350
47
- moleditpy/modules/translation_dialog.py,sha256=aWlgTR9mtEMbzGIY1SoQhDltsX-01LtCxjqy5NWtGuA,14663
47
+ moleditpy/modules/translation_dialog.py,sha256=x_GJsbVk-cj4aN2KgmYWDRUDInFlXezAoYoTvX-OT30,14553
48
48
  moleditpy/modules/user_template_dialog.py,sha256=2hARO04DaILgdExx5ubL0GPsxK95VvVRqy7fNfudD_M,30843
49
49
  moleditpy/modules/zoomable_view.py,sha256=hjwljui13QpvjvxJHY4Evot4jMQvxRBQUNH5HUlyFOk,5966
50
50
  moleditpy/modules/assets/file_icon.ico,sha256=yyVj084A7HuMNbV073cE_Ag3Ne405qgOP3Mia1ZqLpE,101632
51
51
  moleditpy/modules/assets/icon.icns,sha256=wD5R6-Vw7K662tVKhu2E1ImN0oUuyAP4youesEQsn9c,139863
52
52
  moleditpy/modules/assets/icon.ico,sha256=RfgFcx7-dHY_2STdsOQCQziY5SNhDr3gPnjO6jzEDPI,147975
53
53
  moleditpy/modules/assets/icon.png,sha256=kCFN1WacYIdy0GN6SFEbNA00ef39pCczBnFdkkBI8Bs,147110
54
- moleditpy-2.2.5.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
55
- moleditpy-2.2.5.dist-info/METADATA,sha256=0aF8Gce505hfTDsA0QNVH6C5SZVUfHEQSGwxTEIDRQI,60629
56
- moleditpy-2.2.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
57
- moleditpy-2.2.5.dist-info/entry_points.txt,sha256=yH1h9JjALhok1foXT3-hYrC4ufoZt8b7oiBcsdnGNNM,54
58
- moleditpy-2.2.5.dist-info/top_level.txt,sha256=ARICrS4ihlPXqywlKl6o-oJa3Qz3gZRWu_VZsQ3_c44,10
59
- moleditpy-2.2.5.dist-info/RECORD,,
54
+ moleditpy-2.2.6.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
55
+ moleditpy-2.2.6.dist-info/METADATA,sha256=vHsKbmtTEC9mSX2KC3VMASZqxNyvRpQCblso_8fBiN8,60629
56
+ moleditpy-2.2.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
57
+ moleditpy-2.2.6.dist-info/entry_points.txt,sha256=yH1h9JjALhok1foXT3-hYrC4ufoZt8b7oiBcsdnGNNM,54
58
+ moleditpy-2.2.6.dist-info/top_level.txt,sha256=ARICrS4ihlPXqywlKl6o-oJa3Qz3gZRWu_VZsQ3_c44,10
59
+ moleditpy-2.2.6.dist-info/RECORD,,