MoleditPy 1.15.1__py3-none-any.whl → 1.16.0__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.
Files changed (55) hide show
  1. moleditpy/__init__.py +4 -0
  2. moleditpy/__main__.py +29 -19748
  3. moleditpy/main.py +37 -0
  4. moleditpy/modules/__init__.py +36 -0
  5. moleditpy/modules/about_dialog.py +92 -0
  6. moleditpy/modules/align_plane_dialog.py +281 -0
  7. moleditpy/modules/alignment_dialog.py +261 -0
  8. moleditpy/modules/analysis_window.py +197 -0
  9. moleditpy/modules/angle_dialog.py +428 -0
  10. moleditpy/modules/assets/icon.icns +0 -0
  11. moleditpy/modules/atom_item.py +336 -0
  12. moleditpy/modules/bond_item.py +303 -0
  13. moleditpy/modules/bond_length_dialog.py +368 -0
  14. moleditpy/modules/calculation_worker.py +754 -0
  15. moleditpy/modules/color_settings_dialog.py +309 -0
  16. moleditpy/modules/constants.py +76 -0
  17. moleditpy/modules/constrained_optimization_dialog.py +667 -0
  18. moleditpy/modules/custom_interactor_style.py +737 -0
  19. moleditpy/modules/custom_qt_interactor.py +49 -0
  20. moleditpy/modules/dialog3_d_picking_mixin.py +96 -0
  21. moleditpy/modules/dihedral_dialog.py +431 -0
  22. moleditpy/modules/main_window.py +830 -0
  23. moleditpy/modules/main_window_app_state.py +747 -0
  24. moleditpy/modules/main_window_compute.py +1203 -0
  25. moleditpy/modules/main_window_dialog_manager.py +454 -0
  26. moleditpy/modules/main_window_edit_3d.py +531 -0
  27. moleditpy/modules/main_window_edit_actions.py +1449 -0
  28. moleditpy/modules/main_window_export.py +744 -0
  29. moleditpy/modules/main_window_main_init.py +1641 -0
  30. moleditpy/modules/main_window_molecular_parsers.py +956 -0
  31. moleditpy/modules/main_window_project_io.py +429 -0
  32. moleditpy/modules/main_window_string_importers.py +270 -0
  33. moleditpy/modules/main_window_ui_manager.py +567 -0
  34. moleditpy/modules/main_window_view_3d.py +1163 -0
  35. moleditpy/modules/main_window_view_loaders.py +350 -0
  36. moleditpy/modules/mirror_dialog.py +110 -0
  37. moleditpy/modules/molecular_data.py +290 -0
  38. moleditpy/modules/molecule_scene.py +1895 -0
  39. moleditpy/modules/move_group_dialog.py +586 -0
  40. moleditpy/modules/periodic_table_dialog.py +72 -0
  41. moleditpy/modules/planarize_dialog.py +209 -0
  42. moleditpy/modules/settings_dialog.py +1034 -0
  43. moleditpy/modules/template_preview_item.py +148 -0
  44. moleditpy/modules/template_preview_view.py +62 -0
  45. moleditpy/modules/translation_dialog.py +353 -0
  46. moleditpy/modules/user_template_dialog.py +621 -0
  47. moleditpy/modules/zoomable_view.py +98 -0
  48. {moleditpy-1.15.1.dist-info → moleditpy-1.16.0.dist-info}/METADATA +1 -1
  49. moleditpy-1.16.0.dist-info/RECORD +54 -0
  50. moleditpy-1.15.1.dist-info/RECORD +0 -9
  51. /moleditpy/{assets → modules/assets}/icon.ico +0 -0
  52. /moleditpy/{assets → modules/assets}/icon.png +0 -0
  53. {moleditpy-1.15.1.dist-info → moleditpy-1.16.0.dist-info}/WHEEL +0 -0
  54. {moleditpy-1.15.1.dist-info → moleditpy-1.16.0.dist-info}/entry_points.txt +0 -0
  55. {moleditpy-1.15.1.dist-info → moleditpy-1.16.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,531 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ main_window_edit_3d.py
6
+ MainWindow (main_window.py) から分離されたモジュール
7
+ 機能クラス: MainWindowEdit3d
8
+ """
9
+
10
+
11
+ import numpy as np
12
+
13
+
14
+ # RDKit imports (explicit to satisfy flake8 and used features)
15
+ try:
16
+ from . import sip_isdeleted_safe
17
+ except Exception:
18
+ from modules import sip_isdeleted_safe
19
+
20
+ # PyQt6 Modules
21
+ from PyQt6.QtWidgets import (
22
+ QGraphicsTextItem
23
+ )
24
+
25
+ from PyQt6.QtGui import (
26
+ QColor, QFont
27
+ )
28
+
29
+
30
+ from PyQt6.QtCore import (
31
+ QPointF
32
+ )
33
+
34
+ import pyvista as pv
35
+
36
+ # Use centralized Open Babel availability from package-level __init__
37
+ # Use per-package modules availability (local __init__).
38
+ try:
39
+ from . import OBABEL_AVAILABLE
40
+ except Exception:
41
+ from modules import OBABEL_AVAILABLE
42
+ # Only import pybel on demand — `moleditpy` itself doesn't expose `pybel`.
43
+ if OBABEL_AVAILABLE:
44
+ try:
45
+ from openbabel import pybel
46
+ except Exception:
47
+ # If import fails here, disable OBABEL locally; avoid raising
48
+ pybel = None
49
+ OBABEL_AVAILABLE = False
50
+ print("Warning: openbabel.pybel not available. Open Babel fallback and OBabel-based options will be disabled.")
51
+ else:
52
+ pybel = None
53
+
54
+ # Optional SIP helper: on some PyQt6 builds sip.isdeleted is available and
55
+ # allows safely detecting C++ wrapper objects that have been deleted. Import
56
+ # it once at module import time and expose a small, robust wrapper so callers
57
+ # can avoid re-importing sip repeatedly and so we centralize exception
58
+ # handling (this reduces crash risk during teardown and deletion operations).
59
+ try:
60
+ import sip as _sip # type: ignore
61
+ _sip_isdeleted = getattr(_sip, 'isdeleted', None)
62
+ except Exception:
63
+ _sip = None
64
+ _sip_isdeleted = None
65
+
66
+ try:
67
+ # package relative imports (preferred when running as `python -m moleditpy`)
68
+ from .constants import VDW_RADII
69
+ except Exception:
70
+ # Fallback to absolute imports for script-style execution
71
+ from modules.constants import VDW_RADII
72
+
73
+
74
+ # --- クラス定義 ---
75
+ class MainWindowEdit3d(object):
76
+ """ main_window.py から分離された機能クラス """
77
+
78
+ def __init__(self, main_window):
79
+ """ クラスの初期化 """
80
+ self.mw = main_window
81
+
82
+
83
+ def toggle_measurement_mode(self, checked):
84
+ """測定モードのオン/オフを切り替える"""
85
+ if checked:
86
+ # 測定モードをオンにする時は、3D Dragモードを無効化
87
+ if self.is_3d_edit_mode:
88
+ self.edit_3d_action.setChecked(False)
89
+ self.toggle_3d_edit_mode(False)
90
+
91
+ # アクティブな3D編集ダイアログを閉じる
92
+ self.close_all_3d_edit_dialogs()
93
+
94
+ self.measurement_mode = checked
95
+
96
+ if not checked:
97
+ self.clear_measurement_selection()
98
+
99
+ # ボタンのテキストとステータスメッセージを更新
100
+ if checked:
101
+ self.statusBar().showMessage("Measurement mode enabled. Click atoms to measure distances/angles/dihedrals.")
102
+ else:
103
+ self.statusBar().showMessage("Measurement mode disabled.")
104
+
105
+
106
+
107
+ def close_all_3d_edit_dialogs(self):
108
+ """すべてのアクティブな3D編集ダイアログを閉じる"""
109
+ dialogs_to_close = self.active_3d_dialogs.copy()
110
+ for dialog in dialogs_to_close:
111
+ try:
112
+ dialog.close()
113
+ except:
114
+ pass
115
+ self.active_3d_dialogs.clear()
116
+
117
+
118
+
119
+ def handle_measurement_atom_selection(self, atom_idx):
120
+ """測定用の原子選択を処理する"""
121
+ # 既に選択されている原子の場合は除外
122
+ if atom_idx in self.selected_atoms_for_measurement:
123
+ return
124
+
125
+ self.selected_atoms_for_measurement.append(atom_idx)
126
+
127
+ '''
128
+ # 4つ以上選択された場合はクリア
129
+ if len(self.selected_atoms_for_measurement) > 4:
130
+ self.clear_measurement_selection()
131
+ self.selected_atoms_for_measurement.append(atom_idx)
132
+ '''
133
+
134
+ # 原子にラベルを追加
135
+ self.add_measurement_label(atom_idx, len(self.selected_atoms_for_measurement))
136
+
137
+ # 測定値を計算して表示
138
+ self.calculate_and_display_measurements()
139
+
140
+
141
+
142
+ def add_measurement_label(self, atom_idx, label_number):
143
+ """原子に数字ラベルを追加する"""
144
+ if not self.current_mol or atom_idx >= self.current_mol.GetNumAtoms():
145
+ return
146
+
147
+ # 測定ラベルリストを更新
148
+ self.measurement_labels.append((atom_idx, str(label_number)))
149
+
150
+ # 3Dビューの測定ラベルを再描画
151
+ self.update_measurement_labels_display()
152
+
153
+ # 2Dビューの測定ラベルも更新
154
+ self.update_2d_measurement_labels()
155
+
156
+
157
+
158
+ def update_measurement_labels_display(self):
159
+ """測定ラベルを3D表示に描画する(原子中心配置)"""
160
+ try:
161
+ # 既存の測定ラベルを削除
162
+ self.plotter.remove_actor('measurement_labels')
163
+ except:
164
+ pass
165
+
166
+ if not self.measurement_labels or not self.current_mol:
167
+ return
168
+
169
+ # ラベル位置とテキストを準備
170
+ pts, labels = [], []
171
+ for atom_idx, label_text in self.measurement_labels:
172
+ if atom_idx < len(self.atom_positions_3d):
173
+ coord = self.atom_positions_3d[atom_idx].copy()
174
+ # オフセットを削除して原子中心に配置
175
+ pts.append(coord)
176
+ labels.append(label_text)
177
+
178
+ if pts and labels:
179
+ # PyVistaのpoint_labelsを使用(赤色固定)
180
+ self.plotter.add_point_labels(
181
+ np.array(pts),
182
+ labels,
183
+ font_size=16,
184
+ point_size=0,
185
+ text_color='red', # 測定時は常に赤色
186
+ name='measurement_labels',
187
+ always_visible=True,
188
+ tolerance=0.01,
189
+ show_points=False
190
+ )
191
+
192
+
193
+
194
+ def clear_measurement_selection(self):
195
+ """測定選択をクリアする"""
196
+ self.selected_atoms_for_measurement.clear()
197
+
198
+ # 3Dビューのラベルを削除
199
+ self.measurement_labels.clear()
200
+ try:
201
+ self.plotter.remove_actor('measurement_labels')
202
+ except:
203
+ pass
204
+
205
+ # 2Dビューの測定ラベルも削除
206
+ self.clear_2d_measurement_labels()
207
+
208
+ # 測定結果のテキストを削除
209
+ if self.measurement_text_actor:
210
+ try:
211
+ self.plotter.remove_actor(self.measurement_text_actor)
212
+ self.measurement_text_actor = None
213
+ except:
214
+ pass
215
+
216
+ self.plotter.render()
217
+
218
+
219
+
220
+ def update_2d_measurement_labels(self):
221
+ """2Dビューで測定ラベルを更新表示する"""
222
+ # 既存の2D測定ラベルを削除
223
+ self.clear_2d_measurement_labels()
224
+
225
+ # 現在の分子から原子-AtomItemマッピングを作成
226
+ if not self.current_mol or not hasattr(self, 'data') or not self.data.atoms:
227
+ return
228
+
229
+ # RDKit原子インデックスから2D AtomItemへのマッピングを作成
230
+ atom_idx_to_item = {}
231
+
232
+ # シーンからAtomItemを取得してマッピング
233
+ if hasattr(self, 'scene'):
234
+ for item in self.scene.items():
235
+ if hasattr(item, 'atom_id') and hasattr(item, 'symbol'): # AtomItemかチェック
236
+ # 原子IDから対応するRDKit原子インデックスを見つける
237
+ rdkit_idx = self.find_rdkit_atom_index(item)
238
+ if rdkit_idx is not None:
239
+ atom_idx_to_item[rdkit_idx] = item
240
+
241
+ # 測定ラベルを2Dビューに追加
242
+ if not hasattr(self, 'measurement_label_items_2d'):
243
+ self.measurement_label_items_2d = []
244
+
245
+ for atom_idx, label_text in self.measurement_labels:
246
+ if atom_idx in atom_idx_to_item:
247
+ atom_item = atom_idx_to_item[atom_idx]
248
+ self.add_2d_measurement_label(atom_item, label_text)
249
+
250
+
251
+
252
+ def add_2d_measurement_label(self, atom_item, label_text):
253
+ """特定のAtomItemに測定ラベルを追加する"""
254
+ # ラベルアイテムを作成
255
+ label_item = QGraphicsTextItem(label_text)
256
+ label_item.setDefaultTextColor(QColor(255, 0, 0)) # 赤色
257
+ label_item.setFont(QFont("Arial", 12, QFont.Weight.Bold))
258
+
259
+ # Z値を設定して最前面に表示(原子ラベルより上)
260
+ label_item.setZValue(2000) # より高い値で確実に最前面に配置
261
+
262
+ # 原子の右上により近く配置
263
+ atom_pos = atom_item.pos()
264
+ atom_rect = atom_item.boundingRect()
265
+ label_pos = QPointF(
266
+ atom_pos.x() + atom_rect.width() / 4 + 2,
267
+ atom_pos.y() - atom_rect.height() / 4 - 8
268
+ )
269
+ label_item.setPos(label_pos)
270
+
271
+ # シーンに追加
272
+ self.scene.addItem(label_item)
273
+ self.measurement_label_items_2d.append(label_item)
274
+
275
+
276
+
277
+ def clear_2d_measurement_labels(self):
278
+ """2Dビューの測定ラベルを全て削除する"""
279
+ if hasattr(self, 'measurement_label_items_2d'):
280
+ for label_item in self.measurement_label_items_2d:
281
+ try:
282
+ # Avoid touching partially-deleted wrappers
283
+ if sip_isdeleted_safe(label_item):
284
+ continue
285
+ try:
286
+ if label_item.scene():
287
+ self.scene.removeItem(label_item)
288
+ except Exception:
289
+ # Scene access or removal failed; skip
290
+ continue
291
+ except Exception:
292
+ # If sip check itself fails, fall back to best-effort removal
293
+ try:
294
+ if label_item.scene():
295
+ self.scene.removeItem(label_item)
296
+ except Exception:
297
+ continue
298
+ self.measurement_label_items_2d.clear()
299
+
300
+
301
+
302
+ def find_rdkit_atom_index(self, atom_item):
303
+ """AtomItemから対応するRDKit原子インデックスを見つける"""
304
+ if not self.current_mol or not atom_item:
305
+ return None
306
+
307
+ # マッピング辞書を使用(最も確実)
308
+ if hasattr(self, 'atom_id_to_rdkit_idx_map') and atom_item.atom_id in self.atom_id_to_rdkit_idx_map:
309
+ return self.atom_id_to_rdkit_idx_map[atom_item.atom_id]
310
+
311
+ # マッピングが存在しない場合はNone(外部ファイル読み込み時など)
312
+ return None
313
+
314
+
315
+
316
+ def calculate_and_display_measurements(self):
317
+ """選択された原子に基づいて測定値を計算し表示する"""
318
+ num_selected = len(self.selected_atoms_for_measurement)
319
+ if num_selected < 2:
320
+ return
321
+
322
+ measurement_text = []
323
+
324
+ if num_selected >= 2:
325
+ # 距離の計算
326
+ atom1_idx = self.selected_atoms_for_measurement[0]
327
+ atom2_idx = self.selected_atoms_for_measurement[1]
328
+ distance = self.calculate_distance(atom1_idx, atom2_idx)
329
+ measurement_text.append(f"Distance 1-2: {distance:.3f} Å")
330
+
331
+ if num_selected >= 3:
332
+ # 角度の計算
333
+ atom1_idx = self.selected_atoms_for_measurement[0]
334
+ atom2_idx = self.selected_atoms_for_measurement[1]
335
+ atom3_idx = self.selected_atoms_for_measurement[2]
336
+ angle = self.calculate_angle(atom1_idx, atom2_idx, atom3_idx)
337
+ measurement_text.append(f"Angle 1-2-3: {angle:.2f}°")
338
+
339
+ if num_selected >= 4:
340
+ # 二面角の計算
341
+ atom1_idx = self.selected_atoms_for_measurement[0]
342
+ atom2_idx = self.selected_atoms_for_measurement[1]
343
+ atom3_idx = self.selected_atoms_for_measurement[2]
344
+ atom4_idx = self.selected_atoms_for_measurement[3]
345
+ dihedral = self.calculate_dihedral(atom1_idx, atom2_idx, atom3_idx, atom4_idx)
346
+ measurement_text.append(f"Dihedral 1-2-3-4: {dihedral:.2f}°")
347
+
348
+ # 測定結果を3D画面の右上に表示
349
+ self.display_measurement_text(measurement_text)
350
+
351
+
352
+
353
+ def calculate_distance(self, atom1_idx, atom2_idx):
354
+ """2原子間の距離を計算する"""
355
+ pos1 = np.array(self.atom_positions_3d[atom1_idx])
356
+ pos2 = np.array(self.atom_positions_3d[atom2_idx])
357
+ return np.linalg.norm(pos2 - pos1)
358
+
359
+
360
+
361
+ def calculate_angle(self, atom1_idx, atom2_idx, atom3_idx):
362
+ """3原子の角度を計算する(中央が頂点)"""
363
+ pos1 = np.array(self.atom_positions_3d[atom1_idx])
364
+ pos2 = np.array(self.atom_positions_3d[atom2_idx]) # 頂点
365
+ pos3 = np.array(self.atom_positions_3d[atom3_idx])
366
+
367
+ # ベクトルを計算
368
+ vec1 = pos1 - pos2
369
+ vec2 = pos3 - pos2
370
+
371
+ # 角度を計算(ラジアンから度に変換)
372
+ cos_angle = np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))
373
+ # 数値誤差による範囲外の値をクリップ
374
+ cos_angle = np.clip(cos_angle, -1.0, 1.0)
375
+ angle_rad = np.arccos(cos_angle)
376
+ return np.degrees(angle_rad)
377
+
378
+
379
+
380
+ def calculate_dihedral(self, atom1_idx, atom2_idx, atom3_idx, atom4_idx):
381
+ """4原子の二面角を計算する(正しい公式を使用)"""
382
+ pos1 = np.array(self.atom_positions_3d[atom1_idx])
383
+ pos2 = np.array(self.atom_positions_3d[atom2_idx])
384
+ pos3 = np.array(self.atom_positions_3d[atom3_idx])
385
+ pos4 = np.array(self.atom_positions_3d[atom4_idx])
386
+
387
+ # Vectors between consecutive atoms
388
+ v1 = pos2 - pos1 # 1->2
389
+ v2 = pos3 - pos2 # 2->3 (central bond)
390
+ v3 = pos4 - pos3 # 3->4
391
+
392
+ # Normalize the central bond vector
393
+ v2_norm = v2 / np.linalg.norm(v2)
394
+
395
+ # Calculate plane normal vectors
396
+ n1 = np.cross(v1, v2) # Normal to plane 1-2-3
397
+ n2 = np.cross(v2, v3) # Normal to plane 2-3-4
398
+
399
+ # Normalize the normal vectors
400
+ n1_norm = np.linalg.norm(n1)
401
+ n2_norm = np.linalg.norm(n2)
402
+
403
+ if n1_norm == 0 or n2_norm == 0:
404
+ return 0.0 # Atoms are collinear
405
+
406
+ n1 = n1 / n1_norm
407
+ n2 = n2 / n2_norm
408
+
409
+ # Calculate the cosine of the dihedral angle
410
+ cos_angle = np.dot(n1, n2)
411
+ cos_angle = np.clip(cos_angle, -1.0, 1.0)
412
+
413
+ # Calculate the sine for proper sign determination
414
+ sin_angle = np.dot(np.cross(n1, n2), v2_norm)
415
+
416
+ # Calculate the dihedral angle with correct sign
417
+ angle_rad = np.arctan2(sin_angle, cos_angle)
418
+ return np.degrees(angle_rad)
419
+
420
+
421
+
422
+ def display_measurement_text(self, measurement_lines):
423
+ """測定結果のテキストを3D画面の左上に表示する(小さな等幅フォント)"""
424
+ # 既存のテキストを削除
425
+ if self.measurement_text_actor:
426
+ try:
427
+ self.plotter.remove_actor(self.measurement_text_actor)
428
+ except:
429
+ pass
430
+
431
+ if not measurement_lines:
432
+ self.measurement_text_actor = None
433
+ return
434
+
435
+ # テキストを結合
436
+ text = '\n'.join(measurement_lines)
437
+
438
+ # 背景色から適切なテキスト色を決定
439
+ try:
440
+ bg_color_hex = self.settings.get('background_color', '#919191')
441
+ bg_qcolor = QColor(bg_color_hex)
442
+ if bg_qcolor.isValid():
443
+ luminance = bg_qcolor.toHsl().lightness()
444
+ text_color = 'black' if luminance > 128 else 'white'
445
+ else:
446
+ text_color = 'white'
447
+ except:
448
+ text_color = 'white'
449
+
450
+ # 左上に表示(小さな等幅フォント)
451
+ self.measurement_text_actor = self.plotter.add_text(
452
+ text,
453
+ position='upper_left',
454
+ font_size=10, # より小さく
455
+ color=text_color, # 背景に合わせた色
456
+ font='courier', # 等幅フォント
457
+ name='measurement_display'
458
+ )
459
+
460
+ self.plotter.render()
461
+
462
+ # --- 3D Drag functionality ---
463
+
464
+
465
+
466
+ def toggle_atom_selection_3d(self, atom_idx):
467
+ """3Dビューで原子の選択状態をトグルする"""
468
+ if atom_idx in self.selected_atoms_3d:
469
+ self.selected_atoms_3d.remove(atom_idx)
470
+ else:
471
+ self.selected_atoms_3d.add(atom_idx)
472
+
473
+ # 選択状態のビジュアルフィードバックを更新
474
+ self.update_3d_selection_display()
475
+
476
+
477
+
478
+ def clear_3d_selection(self):
479
+ """3Dビューでの原子選択をクリア"""
480
+ self.selected_atoms_3d.clear()
481
+ self.update_3d_selection_display()
482
+
483
+
484
+
485
+ def update_3d_selection_display(self):
486
+ """3Dビューでの選択原子のハイライト表示を更新"""
487
+ try:
488
+ # 既存の選択ハイライトを削除
489
+ self.plotter.remove_actor('selection_highlight')
490
+ except:
491
+ pass
492
+
493
+ if not self.selected_atoms_3d or not self.current_mol:
494
+ self.plotter.render()
495
+ return
496
+
497
+ # 選択された原子のインデックスリストを作成
498
+ selected_indices = list(self.selected_atoms_3d)
499
+
500
+ # 選択された原子の位置を取得
501
+ selected_positions = self.atom_positions_3d[selected_indices]
502
+
503
+ # 原子の半径を少し大きくしてハイライト表示
504
+ selected_radii = np.array([VDW_RADII.get(
505
+ self.current_mol.GetAtomWithIdx(i).GetSymbol(), 0.4) * 1.3
506
+ for i in selected_indices])
507
+
508
+ # ハイライト用のデータセットを作成
509
+ highlight_source = pv.PolyData(selected_positions)
510
+ highlight_source['radii'] = selected_radii
511
+
512
+ # 黄色の半透明球でハイライト
513
+ highlight_glyphs = highlight_source.glyph(
514
+ scale='radii',
515
+ geom=pv.Sphere(radius=1.0, theta_resolution=16, phi_resolution=16),
516
+ orient=False
517
+ )
518
+
519
+ self.plotter.add_mesh(
520
+ highlight_glyphs,
521
+ color='yellow',
522
+ opacity=0.3,
523
+ name='selection_highlight'
524
+ )
525
+
526
+ self.plotter.render()
527
+
528
+ def remove_dialog_from_list(self, dialog):
529
+ """ダイアログをアクティブリストから削除"""
530
+ if dialog in self.active_3d_dialogs:
531
+ self.active_3d_dialogs.remove(dialog)