MoleditPy-linux 2.4.1__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.
- moleditpy_linux/__init__.py +17 -0
- moleditpy_linux/__main__.py +29 -0
- moleditpy_linux/main.py +37 -0
- moleditpy_linux/modules/__init__.py +41 -0
- moleditpy_linux/modules/about_dialog.py +104 -0
- moleditpy_linux/modules/align_plane_dialog.py +292 -0
- moleditpy_linux/modules/alignment_dialog.py +272 -0
- moleditpy_linux/modules/analysis_window.py +209 -0
- moleditpy_linux/modules/angle_dialog.py +440 -0
- moleditpy_linux/modules/assets/file_icon.ico +0 -0
- moleditpy_linux/modules/assets/icon.icns +0 -0
- moleditpy_linux/modules/assets/icon.ico +0 -0
- moleditpy_linux/modules/assets/icon.png +0 -0
- moleditpy_linux/modules/atom_item.py +395 -0
- moleditpy_linux/modules/bond_item.py +464 -0
- moleditpy_linux/modules/bond_length_dialog.py +380 -0
- moleditpy_linux/modules/calculation_worker.py +766 -0
- moleditpy_linux/modules/color_settings_dialog.py +321 -0
- moleditpy_linux/modules/constants.py +88 -0
- moleditpy_linux/modules/constrained_optimization_dialog.py +678 -0
- moleditpy_linux/modules/custom_interactor_style.py +749 -0
- moleditpy_linux/modules/custom_qt_interactor.py +102 -0
- moleditpy_linux/modules/dialog3_d_picking_mixin.py +141 -0
- moleditpy_linux/modules/dihedral_dialog.py +443 -0
- moleditpy_linux/modules/main_window.py +850 -0
- moleditpy_linux/modules/main_window_app_state.py +787 -0
- moleditpy_linux/modules/main_window_compute.py +1242 -0
- moleditpy_linux/modules/main_window_dialog_manager.py +460 -0
- moleditpy_linux/modules/main_window_edit_3d.py +536 -0
- moleditpy_linux/modules/main_window_edit_actions.py +1565 -0
- moleditpy_linux/modules/main_window_export.py +917 -0
- moleditpy_linux/modules/main_window_main_init.py +2100 -0
- moleditpy_linux/modules/main_window_molecular_parsers.py +1044 -0
- moleditpy_linux/modules/main_window_project_io.py +434 -0
- moleditpy_linux/modules/main_window_string_importers.py +275 -0
- moleditpy_linux/modules/main_window_ui_manager.py +602 -0
- moleditpy_linux/modules/main_window_view_3d.py +1539 -0
- moleditpy_linux/modules/main_window_view_loaders.py +355 -0
- moleditpy_linux/modules/mirror_dialog.py +122 -0
- moleditpy_linux/modules/molecular_data.py +302 -0
- moleditpy_linux/modules/molecule_scene.py +2000 -0
- moleditpy_linux/modules/move_group_dialog.py +600 -0
- moleditpy_linux/modules/periodic_table_dialog.py +84 -0
- moleditpy_linux/modules/planarize_dialog.py +220 -0
- moleditpy_linux/modules/plugin_interface.py +215 -0
- moleditpy_linux/modules/plugin_manager.py +473 -0
- moleditpy_linux/modules/plugin_manager_window.py +274 -0
- moleditpy_linux/modules/settings_dialog.py +1503 -0
- moleditpy_linux/modules/template_preview_item.py +157 -0
- moleditpy_linux/modules/template_preview_view.py +74 -0
- moleditpy_linux/modules/translation_dialog.py +364 -0
- moleditpy_linux/modules/user_template_dialog.py +692 -0
- moleditpy_linux/modules/zoomable_view.py +129 -0
- moleditpy_linux-2.4.1.dist-info/METADATA +954 -0
- moleditpy_linux-2.4.1.dist-info/RECORD +59 -0
- moleditpy_linux-2.4.1.dist-info/WHEEL +5 -0
- moleditpy_linux-2.4.1.dist-info/entry_points.txt +2 -0
- moleditpy_linux-2.4.1.dist-info/licenses/LICENSE +674 -0
- moleditpy_linux-2.4.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
MoleditPy — A Python-based molecular editing software
|
|
6
|
+
|
|
7
|
+
Author: Hiromichi Yokoyama
|
|
8
|
+
License: GPL-3.0 license
|
|
9
|
+
Repo: https://github.com/HiroYokoyama/python_molecular_editor
|
|
10
|
+
DOI: 10.5281/zenodo.17268532
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
main_window_project_io.py
|
|
15
|
+
MainWindow (main_window.py) から分離されたモジュール
|
|
16
|
+
機能クラス: MainWindowProjectIo
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
import pickle
|
|
21
|
+
import os
|
|
22
|
+
import json
|
|
23
|
+
import traceback
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# RDKit imports (explicit to satisfy flake8 and used features)
|
|
27
|
+
try:
|
|
28
|
+
pass
|
|
29
|
+
except Exception:
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
# PyQt6 Modules
|
|
33
|
+
from PyQt6.QtWidgets import (
|
|
34
|
+
QFileDialog, QMessageBox
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
from PyQt6.QtCore import (
|
|
40
|
+
QTimer
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Use centralized Open Babel availability from package-level __init__
|
|
45
|
+
# Use per-package modules availability (local __init__).
|
|
46
|
+
try:
|
|
47
|
+
from . import OBABEL_AVAILABLE
|
|
48
|
+
except Exception:
|
|
49
|
+
from modules import OBABEL_AVAILABLE
|
|
50
|
+
# Only import pybel on demand — `moleditpy` itself doesn't expose `pybel`.
|
|
51
|
+
if OBABEL_AVAILABLE:
|
|
52
|
+
try:
|
|
53
|
+
from openbabel import pybel
|
|
54
|
+
except Exception:
|
|
55
|
+
# If import fails here, disable OBABEL locally; avoid raising
|
|
56
|
+
pybel = None
|
|
57
|
+
OBABEL_AVAILABLE = False
|
|
58
|
+
print("Warning: openbabel.pybel not available. Open Babel fallback and OBabel-based options will be disabled.")
|
|
59
|
+
else:
|
|
60
|
+
pybel = None
|
|
61
|
+
|
|
62
|
+
# Optional SIP helper: on some PyQt6 builds sip.isdeleted is available and
|
|
63
|
+
# allows safely detecting C++ wrapper objects that have been deleted. Import
|
|
64
|
+
# it once at module import time and expose a small, robust wrapper so callers
|
|
65
|
+
# can avoid re-importing sip repeatedly and so we centralize exception
|
|
66
|
+
# handling (this reduces crash risk during teardown and deletion operations).
|
|
67
|
+
try:
|
|
68
|
+
import sip as _sip # type: ignore
|
|
69
|
+
_sip_isdeleted = getattr(_sip, 'isdeleted', None)
|
|
70
|
+
except Exception:
|
|
71
|
+
_sip = None
|
|
72
|
+
_sip_isdeleted = None
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
# package relative imports (preferred when running as `python -m moleditpy`)
|
|
76
|
+
pass
|
|
77
|
+
except Exception:
|
|
78
|
+
# Fallback to absolute imports for script-style execution
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# --- クラス定義 ---
|
|
83
|
+
class MainWindowProjectIo(object):
|
|
84
|
+
""" main_window.py から分離された機能クラス """
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def save_project(self):
|
|
88
|
+
"""上書き保存(Ctrl+S)- デフォルトでPMEPRJ形式"""
|
|
89
|
+
if not self.data.atoms and not self.current_mol:
|
|
90
|
+
self.statusBar().showMessage("Error: Nothing to save.")
|
|
91
|
+
return
|
|
92
|
+
# 非ネイティブ形式(.mol, .sdf, .xyz など)は上書き保存せず、必ず「名前を付けて保存」にする
|
|
93
|
+
native_exts = ['.pmeprj', '.pmeraw']
|
|
94
|
+
if self.current_file_path and any(self.current_file_path.lower().endswith(ext) for ext in native_exts):
|
|
95
|
+
# 既存のPMEPRJ/PMERAWファイルの場合は上書き保存
|
|
96
|
+
try:
|
|
97
|
+
if self.current_file_path.lower().endswith('.pmeraw'):
|
|
98
|
+
# 既存のPMERAWファイルの場合はPMERAW形式で保存
|
|
99
|
+
save_data = self.get_current_state()
|
|
100
|
+
with open(self.current_file_path, 'wb') as f:
|
|
101
|
+
pickle.dump(save_data, f)
|
|
102
|
+
else:
|
|
103
|
+
# PMEPRJ形式で保存
|
|
104
|
+
json_data = self.create_json_data()
|
|
105
|
+
with open(self.current_file_path, 'w', encoding='utf-8') as f:
|
|
106
|
+
json.dump(json_data, f, indent=2, ensure_ascii=False)
|
|
107
|
+
|
|
108
|
+
# 保存成功時に状態をリセット
|
|
109
|
+
self.has_unsaved_changes = False
|
|
110
|
+
self.update_window_title()
|
|
111
|
+
|
|
112
|
+
self.statusBar().showMessage(f"Project saved to {self.current_file_path}")
|
|
113
|
+
|
|
114
|
+
except (OSError, IOError) as e:
|
|
115
|
+
self.statusBar().showMessage(f"File I/O error: {e}")
|
|
116
|
+
except (pickle.PicklingError, TypeError, ValueError) as e:
|
|
117
|
+
self.statusBar().showMessage(f"Data serialization error: {e}")
|
|
118
|
+
except Exception as e:
|
|
119
|
+
self.statusBar().showMessage(f"Error saving project file: {e}")
|
|
120
|
+
|
|
121
|
+
traceback.print_exc()
|
|
122
|
+
else:
|
|
123
|
+
# MOL/SDF/XYZなどは上書き保存せず、必ず「名前を付けて保存」にする
|
|
124
|
+
self.save_project_as()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def save_project_as(self):
|
|
129
|
+
"""名前を付けて保存(Ctrl+Shift+S)- デフォルトでPMEPRJ形式"""
|
|
130
|
+
if not self.data.atoms and not self.current_mol:
|
|
131
|
+
self.statusBar().showMessage("Error: Nothing to save.")
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
# Determine a sensible default filename based on current file (strip extension)
|
|
136
|
+
default_name = "untitled"
|
|
137
|
+
try:
|
|
138
|
+
if self.current_file_path:
|
|
139
|
+
base = os.path.basename(self.current_file_path)
|
|
140
|
+
default_name = os.path.splitext(base)[0]
|
|
141
|
+
except Exception:
|
|
142
|
+
default_name = "untitled"
|
|
143
|
+
|
|
144
|
+
# Prefer the directory of the currently opened file as default
|
|
145
|
+
default_path = default_name
|
|
146
|
+
try:
|
|
147
|
+
if self.current_file_path:
|
|
148
|
+
default_path = os.path.join(os.path.dirname(self.current_file_path), default_name)
|
|
149
|
+
except Exception:
|
|
150
|
+
default_path = default_name
|
|
151
|
+
|
|
152
|
+
file_path, _ = QFileDialog.getSaveFileName(
|
|
153
|
+
self, "Save Project As", default_path,
|
|
154
|
+
"PME Project Files (*.pmeprj);;All Files (*)",
|
|
155
|
+
)
|
|
156
|
+
if not file_path:
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
if not file_path.lower().endswith('.pmeprj'):
|
|
160
|
+
file_path += '.pmeprj'
|
|
161
|
+
|
|
162
|
+
# JSONデータを保存
|
|
163
|
+
json_data = self.create_json_data()
|
|
164
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
165
|
+
json.dump(json_data, f, indent=2, ensure_ascii=False)
|
|
166
|
+
|
|
167
|
+
# 保存成功時に状態をリセット
|
|
168
|
+
self.has_unsaved_changes = False
|
|
169
|
+
# Replace current file with the newly saved file so subsequent saves go to this path
|
|
170
|
+
self.current_file_path = file_path
|
|
171
|
+
self.update_window_title()
|
|
172
|
+
# Mark this state as the last saved state for undo tracking
|
|
173
|
+
try:
|
|
174
|
+
self._saved_state = copy.deepcopy(self.get_current_state())
|
|
175
|
+
except Exception:
|
|
176
|
+
pass
|
|
177
|
+
|
|
178
|
+
self.statusBar().showMessage(f"Project saved to {file_path}")
|
|
179
|
+
|
|
180
|
+
except (OSError, IOError) as e:
|
|
181
|
+
self.statusBar().showMessage(f"File I/O error: {e}")
|
|
182
|
+
except pickle.PicklingError as e:
|
|
183
|
+
self.statusBar().showMessage(f"Data serialization error: {e}")
|
|
184
|
+
except Exception as e:
|
|
185
|
+
self.statusBar().showMessage(f"Error saving project file: {e}")
|
|
186
|
+
|
|
187
|
+
traceback.print_exc()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def save_raw_data(self):
|
|
192
|
+
if not self.data.atoms and not self.current_mol:
|
|
193
|
+
self.statusBar().showMessage("Error: Nothing to save.")
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
save_data = self.get_current_state()
|
|
198
|
+
# default filename based on current file
|
|
199
|
+
default_name = "untitled"
|
|
200
|
+
try:
|
|
201
|
+
if self.current_file_path:
|
|
202
|
+
base = os.path.basename(self.current_file_path)
|
|
203
|
+
default_name = os.path.splitext(base)[0]
|
|
204
|
+
except Exception:
|
|
205
|
+
default_name = "untitled"
|
|
206
|
+
|
|
207
|
+
# prefer same directory as current file when available
|
|
208
|
+
default_path = default_name
|
|
209
|
+
try:
|
|
210
|
+
if self.current_file_path:
|
|
211
|
+
default_path = os.path.join(os.path.dirname(self.current_file_path), default_name)
|
|
212
|
+
except Exception:
|
|
213
|
+
default_path = default_name
|
|
214
|
+
|
|
215
|
+
file_path, _ = QFileDialog.getSaveFileName(self, "Save Project File", default_path, "Project Files (*.pmeraw);;All Files (*)")
|
|
216
|
+
if not file_path:
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
if not file_path.lower().endswith('.pmeraw'):
|
|
220
|
+
file_path += '.pmeraw'
|
|
221
|
+
|
|
222
|
+
with open(file_path, 'wb') as f:
|
|
223
|
+
pickle.dump(save_data, f)
|
|
224
|
+
|
|
225
|
+
# 保存成功時に状態をリセット
|
|
226
|
+
self.has_unsaved_changes = False
|
|
227
|
+
# Update current file to the newly saved raw file
|
|
228
|
+
self.current_file_path = file_path
|
|
229
|
+
self.update_window_title()
|
|
230
|
+
try:
|
|
231
|
+
self._saved_state = copy.deepcopy(self.get_current_state())
|
|
232
|
+
except Exception:
|
|
233
|
+
pass
|
|
234
|
+
|
|
235
|
+
self.statusBar().showMessage(f"Project saved to {file_path}")
|
|
236
|
+
|
|
237
|
+
except (OSError, IOError) as e:
|
|
238
|
+
self.statusBar().showMessage(f"File I/O error: {e}")
|
|
239
|
+
except pickle.PicklingError as e:
|
|
240
|
+
self.statusBar().showMessage(f"Data serialization error: {e}")
|
|
241
|
+
except Exception as e:
|
|
242
|
+
self.statusBar().showMessage(f"Error saving project file: {e}")
|
|
243
|
+
|
|
244
|
+
traceback.print_exc()
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def load_raw_data(self, file_path=None):
|
|
250
|
+
if not file_path:
|
|
251
|
+
file_path, _ = QFileDialog.getOpenFileName(self, "Open Project File", "", "Project Files (*.pmeraw);;All Files (*)")
|
|
252
|
+
if not file_path:
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
with open(file_path, 'rb') as f:
|
|
257
|
+
loaded_data = pickle.load(f)
|
|
258
|
+
self.restore_ui_for_editing()
|
|
259
|
+
self.set_state_from_data(loaded_data)
|
|
260
|
+
|
|
261
|
+
# ファイル読み込み時に状態をリセット
|
|
262
|
+
self.reset_undo_stack()
|
|
263
|
+
self.has_unsaved_changes = False
|
|
264
|
+
self.current_file_path = file_path
|
|
265
|
+
self.update_window_title()
|
|
266
|
+
try:
|
|
267
|
+
self._saved_state= copy.deepcopy(self.et_current_state())
|
|
268
|
+
except Exception:
|
|
269
|
+
pass
|
|
270
|
+
|
|
271
|
+
self.statusBar.showMessage(f"Project loaded from {file_path}")
|
|
272
|
+
|
|
273
|
+
QTimer.singleShot(0, self.fit_to_view)
|
|
274
|
+
|
|
275
|
+
except FileNotFoundError:
|
|
276
|
+
self.statusBar().showMessage(f"File not found: {file_path}")
|
|
277
|
+
except (OSError, IOError) as e:
|
|
278
|
+
self.statusBar().showMessage(f"File I/O error: {e}")
|
|
279
|
+
except pickle.UnpicklingError as e:
|
|
280
|
+
self.statusBar().showMessage(f"Invalid project file format: {e}")
|
|
281
|
+
except Exception as e:
|
|
282
|
+
self.statusBar().showMessage(f"Error loading project file: {e}")
|
|
283
|
+
|
|
284
|
+
traceback.print_exc()
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def save_as_json(self):
|
|
289
|
+
"""PMEJSONファイル形式で保存 (3D MOL情報含む)"""
|
|
290
|
+
if not self.data.atoms and not self.current_mol:
|
|
291
|
+
self.statusBar().showMessage("Error: Nothing to save.")
|
|
292
|
+
return
|
|
293
|
+
|
|
294
|
+
try:
|
|
295
|
+
# default filename based on current file
|
|
296
|
+
default_name = "untitled"
|
|
297
|
+
try:
|
|
298
|
+
if self.current_file_path:
|
|
299
|
+
base = os.path.basename(self.current_file_path)
|
|
300
|
+
default_name = os.path.splitext(base)[0]
|
|
301
|
+
except Exception:
|
|
302
|
+
default_name = "untitled"
|
|
303
|
+
|
|
304
|
+
# prefer same directory as current file when available
|
|
305
|
+
default_path = default_name
|
|
306
|
+
try:
|
|
307
|
+
if self.current_file_path:
|
|
308
|
+
default_path = os.path.join(os.path.dirname(self.current_file_path), default_name)
|
|
309
|
+
except Exception:
|
|
310
|
+
default_path = default_name
|
|
311
|
+
|
|
312
|
+
file_path, _ = QFileDialog.getSaveFileName(
|
|
313
|
+
self, "Save as PME Project", default_path,
|
|
314
|
+
"PME Project Files (*.pmeprj);;All Files (*)",
|
|
315
|
+
)
|
|
316
|
+
if not file_path:
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
if not file_path.lower().endswith('.pmeprj'):
|
|
320
|
+
file_path += '.pmeprj'
|
|
321
|
+
|
|
322
|
+
# JSONデータを作成
|
|
323
|
+
json_data = self.create_json_data()
|
|
324
|
+
|
|
325
|
+
# JSON形式で保存(美しい整形付き)
|
|
326
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
327
|
+
json.dump(json_data, f, indent=2, ensure_ascii=False)
|
|
328
|
+
|
|
329
|
+
# 保存成功時に状態をリセット
|
|
330
|
+
self.has_unsaved_changes = False
|
|
331
|
+
# Replace current file with the newly saved PME Project
|
|
332
|
+
self.current_file_path = file_path
|
|
333
|
+
self.update_window_title()
|
|
334
|
+
|
|
335
|
+
self.statusBar().showMessage(f"PME Project saved to {file_path}")
|
|
336
|
+
|
|
337
|
+
except (OSError, IOError) as e:
|
|
338
|
+
self.statusBar().showMessage(f"File I/O error: {e}")
|
|
339
|
+
except (TypeError, ValueError) as e:
|
|
340
|
+
self.statusBar().showMessage(f"JSON serialization error: {e}")
|
|
341
|
+
except Exception as e:
|
|
342
|
+
self.statusBar().showMessage(f"Error saving PME Project file: {e}")
|
|
343
|
+
|
|
344
|
+
traceback.print_exc()
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def load_json_data(self, file_path=None):
|
|
349
|
+
"""PME Projectファイル形式を読み込み"""
|
|
350
|
+
if not file_path:
|
|
351
|
+
file_path, _ = QFileDialog.getOpenFileName(
|
|
352
|
+
self, "Open PME Project File", "",
|
|
353
|
+
"PME Project Files (*.pmeprj);;All Files (*)",
|
|
354
|
+
)
|
|
355
|
+
if not file_path:
|
|
356
|
+
return
|
|
357
|
+
|
|
358
|
+
try:
|
|
359
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
360
|
+
json_data = json.load(f)
|
|
361
|
+
|
|
362
|
+
# フォーマット検証
|
|
363
|
+
if json_data.get("format") != "PME Project":
|
|
364
|
+
QMessageBox.warning(
|
|
365
|
+
self, "Invalid Format",
|
|
366
|
+
"This file is not a valid PME Project format."
|
|
367
|
+
)
|
|
368
|
+
return
|
|
369
|
+
|
|
370
|
+
# バージョン確認
|
|
371
|
+
file_version = json_data.get("version", "1.0")
|
|
372
|
+
if file_version != "1.0":
|
|
373
|
+
QMessageBox.information(
|
|
374
|
+
self, "Version Notice",
|
|
375
|
+
f"This file was created with PME Project version {file_version}.\n"
|
|
376
|
+
"Loading will be attempted but some features may not work correctly."
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
self.restore_ui_for_editing()
|
|
380
|
+
self.load_from_json_data(json_data)
|
|
381
|
+
# ファイル読み込み時に状態をリセット
|
|
382
|
+
self.reset_undo_stack()
|
|
383
|
+
self.has_unsaved_changes = False
|
|
384
|
+
self.current_file_path = file_path
|
|
385
|
+
self.update_window_title()
|
|
386
|
+
|
|
387
|
+
self.statusBar().showMessage(f"PME Project loaded from {file_path}")
|
|
388
|
+
|
|
389
|
+
QTimer.singleShot(0, self.fit_to_view)
|
|
390
|
+
|
|
391
|
+
except FileNotFoundError:
|
|
392
|
+
self.statusBar().showMessage(f"File not found: {file_path}")
|
|
393
|
+
except json.JSONDecodeError as e:
|
|
394
|
+
self.statusBar().showMessage(f"Invalid JSON format: {e}")
|
|
395
|
+
except (OSError, IOError) as e:
|
|
396
|
+
self.statusBar().showMessage(f"File I/O error: {e}")
|
|
397
|
+
except Exception as e:
|
|
398
|
+
self.statusBar().showMessage(f"Error loading PME Project file: {e}")
|
|
399
|
+
|
|
400
|
+
traceback.print_exc()
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def open_project_file(self, file_path=None):
|
|
405
|
+
"""プロジェクトファイルを開く(.pmeprjと.pmerawの両方に対応)"""
|
|
406
|
+
# Check for unsaved changes before opening a new project file.
|
|
407
|
+
# Previously this function opened .pmeprj/.pmeraw without prompting the
|
|
408
|
+
# user to save current unsaved work. Ensure we honor the global
|
|
409
|
+
# unsaved-change check like other loaders (SMILES/MOL/etc.).
|
|
410
|
+
if not self.check_unsaved_changes():
|
|
411
|
+
return
|
|
412
|
+
if not file_path:
|
|
413
|
+
file_path, _ = QFileDialog.getOpenFileName(
|
|
414
|
+
self, "Open Project File", "",
|
|
415
|
+
"PME Project Files (*.pmeprj);;PME Raw Files (*.pmeraw);;All Files (*)",
|
|
416
|
+
)
|
|
417
|
+
if not file_path:
|
|
418
|
+
return
|
|
419
|
+
|
|
420
|
+
# 拡張子に応じて適切な読み込み関数を呼び出し
|
|
421
|
+
if file_path.lower().endswith('.pmeprj'):
|
|
422
|
+
self.load_json_data(file_path)
|
|
423
|
+
elif file_path.lower().endswith('.pmeraw'):
|
|
424
|
+
self.load_raw_data(file_path)
|
|
425
|
+
else:
|
|
426
|
+
# 拡張子不明の場合はJSONとして試行
|
|
427
|
+
try:
|
|
428
|
+
self.load_json_data(file_path)
|
|
429
|
+
except Exception:
|
|
430
|
+
try:
|
|
431
|
+
self.load_raw_data(file_path)
|
|
432
|
+
except Exception:
|
|
433
|
+
self.statusBar().showMessage("Error: Unable to determine file format.")
|
|
434
|
+
|