MoleditPy-linux 1.17.1__tar.gz → 1.18.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/PKG-INFO +1 -2
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/pyproject.toml +1 -2
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/MoleditPy_linux.egg-info/PKG-INFO +1 -2
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/align_plane_dialog.py +1 -1
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/alignment_dialog.py +1 -1
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/angle_dialog.py +2 -2
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/bond_item.py +96 -5
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/bond_length_dialog.py +2 -2
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/constants.py +1 -1
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/constrained_optimization_dialog.py +2 -2
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/dihedral_dialog.py +1 -1
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/main_window_app_state.py +1 -1
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/main_window_compute.py +6 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/main_window_edit_3d.py +7 -7
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/main_window_export.py +106 -49
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/main_window_main_init.py +3 -3
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/main_window_molecular_parsers.py +4 -3
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/main_window_project_io.py +2 -2
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/main_window_view_3d.py +359 -131
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/main_window_view_loaders.py +1 -1
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/molecule_scene.py +14 -15
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/planarize_dialog.py +1 -1
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/settings_dialog.py +86 -20
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/user_template_dialog.py +9 -8
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/LICENSE +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/README.md +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/setup.cfg +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/MoleditPy_linux.egg-info/SOURCES.txt +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/MoleditPy_linux.egg-info/dependency_links.txt +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/MoleditPy_linux.egg-info/entry_points.txt +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/MoleditPy_linux.egg-info/requires.txt +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/MoleditPy_linux.egg-info/top_level.txt +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/__init__.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/__main__.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/main.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/__init__.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/about_dialog.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/analysis_window.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/assets/icon.icns +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/assets/icon.ico +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/assets/icon.png +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/atom_item.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/calculation_worker.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/color_settings_dialog.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/custom_interactor_style.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/custom_qt_interactor.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/dialog3_d_picking_mixin.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/main_window.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/main_window_dialog_manager.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/main_window_edit_actions.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/main_window_string_importers.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/main_window_ui_manager.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/mirror_dialog.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/molecular_data.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/move_group_dialog.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/periodic_table_dialog.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/template_preview_item.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/template_preview_view.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/translation_dialog.py +0 -0
- {moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/zoomable_view.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy-linux
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.18.1
|
|
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
|
|
@@ -684,7 +684,6 @@ Classifier: Programming Language :: Python :: 3
|
|
|
684
684
|
Classifier: Operating System :: OS Independent
|
|
685
685
|
Classifier: Intended Audience :: Science/Research
|
|
686
686
|
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
687
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
688
687
|
Classifier: Programming Language :: Python :: 3.9
|
|
689
688
|
Classifier: Programming Language :: Python :: 3.10
|
|
690
689
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "MoleditPy-linux"
|
|
7
7
|
|
|
8
|
-
version = "1.
|
|
8
|
+
version = "1.18.1"
|
|
9
9
|
|
|
10
10
|
license = {file = "LICENSE"}
|
|
11
11
|
|
|
@@ -24,7 +24,6 @@ classifiers = [
|
|
|
24
24
|
"Operating System :: OS Independent",
|
|
25
25
|
"Intended Audience :: Science/Research",
|
|
26
26
|
"Topic :: Scientific/Engineering :: Chemistry",
|
|
27
|
-
"Programming Language :: Python :: 3.8",
|
|
28
27
|
"Programming Language :: Python :: 3.9",
|
|
29
28
|
"Programming Language :: Python :: 3.10",
|
|
30
29
|
"Programming Language :: Python :: 3.11",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy-linux
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.18.1
|
|
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
|
|
@@ -684,7 +684,6 @@ Classifier: Programming Language :: Python :: 3
|
|
|
684
684
|
Classifier: Operating System :: OS Independent
|
|
685
685
|
Classifier: Intended Audience :: Science/Research
|
|
686
686
|
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
687
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
688
687
|
Classifier: Programming Language :: Python :: 3.9
|
|
689
688
|
Classifier: Programming Language :: Python :: 3.10
|
|
690
689
|
Classifier: Programming Language :: Python :: 3.11
|
{moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/angle_dialog.py
RENAMED
|
@@ -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
|
|
|
@@ -216,11 +216,102 @@ class BondItem(QGraphicsItem):
|
|
|
216
216
|
offset = QPointF(v.dx(), v.dy()) * BOND_OFFSET
|
|
217
217
|
|
|
218
218
|
if self.order == 2:
|
|
219
|
-
#
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
219
|
+
# 環構造かどうかを判定し、描画方法を変更
|
|
220
|
+
is_in_ring = False
|
|
221
|
+
ring_center = None
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
# シーンからRDKit分子を取得
|
|
225
|
+
sc = self.scene()
|
|
226
|
+
if sc and hasattr(sc, 'window') and sc.window:
|
|
227
|
+
# 2DデータからRDKit分子を生成
|
|
228
|
+
mol = sc.window.data.to_rdkit_mol(use_2d_stereo=False)
|
|
229
|
+
if mol:
|
|
230
|
+
# この結合に対応するRDKitボンドを探す
|
|
231
|
+
atom1_id = self.atom1.atom_id
|
|
232
|
+
atom2_id = self.atom2.atom_id
|
|
233
|
+
|
|
234
|
+
# RDKitインデックスを取得
|
|
235
|
+
rdkit_idx1 = None
|
|
236
|
+
rdkit_idx2 = None
|
|
237
|
+
for atom in mol.GetAtoms():
|
|
238
|
+
if atom.HasProp("_original_atom_id"):
|
|
239
|
+
orig_id = atom.GetIntProp("_original_atom_id")
|
|
240
|
+
if orig_id == atom1_id:
|
|
241
|
+
rdkit_idx1 = atom.GetIdx()
|
|
242
|
+
elif orig_id == atom2_id:
|
|
243
|
+
rdkit_idx2 = atom.GetIdx()
|
|
244
|
+
|
|
245
|
+
if rdkit_idx1 is not None and rdkit_idx2 is not None:
|
|
246
|
+
bond = mol.GetBondBetweenAtoms(rdkit_idx1, rdkit_idx2)
|
|
247
|
+
if bond and bond.IsInRing():
|
|
248
|
+
is_in_ring = True
|
|
249
|
+
# 環の中心を計算(この結合を含む最小環)
|
|
250
|
+
from rdkit import Chem
|
|
251
|
+
ring_info = mol.GetRingInfo()
|
|
252
|
+
for ring in ring_info.AtomRings():
|
|
253
|
+
if rdkit_idx1 in ring and rdkit_idx2 in ring:
|
|
254
|
+
# 環の原子位置の平均を計算
|
|
255
|
+
ring_positions = []
|
|
256
|
+
for atom_idx in ring:
|
|
257
|
+
# 対応するエディタ側の原子を探す
|
|
258
|
+
rdkit_atom = mol.GetAtomWithIdx(atom_idx)
|
|
259
|
+
if rdkit_atom.HasProp("_original_atom_id"):
|
|
260
|
+
editor_atom_id = rdkit_atom.GetIntProp("_original_atom_id")
|
|
261
|
+
if editor_atom_id in sc.window.data.atoms:
|
|
262
|
+
atom_item = sc.window.data.atoms[editor_atom_id]['item']
|
|
263
|
+
if atom_item:
|
|
264
|
+
ring_positions.append(atom_item.pos())
|
|
265
|
+
|
|
266
|
+
if ring_positions:
|
|
267
|
+
# 環の中心を計算
|
|
268
|
+
center_x = sum(p.x() for p in ring_positions) / len(ring_positions)
|
|
269
|
+
center_y = sum(p.y() for p in ring_positions) / len(ring_positions)
|
|
270
|
+
ring_center = QPointF(center_x, center_y)
|
|
271
|
+
break
|
|
272
|
+
except Exception as e:
|
|
273
|
+
# エラーが発生した場合は通常の描画にフォールバック
|
|
274
|
+
is_in_ring = False
|
|
275
|
+
|
|
276
|
+
v = line.unitVector().normalVector()
|
|
277
|
+
offset = QPointF(v.dx(), v.dy()) * BOND_OFFSET
|
|
278
|
+
|
|
279
|
+
if is_in_ring and ring_center:
|
|
280
|
+
# 環構造: 1本の中心線(単結合位置) + 1本の短い内側線
|
|
281
|
+
# 結合の中心から環の中心への方向を計算
|
|
282
|
+
bond_center = line.center()
|
|
283
|
+
|
|
284
|
+
# ローカル座標系での環中心方向
|
|
285
|
+
local_ring_center = self.mapFromScene(ring_center)
|
|
286
|
+
local_bond_center = line.center()
|
|
287
|
+
inward_vec = local_ring_center - local_bond_center
|
|
288
|
+
|
|
289
|
+
# offsetとinward_vecの内積で内側を判定
|
|
290
|
+
if QPointF.dotProduct(offset, inward_vec) > 0:
|
|
291
|
+
# offsetが内側方向(2倍のオフセット)
|
|
292
|
+
inner_offset = offset * 2
|
|
293
|
+
else:
|
|
294
|
+
# -offsetが内側方向(2倍のオフセット)
|
|
295
|
+
inner_offset = -offset * 2
|
|
296
|
+
|
|
297
|
+
# 中心線を描画(単結合と同じ位置)
|
|
298
|
+
painter.drawLine(line)
|
|
299
|
+
|
|
300
|
+
# 内側の短い線を描画(80%の長さ)
|
|
301
|
+
inner_line = line.translated(inner_offset)
|
|
302
|
+
shorten_factor = 0.8
|
|
303
|
+
p1 = inner_line.p1()
|
|
304
|
+
p2 = inner_line.p2()
|
|
305
|
+
center = QPointF((p1.x() + p2.x()) / 2, (p1.y() + p2.y()) / 2)
|
|
306
|
+
shortened_p1 = center + (p1 - center) * shorten_factor
|
|
307
|
+
shortened_p2 = center + (p2 - center) * shorten_factor
|
|
308
|
+
painter.drawLine(QLineF(shortened_p1, shortened_p2))
|
|
309
|
+
else:
|
|
310
|
+
# 非環構造: 従来の2本の平行線
|
|
311
|
+
line1 = line.translated(offset)
|
|
312
|
+
line2 = line.translated(-offset)
|
|
313
|
+
painter.drawLine(line1)
|
|
314
|
+
painter.drawLine(line2)
|
|
224
315
|
|
|
225
316
|
# E/Z ラベルの描画処理
|
|
226
317
|
if self.stereo in [3, 4]:
|
{moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/bond_length_dialog.py
RENAMED
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -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:
|
{moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/main_window_compute.py
RENAMED
|
@@ -958,6 +958,12 @@ class MainWindowCompute(object):
|
|
|
958
958
|
try:
|
|
959
959
|
if mol.GetNumConformers() > 0:
|
|
960
960
|
# 初回変換では、2Dで設定したwedge/dashボンドの立体情報を保持
|
|
961
|
+
|
|
962
|
+
# 3D立体化学計算で上書きされる前に、2D由来の立体化学情報をプロパティとして保存
|
|
963
|
+
for bond in mol.GetBonds():
|
|
964
|
+
if bond.GetBondType() == Chem.BondType.DOUBLE:
|
|
965
|
+
bond.SetIntProp("_original_2d_stereo", bond.GetStereo())
|
|
966
|
+
|
|
961
967
|
# 立体化学の割り当てを行うが、既存の2D立体情報を尊重
|
|
962
968
|
Chem.AssignStereochemistry(mol, cleanIt=False, force=True)
|
|
963
969
|
|
{moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/main_window_edit_3d.py
RENAMED
|
@@ -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:
|
{moleditpy_linux-1.17.1 → moleditpy_linux-1.18.1}/src/moleditpy_linux/modules/main_window_export.py
RENAMED
|
@@ -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:
|