MoleditPy 1.18.0__py3-none-any.whl → 2.0.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.
- moleditpy/modules/align_plane_dialog.py +1 -1
- moleditpy/modules/alignment_dialog.py +1 -1
- moleditpy/modules/angle_dialog.py +2 -2
- moleditpy/modules/bond_length_dialog.py +2 -2
- moleditpy/modules/constants.py +1 -1
- moleditpy/modules/constrained_optimization_dialog.py +2 -2
- moleditpy/modules/dihedral_dialog.py +1 -1
- moleditpy/modules/main_window.py +4 -0
- moleditpy/modules/main_window_app_state.py +1 -1
- moleditpy/modules/main_window_edit_3d.py +7 -7
- moleditpy/modules/main_window_export.py +106 -49
- moleditpy/modules/main_window_main_init.py +68 -4
- moleditpy/modules/main_window_molecular_parsers.py +4 -3
- moleditpy/modules/main_window_project_io.py +2 -2
- moleditpy/modules/main_window_view_3d.py +13 -12
- moleditpy/modules/main_window_view_loaders.py +1 -1
- moleditpy/modules/molecule_scene.py +41 -13
- moleditpy/modules/planarize_dialog.py +1 -1
- moleditpy/modules/plugin_manager.py +85 -0
- moleditpy/modules/settings_dialog.py +5 -23
- moleditpy/modules/user_template_dialog.py +9 -8
- {moleditpy-1.18.0.dist-info → moleditpy-2.0.0.dist-info}/METADATA +3 -2
- {moleditpy-1.18.0.dist-info → moleditpy-2.0.0.dist-info}/RECORD +27 -26
- {moleditpy-1.18.0.dist-info → moleditpy-2.0.0.dist-info}/WHEEL +0 -0
- {moleditpy-1.18.0.dist-info → moleditpy-2.0.0.dist-info}/entry_points.txt +0 -0
- {moleditpy-1.18.0.dist-info → moleditpy-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {moleditpy-1.18.0.dist-info → moleditpy-2.0.0.dist-info}/top_level.txt +0 -0
|
@@ -202,7 +202,7 @@ class AngleDialog(Dialog3DPickingMixin, QDialog):
|
|
|
202
202
|
for label_actor in self.selection_labels:
|
|
203
203
|
try:
|
|
204
204
|
self.main_window.plotter.remove_actor(label_actor)
|
|
205
|
-
except:
|
|
205
|
+
except Exception:
|
|
206
206
|
pass
|
|
207
207
|
self.selection_labels = []
|
|
208
208
|
|
|
@@ -212,7 +212,7 @@ class AngleDialog(Dialog3DPickingMixin, QDialog):
|
|
|
212
212
|
for label_actor in self.selection_labels:
|
|
213
213
|
try:
|
|
214
214
|
self.main_window.plotter.remove_actor(label_actor)
|
|
215
|
-
except:
|
|
215
|
+
except Exception:
|
|
216
216
|
pass
|
|
217
217
|
self.selection_labels = []
|
|
218
218
|
|
|
@@ -190,7 +190,7 @@ class BondLengthDialog(Dialog3DPickingMixin, QDialog):
|
|
|
190
190
|
for label_actor in self.selection_labels:
|
|
191
191
|
try:
|
|
192
192
|
self.main_window.plotter.remove_actor(label_actor)
|
|
193
|
-
except:
|
|
193
|
+
except Exception:
|
|
194
194
|
pass
|
|
195
195
|
self.selection_labels = []
|
|
196
196
|
|
|
@@ -200,7 +200,7 @@ class BondLengthDialog(Dialog3DPickingMixin, QDialog):
|
|
|
200
200
|
for label_actor in self.selection_labels:
|
|
201
201
|
try:
|
|
202
202
|
self.main_window.plotter.remove_actor(label_actor)
|
|
203
|
-
except:
|
|
203
|
+
except Exception:
|
|
204
204
|
pass
|
|
205
205
|
self.selection_labels = []
|
|
206
206
|
|
moleditpy/modules/constants.py
CHANGED
|
@@ -394,7 +394,7 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
|
|
|
394
394
|
for label_actor in self.constraint_labels:
|
|
395
395
|
try:
|
|
396
396
|
self.main_window.plotter.remove_actor(label_actor)
|
|
397
|
-
except:
|
|
397
|
+
except Exception:
|
|
398
398
|
pass
|
|
399
399
|
self.constraint_labels = []
|
|
400
400
|
|
|
@@ -595,7 +595,7 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
|
|
|
595
595
|
for label_actor in self.selection_labels:
|
|
596
596
|
try:
|
|
597
597
|
self.main_window.plotter.remove_actor(label_actor)
|
|
598
|
-
except:
|
|
598
|
+
except Exception:
|
|
599
599
|
pass
|
|
600
600
|
self.selection_labels = []
|
|
601
601
|
|
moleditpy/modules/main_window.py
CHANGED
|
@@ -211,6 +211,10 @@ class MainWindow(QMainWindow):
|
|
|
211
211
|
# --- MOVED TO main_window_main_init.py ---
|
|
212
212
|
return self.main_window_main_init.init_menu_bar()
|
|
213
213
|
|
|
214
|
+
def update_plugin_menu(self, plugin_menu):
|
|
215
|
+
# --- MOVED TO main_window_main_init.py ---
|
|
216
|
+
return self.main_window_main_init.update_plugin_menu(plugin_menu)
|
|
217
|
+
|
|
214
218
|
def init_worker_thread(self):
|
|
215
219
|
# --- MOVED TO main_window_main_init.py ---
|
|
216
220
|
return self.main_window_main_init.init_worker_thread()
|
|
@@ -574,7 +574,7 @@ class MainWindowAppState(object):
|
|
|
574
574
|
inchi_key = Chem.MolToInchiKey(self.current_mol)
|
|
575
575
|
json_data["identifiers"]["inchi"] = inchi
|
|
576
576
|
json_data["identifiers"]["inchi_key"] = inchi_key
|
|
577
|
-
except:
|
|
577
|
+
except Exception:
|
|
578
578
|
pass # InChI生成に失敗した場合は無視
|
|
579
579
|
|
|
580
580
|
except Exception as e:
|
|
@@ -119,7 +119,7 @@ class MainWindowEdit3d(object):
|
|
|
119
119
|
for dialog in dialogs_to_close:
|
|
120
120
|
try:
|
|
121
121
|
dialog.close()
|
|
122
|
-
except:
|
|
122
|
+
except Exception:
|
|
123
123
|
pass
|
|
124
124
|
self.active_3d_dialogs.clear()
|
|
125
125
|
|
|
@@ -169,7 +169,7 @@ class MainWindowEdit3d(object):
|
|
|
169
169
|
try:
|
|
170
170
|
# 既存の測定ラベルを削除
|
|
171
171
|
self.plotter.remove_actor('measurement_labels')
|
|
172
|
-
except:
|
|
172
|
+
except Exception:
|
|
173
173
|
pass
|
|
174
174
|
|
|
175
175
|
if not self.measurement_labels or not self.current_mol:
|
|
@@ -208,7 +208,7 @@ class MainWindowEdit3d(object):
|
|
|
208
208
|
self.measurement_labels.clear()
|
|
209
209
|
try:
|
|
210
210
|
self.plotter.remove_actor('measurement_labels')
|
|
211
|
-
except:
|
|
211
|
+
except Exception:
|
|
212
212
|
pass
|
|
213
213
|
|
|
214
214
|
# 2Dビューの測定ラベルも削除
|
|
@@ -219,7 +219,7 @@ class MainWindowEdit3d(object):
|
|
|
219
219
|
try:
|
|
220
220
|
self.plotter.remove_actor(self.measurement_text_actor)
|
|
221
221
|
self.measurement_text_actor = None
|
|
222
|
-
except:
|
|
222
|
+
except Exception:
|
|
223
223
|
pass
|
|
224
224
|
|
|
225
225
|
self.plotter.render()
|
|
@@ -434,7 +434,7 @@ class MainWindowEdit3d(object):
|
|
|
434
434
|
if self.measurement_text_actor:
|
|
435
435
|
try:
|
|
436
436
|
self.plotter.remove_actor(self.measurement_text_actor)
|
|
437
|
-
except:
|
|
437
|
+
except Exception:
|
|
438
438
|
pass
|
|
439
439
|
|
|
440
440
|
if not measurement_lines:
|
|
@@ -453,7 +453,7 @@ class MainWindowEdit3d(object):
|
|
|
453
453
|
text_color = 'black' if luminance > 128 else 'white'
|
|
454
454
|
else:
|
|
455
455
|
text_color = 'white'
|
|
456
|
-
except:
|
|
456
|
+
except Exception:
|
|
457
457
|
text_color = 'white'
|
|
458
458
|
|
|
459
459
|
# 左上に表示(小さな等幅フォント)
|
|
@@ -496,7 +496,7 @@ class MainWindowEdit3d(object):
|
|
|
496
496
|
try:
|
|
497
497
|
# 既存の選択ハイライトを削除
|
|
498
498
|
self.plotter.remove_actor('selection_highlight')
|
|
499
|
-
except:
|
|
499
|
+
except Exception:
|
|
500
500
|
pass
|
|
501
501
|
|
|
502
502
|
if not self.selected_atoms_3d or not self.current_mol:
|
|
@@ -178,11 +178,6 @@ class MainWindowExport(object):
|
|
|
178
178
|
except Exception as e:
|
|
179
179
|
self.statusBar().showMessage(f"Error exporting OBJ/MTL: {e}")
|
|
180
180
|
|
|
181
|
-
return meshes_with_colors
|
|
182
|
-
|
|
183
|
-
except Exception:
|
|
184
|
-
return []
|
|
185
|
-
|
|
186
181
|
|
|
187
182
|
|
|
188
183
|
def create_multi_material_obj(self, meshes_with_colors, obj_path, mtl_path):
|
|
@@ -199,17 +194,17 @@ class MainWindowExport(object):
|
|
|
199
194
|
material_name = f"material_{i}_{mesh_data['name'].replace(' ', '_')}"
|
|
200
195
|
|
|
201
196
|
mtl_file.write(f"newmtl {material_name}\n")
|
|
202
|
-
mtl_file.write("Ka 0.2 0.2 0.2\n") # Ambient
|
|
197
|
+
mtl_file.write(f"Ka 0.2 0.2 0.2\n") # Ambient
|
|
203
198
|
mtl_file.write(f"Kd {color[0]/255.0:.3f} {color[1]/255.0:.3f} {color[2]/255.0:.3f}\n") # Diffuse
|
|
204
|
-
mtl_file.write("Ks 0.5 0.5 0.5\n") # Specular
|
|
205
|
-
mtl_file.write("Ns 32.0\n") # Specular exponent
|
|
206
|
-
mtl_file.write("illum 2\n") # Illumination model
|
|
207
|
-
mtl_file.write("\n")
|
|
199
|
+
mtl_file.write(f"Ks 0.5 0.5 0.5\n") # Specular
|
|
200
|
+
mtl_file.write(f"Ns 32.0\n") # Specular exponent
|
|
201
|
+
mtl_file.write(f"illum 2\n") # Illumination model
|
|
202
|
+
mtl_file.write(f"\n")
|
|
208
203
|
|
|
209
204
|
# OBJファイルを作成
|
|
210
205
|
with open(obj_path, 'w') as obj_file:
|
|
211
|
-
obj_file.write("# OBJ file with multiple materials\n")
|
|
212
|
-
obj_file.write("# Generated with individual object colors\n")
|
|
206
|
+
obj_file.write(f"# OBJ file with multiple materials\n")
|
|
207
|
+
obj_file.write(f"# Generated with individual object colors\n")
|
|
213
208
|
obj_file.write(f"mtllib {os.path.basename(mtl_path)}\n\n")
|
|
214
209
|
|
|
215
210
|
vertex_offset = 1 # OBJファイルの頂点インデックスは1から始まる
|
|
@@ -229,6 +224,7 @@ class MainWindowExport(object):
|
|
|
229
224
|
obj_file.write(f"v {point[0]:.6f} {point[1]:.6f} {point[2]:.6f}\n")
|
|
230
225
|
|
|
231
226
|
# 面を書き込み
|
|
227
|
+
faces_written = 0
|
|
232
228
|
for j in range(mesh.n_cells):
|
|
233
229
|
cell = mesh.get_cell(j)
|
|
234
230
|
if cell.type == 5: # VTK_TRIANGLE
|
|
@@ -237,6 +233,25 @@ class MainWindowExport(object):
|
|
|
237
233
|
v2 = points_in_cell[1] + vertex_offset
|
|
238
234
|
v3 = points_in_cell[2] + vertex_offset
|
|
239
235
|
obj_file.write(f"f {v1} {v2} {v3}\n")
|
|
236
|
+
faces_written += 1
|
|
237
|
+
elif cell.type == 6: # VTK_TRIANGLE_STRIP
|
|
238
|
+
# Triangle strips share vertices between adjacent triangles
|
|
239
|
+
# For n points, we get (n-2) triangles
|
|
240
|
+
points_in_cell = cell.point_ids
|
|
241
|
+
n_points = len(points_in_cell)
|
|
242
|
+
for k in range(n_points - 2):
|
|
243
|
+
if k % 2 == 0:
|
|
244
|
+
# Even triangles: use points k, k+1, k+2
|
|
245
|
+
v1 = points_in_cell[k] + vertex_offset
|
|
246
|
+
v2 = points_in_cell[k+1] + vertex_offset
|
|
247
|
+
v3 = points_in_cell[k+2] + vertex_offset
|
|
248
|
+
else:
|
|
249
|
+
# Odd triangles: reverse winding to maintain consistent orientation
|
|
250
|
+
v1 = points_in_cell[k+1] + vertex_offset
|
|
251
|
+
v2 = points_in_cell[k] + vertex_offset
|
|
252
|
+
v3 = points_in_cell[k+2] + vertex_offset
|
|
253
|
+
obj_file.write(f"f {v1} {v2} {v3}\n")
|
|
254
|
+
faces_written += 1
|
|
240
255
|
elif cell.type == 9: # VTK_QUAD
|
|
241
256
|
points_in_cell = cell.point_ids
|
|
242
257
|
v1 = points_in_cell[0] + vertex_offset
|
|
@@ -244,10 +259,12 @@ class MainWindowExport(object):
|
|
|
244
259
|
v3 = points_in_cell[2] + vertex_offset
|
|
245
260
|
v4 = points_in_cell[3] + vertex_offset
|
|
246
261
|
obj_file.write(f"f {v1} {v2} {v3} {v4}\n")
|
|
262
|
+
faces_written += 1
|
|
247
263
|
|
|
248
|
-
vertex_offset += mesh.n_points
|
|
249
|
-
obj_file.write("\n")
|
|
250
264
|
|
|
265
|
+
vertex_offset += mesh.n_points
|
|
266
|
+
obj_file.write(f"\n")
|
|
267
|
+
|
|
251
268
|
except Exception as e:
|
|
252
269
|
raise Exception(f"Failed to create multi-material OBJ: {e}")
|
|
253
270
|
|
|
@@ -310,24 +327,25 @@ class MainWindowExport(object):
|
|
|
310
327
|
# VTKアクターからポリデータを取得する複数の方法を試行
|
|
311
328
|
mesh = None
|
|
312
329
|
|
|
313
|
-
# 方法1: mapperのinputから取得
|
|
330
|
+
# 方法1: mapperのinputから取得 (Improved)
|
|
331
|
+
mapper = None
|
|
314
332
|
if hasattr(actor, 'mapper') and actor.mapper is not None:
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
333
|
+
mapper = actor.mapper
|
|
334
|
+
elif hasattr(actor, 'GetMapper'):
|
|
335
|
+
mapper = actor.GetMapper()
|
|
336
|
+
|
|
337
|
+
if mapper is not None:
|
|
338
|
+
if hasattr(mapper, 'input') and mapper.input is not None:
|
|
339
|
+
mesh = mapper.input
|
|
340
|
+
elif hasattr(mapper, 'GetInput') and mapper.GetInput() is not None:
|
|
341
|
+
mesh = mapper.GetInput()
|
|
342
|
+
elif hasattr(mapper, 'GetInputAsDataSet'):
|
|
343
|
+
mesh = mapper.GetInputAsDataSet()
|
|
319
344
|
|
|
320
345
|
# 方法2: PyVistaプロッターの内部データから取得
|
|
321
346
|
if mesh is None and actor_name in self.plotter.mesh:
|
|
322
347
|
mesh = self.plotter.mesh[actor_name]
|
|
323
348
|
|
|
324
|
-
# 方法3: PyVistaのメッシュデータベースから検索
|
|
325
|
-
if mesh is None:
|
|
326
|
-
for mesh_name, mesh_data in self.plotter.mesh.items():
|
|
327
|
-
if mesh_data is not None and mesh_data.n_points > 0:
|
|
328
|
-
mesh = mesh_data
|
|
329
|
-
break
|
|
330
|
-
|
|
331
349
|
if mesh is not None and hasattr(mesh, 'n_points') and mesh.n_points > 0:
|
|
332
350
|
# PyVistaメッシュに変換(必要な場合)
|
|
333
351
|
if not isinstance(mesh, pv.PolyData):
|
|
@@ -391,23 +409,26 @@ class MainWindowExport(object):
|
|
|
391
409
|
# VTKアクターからポリデータを取得する複数の方法を試行
|
|
392
410
|
mesh = None
|
|
393
411
|
|
|
394
|
-
# 方法1: mapperのinputから取得
|
|
412
|
+
# 方法1: mapperのinputから取得 (Improved)
|
|
413
|
+
mapper = None
|
|
395
414
|
if hasattr(actor, 'mapper') and actor.mapper is not None:
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
415
|
+
mapper = actor.mapper
|
|
416
|
+
elif hasattr(actor, 'GetMapper'):
|
|
417
|
+
mapper = actor.GetMapper()
|
|
418
|
+
|
|
419
|
+
if mapper is not None:
|
|
420
|
+
if hasattr(mapper, 'input') and mapper.input is not None:
|
|
421
|
+
mesh = mapper.input
|
|
422
|
+
elif hasattr(mapper, 'GetInput') and mapper.GetInput() is not None:
|
|
423
|
+
mesh = mapper.GetInput()
|
|
424
|
+
elif hasattr(mapper, 'GetInputAsDataSet'):
|
|
425
|
+
mesh = mapper.GetInputAsDataSet()
|
|
400
426
|
|
|
401
427
|
# 方法2: PyVistaプロッターの内部データから取得
|
|
402
428
|
if mesh is None and actor_name in self.plotter.mesh:
|
|
403
429
|
mesh = self.plotter.mesh[actor_name]
|
|
404
430
|
|
|
405
|
-
# 方法3:
|
|
406
|
-
if mesh is None:
|
|
407
|
-
for mesh_name, mesh_data in self.plotter.mesh.items():
|
|
408
|
-
if mesh_data is not None and mesh_data.n_points > 0:
|
|
409
|
-
mesh = mesh_data
|
|
410
|
-
break
|
|
431
|
+
# 方法3: Removed unsafe fallback
|
|
411
432
|
|
|
412
433
|
if mesh is not None and hasattr(mesh, 'n_points') and mesh.n_points > 0:
|
|
413
434
|
# PyVistaメッシュに変換(必要な場合)
|
|
@@ -447,17 +468,26 @@ class MainWindowExport(object):
|
|
|
447
468
|
actors = renderer.actors
|
|
448
469
|
|
|
449
470
|
actor_count = 0
|
|
471
|
+
|
|
450
472
|
for actor_name, actor in actors.items():
|
|
451
473
|
try:
|
|
452
474
|
# VTKアクターからポリデータを取得
|
|
453
475
|
mesh = None
|
|
454
476
|
|
|
455
|
-
# 方法1: mapperのinputから取得
|
|
477
|
+
# 方法1: mapperのinputから取得 (Improved)
|
|
478
|
+
mapper = None
|
|
456
479
|
if hasattr(actor, 'mapper') and actor.mapper is not None:
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
480
|
+
mapper = actor.mapper
|
|
481
|
+
elif hasattr(actor, 'GetMapper'):
|
|
482
|
+
mapper = actor.GetMapper()
|
|
483
|
+
|
|
484
|
+
if mapper is not None:
|
|
485
|
+
if hasattr(mapper, 'input') and mapper.input is not None:
|
|
486
|
+
mesh = mapper.input
|
|
487
|
+
elif hasattr(mapper, 'GetInput') and mapper.GetInput() is not None:
|
|
488
|
+
mesh = mapper.GetInput()
|
|
489
|
+
elif hasattr(mapper, 'GetInputAsDataSet'):
|
|
490
|
+
mesh = mapper.GetInputAsDataSet()
|
|
461
491
|
|
|
462
492
|
# 方法2: PyVistaプロッターの内部データから取得
|
|
463
493
|
if mesh is None and actor_name in self.plotter.mesh:
|
|
@@ -484,7 +514,7 @@ class MainWindowExport(object):
|
|
|
484
514
|
if prop is not None:
|
|
485
515
|
vtk_color = prop.GetColor()
|
|
486
516
|
color = [int(c * 255) for c in vtk_color]
|
|
487
|
-
except:
|
|
517
|
+
except Exception:
|
|
488
518
|
# 色取得に失敗した場合はデフォルト色をそのまま使用
|
|
489
519
|
pass
|
|
490
520
|
|
|
@@ -513,6 +543,16 @@ class MainWindowExport(object):
|
|
|
513
543
|
# 単一の colors 配列があればそれを使う
|
|
514
544
|
elif 'colors' in pd:
|
|
515
545
|
colors = np.asarray(pd['colors'])
|
|
546
|
+
|
|
547
|
+
# cell_dataのcolorsも確認(Tubeフィルタなどはcell_dataに色を持つ場合がある)
|
|
548
|
+
if colors is None and 'colors' in mesh_copy.cell_data:
|
|
549
|
+
try:
|
|
550
|
+
# cell_dataをpoint_dataに変換
|
|
551
|
+
temp_mesh = mesh_copy.cell_data_to_point_data()
|
|
552
|
+
if 'colors' in temp_mesh.point_data:
|
|
553
|
+
colors = np.asarray(temp_mesh.point_data['colors'])
|
|
554
|
+
except Exception:
|
|
555
|
+
pass
|
|
516
556
|
|
|
517
557
|
if colors is not None and colors.size > 0:
|
|
518
558
|
# 整数に変換。colors が 0-1 の float の場合は 255 倍して正規化する。
|
|
@@ -539,18 +579,26 @@ class MainWindowExport(object):
|
|
|
539
579
|
|
|
540
580
|
# 一意な色ごとにサブメッシュを抽出して追加
|
|
541
581
|
unique_colors, inverse = np.unique(colors_int, axis=0, return_inverse=True)
|
|
582
|
+
|
|
583
|
+
split_success = False
|
|
542
584
|
if unique_colors.shape[0] > 1:
|
|
543
585
|
for uc_idx, uc in enumerate(unique_colors):
|
|
544
586
|
point_inds = np.where(inverse == uc_idx)[0]
|
|
545
587
|
if point_inds.size == 0:
|
|
546
588
|
continue
|
|
547
589
|
try:
|
|
548
|
-
|
|
590
|
+
# Use temp_mesh if available (has point data), else mesh_copy
|
|
591
|
+
target_mesh = temp_mesh if 'temp_mesh' in locals() else mesh_copy
|
|
592
|
+
|
|
593
|
+
# extract_points with adjacent_cells=False to avoid pulling in neighbors
|
|
594
|
+
submesh = target_mesh.extract_points(point_inds, adjacent_cells=False)
|
|
595
|
+
|
|
549
596
|
except Exception:
|
|
550
597
|
# extract_points が利用できない場合はスキップ
|
|
551
598
|
continue
|
|
552
599
|
if submesh is None or getattr(submesh, 'n_points', 0) == 0:
|
|
553
600
|
continue
|
|
601
|
+
|
|
554
602
|
color_rgb = [int(uc[0]), int(uc[1]), int(uc[2])]
|
|
555
603
|
meshes_with_colors.append({
|
|
556
604
|
'mesh': submesh,
|
|
@@ -559,13 +607,22 @@ class MainWindowExport(object):
|
|
|
559
607
|
'type': 'display_actor',
|
|
560
608
|
'actor_name': actor_name
|
|
561
609
|
})
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
610
|
+
split_success = True
|
|
611
|
+
|
|
612
|
+
if split_success:
|
|
613
|
+
actor_count += 1
|
|
614
|
+
# 分割に成功したので以下の通常追加は行わない
|
|
615
|
+
continue
|
|
616
|
+
# If splitting failed (no submeshes added), fall through to default
|
|
617
|
+
else:
|
|
618
|
+
# 色が1色のみの場合は、その色を使用してメッシュ全体を出力
|
|
619
|
+
uc = unique_colors[0]
|
|
620
|
+
color = [int(uc[0]), int(uc[1]), int(uc[2])]
|
|
621
|
+
# ここでは continue せず、下のデフォルト追加処理に任せる(colorを更新したため)
|
|
565
622
|
except Exception:
|
|
566
623
|
# 分割処理に失敗した場合はフォールバックで単体メッシュを追加
|
|
567
624
|
pass
|
|
568
|
-
|
|
625
|
+
|
|
569
626
|
meshes_with_colors.append({
|
|
570
627
|
'mesh': mesh_copy,
|
|
571
628
|
'color': color,
|
|
@@ -577,9 +634,9 @@ class MainWindowExport(object):
|
|
|
577
634
|
actor_count += 1
|
|
578
635
|
|
|
579
636
|
except Exception as e:
|
|
580
|
-
print(f"Error processing actor {actor_name}: {e}")
|
|
581
637
|
continue
|
|
582
638
|
|
|
639
|
+
|
|
583
640
|
return meshes_with_colors
|
|
584
641
|
|
|
585
642
|
except Exception as e:
|
|
@@ -46,12 +46,16 @@ from PyQt6.QtCore import (
|
|
|
46
46
|
Qt, QPointF, QRectF, QLineF, QUrl, QTimer
|
|
47
47
|
)
|
|
48
48
|
import platform
|
|
49
|
-
import subprocess
|
|
50
49
|
try:
|
|
51
50
|
import winreg
|
|
52
51
|
except Exception:
|
|
53
52
|
winreg = None
|
|
54
53
|
|
|
54
|
+
try:
|
|
55
|
+
from .plugin_manager import PluginManager
|
|
56
|
+
except Exception:
|
|
57
|
+
from modules.plugin_manager import PluginManager
|
|
58
|
+
|
|
55
59
|
|
|
56
60
|
def detect_system_dark_mode():
|
|
57
61
|
"""Return True if the OS prefers dark app theme, False if light, or None if unknown.
|
|
@@ -253,6 +257,14 @@ class MainWindowMainInit(object):
|
|
|
253
257
|
# 3D編集ダイアログの参照を保持
|
|
254
258
|
self.active_3d_dialogs = []
|
|
255
259
|
|
|
260
|
+
|
|
261
|
+
# プラグインマネージャーの初期化
|
|
262
|
+
try:
|
|
263
|
+
self.plugin_manager = PluginManager()
|
|
264
|
+
except Exception as e:
|
|
265
|
+
print(f"Failed to initialize PluginManager: {e}")
|
|
266
|
+
self.plugin_manager = None
|
|
267
|
+
|
|
256
268
|
self.init_ui()
|
|
257
269
|
self.init_worker_thread()
|
|
258
270
|
self._setup_3d_picker()
|
|
@@ -286,6 +298,7 @@ class MainWindowMainInit(object):
|
|
|
286
298
|
self.update_atom_id_menu_text()
|
|
287
299
|
self.update_atom_id_menu_state()
|
|
288
300
|
|
|
301
|
+
|
|
289
302
|
# 初期化完了を設定
|
|
290
303
|
self.initialization_complete = True
|
|
291
304
|
self.update_window_title() # 初期化完了後にタイトルを更新
|
|
@@ -1188,6 +1201,25 @@ class MainWindowMainInit(object):
|
|
|
1188
1201
|
edit_3d_menu.addAction(constrained_opt_action)
|
|
1189
1202
|
self.constrained_opt_action = constrained_opt_action
|
|
1190
1203
|
|
|
1204
|
+
# Plugin menu
|
|
1205
|
+
plugin_menu = menu_bar.addMenu("&Plugin")
|
|
1206
|
+
|
|
1207
|
+
open_plugin_dir_action = QAction("Open Plugin Directory", self)
|
|
1208
|
+
if self.plugin_manager:
|
|
1209
|
+
open_plugin_dir_action.triggered.connect(self.plugin_manager.open_plugin_folder)
|
|
1210
|
+
else:
|
|
1211
|
+
open_plugin_dir_action.setEnabled(False)
|
|
1212
|
+
plugin_menu.addAction(open_plugin_dir_action)
|
|
1213
|
+
|
|
1214
|
+
reload_plugins_action = QAction("Reload Plugins", self)
|
|
1215
|
+
reload_plugins_action.triggered.connect(lambda: self.update_plugin_menu(plugin_menu))
|
|
1216
|
+
plugin_menu.addAction(reload_plugins_action)
|
|
1217
|
+
|
|
1218
|
+
plugin_menu.addSeparator()
|
|
1219
|
+
|
|
1220
|
+
# Initial population of plugins
|
|
1221
|
+
self.update_plugin_menu(plugin_menu)
|
|
1222
|
+
|
|
1191
1223
|
settings_menu = menu_bar.addMenu("&Settings")
|
|
1192
1224
|
# 1) 3D View settings (existing)
|
|
1193
1225
|
view_settings_action = QAction("3D View Settings...", self)
|
|
@@ -1563,7 +1595,6 @@ class MainWindowMainInit(object):
|
|
|
1563
1595
|
'wireframe_bond_radius': 0.01,
|
|
1564
1596
|
'wireframe_resolution': 6,
|
|
1565
1597
|
# Stick model parameters
|
|
1566
|
-
'stick_atom_radius': 0.15,
|
|
1567
1598
|
'stick_bond_radius': 0.15,
|
|
1568
1599
|
'stick_resolution': 16,
|
|
1569
1600
|
# Multiple bond offset parameters (per-model)
|
|
@@ -1573,12 +1604,13 @@ class MainWindowMainInit(object):
|
|
|
1573
1604
|
'ball_stick_triple_bond_radius_factor': 0.75,
|
|
1574
1605
|
'wireframe_double_bond_offset_factor': 3.0,
|
|
1575
1606
|
'wireframe_triple_bond_offset_factor': 3.0,
|
|
1576
|
-
'wireframe_double_bond_radius_factor':
|
|
1607
|
+
'wireframe_double_bond_radius_factor': 0.8,
|
|
1577
1608
|
'wireframe_triple_bond_radius_factor': 0.75,
|
|
1578
1609
|
'stick_double_bond_offset_factor': 1.5,
|
|
1579
1610
|
'stick_triple_bond_offset_factor': 1.0,
|
|
1580
1611
|
'stick_double_bond_radius_factor': 0.60,
|
|
1581
1612
|
'stick_triple_bond_radius_factor': 0.40,
|
|
1613
|
+
'aromatic_torus_thickness_factor': 0.6,
|
|
1582
1614
|
# Ensure conversion/optimization defaults are present
|
|
1583
1615
|
# If True, attempts to be permissive when RDKit raises chemical/sanitization errors
|
|
1584
1616
|
# during file import (useful for viewing malformed XYZ/MOL files).
|
|
@@ -1629,7 +1661,7 @@ class MainWindowMainInit(object):
|
|
|
1629
1661
|
('ball_stick_triple_bond_radius_factor', 'triple_bond_radius_factor', 0.75),
|
|
1630
1662
|
('wireframe_double_bond_offset_factor', 'double_bond_offset_factor', 3.0),
|
|
1631
1663
|
('wireframe_triple_bond_offset_factor', 'triple_bond_offset_factor', 3.0),
|
|
1632
|
-
('wireframe_double_bond_radius_factor', 'double_bond_radius_factor',
|
|
1664
|
+
('wireframe_double_bond_radius_factor', 'double_bond_radius_factor', 0.8),
|
|
1633
1665
|
('wireframe_triple_bond_radius_factor', 'triple_bond_radius_factor', 0.75),
|
|
1634
1666
|
('stick_double_bond_offset_factor', 'double_bond_offset_factor', 1.5),
|
|
1635
1667
|
('stick_triple_bond_offset_factor', 'triple_bond_offset_factor', 1.0),
|
|
@@ -1675,3 +1707,35 @@ class MainWindowMainInit(object):
|
|
|
1675
1707
|
except Exception as e:
|
|
1676
1708
|
print(f"Error saving settings: {e}")
|
|
1677
1709
|
|
|
1710
|
+
def update_plugin_menu(self, plugin_menu):
|
|
1711
|
+
"""Discovers plugins and updates the plugin menu actions."""
|
|
1712
|
+
if not self.plugin_manager:
|
|
1713
|
+
return
|
|
1714
|
+
|
|
1715
|
+
# Clear existing plugin actions
|
|
1716
|
+
plugin_menu.clear()
|
|
1717
|
+
|
|
1718
|
+
# Re-add static actions
|
|
1719
|
+
open_plugin_dir_action = QAction("Open Plugin Directory", self)
|
|
1720
|
+
open_plugin_dir_action.triggered.connect(self.plugin_manager.open_plugin_folder)
|
|
1721
|
+
plugin_menu.addAction(open_plugin_dir_action)
|
|
1722
|
+
|
|
1723
|
+
reload_plugins_action = QAction("Reload Plugins", self)
|
|
1724
|
+
reload_plugins_action.triggered.connect(lambda: self.update_plugin_menu(plugin_menu))
|
|
1725
|
+
plugin_menu.addAction(reload_plugins_action)
|
|
1726
|
+
|
|
1727
|
+
plugin_menu.addSeparator()
|
|
1728
|
+
|
|
1729
|
+
# Add dynamic plugin actions
|
|
1730
|
+
plugins = self.plugin_manager.discover_plugins(self)
|
|
1731
|
+
if not plugins:
|
|
1732
|
+
no_plugin_action = QAction("(No plugins found)", self)
|
|
1733
|
+
no_plugin_action.setEnabled(False)
|
|
1734
|
+
plugin_menu.addAction(no_plugin_action)
|
|
1735
|
+
else:
|
|
1736
|
+
for p in plugins:
|
|
1737
|
+
# Use default param in lambda to capture the current p
|
|
1738
|
+
action = QAction(p['name'], self)
|
|
1739
|
+
action.triggered.connect(lambda checked, mod=p['module']: self.plugin_manager.run_plugin(mod, self.mw if hasattr(self, 'mw') else self))
|
|
1740
|
+
plugin_menu.addAction(action)
|
|
1741
|
+
|
|
@@ -22,6 +22,7 @@ import io
|
|
|
22
22
|
import os
|
|
23
23
|
import contextlib
|
|
24
24
|
import traceback
|
|
25
|
+
import logging
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
# RDKit imports (explicit to satisfy flake8 and used features)
|
|
@@ -59,7 +60,7 @@ if OBABEL_AVAILABLE:
|
|
|
59
60
|
# If import fails here, disable OBABEL locally; avoid raising
|
|
60
61
|
pybel = None
|
|
61
62
|
OBABEL_AVAILABLE = False
|
|
62
|
-
|
|
63
|
+
logging.warning("Warning: openbabel.pybel not available. Open Babel fallback and OBabel-based options will be disabled.")
|
|
63
64
|
else:
|
|
64
65
|
pybel = None
|
|
65
66
|
|
|
@@ -901,12 +902,12 @@ class MainWindowMolecularParsers(object):
|
|
|
901
902
|
try:
|
|
902
903
|
mol.AddBond(i, j, Chem.BondType.SINGLE)
|
|
903
904
|
bonds_added.append((i, j, distance))
|
|
904
|
-
except:
|
|
905
|
+
except Exception:
|
|
905
906
|
# 既に結合が存在する場合はスキップ
|
|
906
907
|
pass
|
|
907
908
|
|
|
908
909
|
# デバッグ情報(オプション)
|
|
909
|
-
#
|
|
910
|
+
# Added bonds based on distance analysis
|
|
910
911
|
|
|
911
912
|
return len(bonds_added)
|
|
912
913
|
|
|
@@ -430,9 +430,9 @@ class MainWindowProjectIo(object):
|
|
|
430
430
|
# 拡張子不明の場合はJSONとして試行
|
|
431
431
|
try:
|
|
432
432
|
self.load_json_data(file_path)
|
|
433
|
-
except:
|
|
433
|
+
except Exception:
|
|
434
434
|
try:
|
|
435
435
|
self.load_raw_data(file_path)
|
|
436
|
-
except:
|
|
436
|
+
except Exception:
|
|
437
437
|
self.statusBar().showMessage("Error: Unable to determine file format.")
|
|
438
438
|
|