MoleditPy-linux 4.3.0__tar.gz → 4.3.2__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-4.3.0 → moleditpy_linux-4.3.2}/PKG-INFO +11 -8
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/README.md +10 -7
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/pyproject.toml +1 -1
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/MoleditPy_linux.egg-info/PKG-INFO +11 -8
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/constrained_optimization_dialog.py +31 -19
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/custom_interactor_style.py +54 -6
- moleditpy_linux-4.3.2/src/moleditpy_linux/ui/custom_qt_interactor.py +75 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/dialog_3d_picking_mixin.py +2 -3
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/edit_actions_logic.py +12 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/export_logic.py +2 -2
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/io_logic.py +63 -20
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/main_window_init.py +3 -1
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/molecular_scene_handler.py +3 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/move_selected_atoms_dialog.py +6 -2
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/plugin_menu_manager.py +17 -1
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/ui_manager.py +43 -1
- moleditpy_linux-4.3.0/src/moleditpy_linux/ui/custom_qt_interactor.py +0 -105
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/LICENSE +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/setup.cfg +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/MoleditPy_linux.egg-info/SOURCES.txt +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/MoleditPy_linux.egg-info/dependency_links.txt +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/MoleditPy_linux.egg-info/entry_points.txt +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/MoleditPy_linux.egg-info/requires.txt +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/MoleditPy_linux.egg-info/top_level.txt +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/__init__.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/__main__.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/assets/file_icon.ico +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/assets/icon.icns +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/assets/icon.ico +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/assets/icon.png +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/core/__init__.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/core/mol_geometry.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/core/molecular_data.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/main.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/plugins/__init__.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/plugins/plugin_interface.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/plugins/plugin_manager.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/plugins/plugin_manager_window.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/__init__.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/about_dialog.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/align_plane_dialog.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/alignment_dialog.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/analysis_window.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/angle_dialog.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/app_state.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/atom_item.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/atom_picking.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/base_picking_dialog.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/bond_item.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/bond_length_dialog.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/calculation_worker.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/color_settings_dialog.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/compute_logic.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/dialog_logic.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/dihedral_dialog.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/edit_3d_logic.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/geometry_base_dialog.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/main_window.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/mirror_dialog.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/molecule_scene.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/move_group_dialog.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/periodic_table_dialog.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/planarize_dialog.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/settings_dialog.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/settings_tabs/__init__.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/settings_tabs/settings_2d_tab.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/settings_tabs/settings_3d_tabs.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/settings_tabs/settings_other_tab.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/settings_tabs/settings_tab_base.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/string_importers.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/template_preview_item.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/template_preview_view.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/translation_dialog.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/user_template_dialog.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/view_3d_logic.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/zoomable_view.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/utils/__init__.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/utils/constants.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/utils/default_settings.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/utils/sip_isdeleted_safe.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/utils/suppress_log.py +0 -0
- {moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/utils/system_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy-linux
|
|
3
|
-
Version: 4.3.
|
|
3
|
+
Version: 4.3.2
|
|
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
|
|
@@ -770,16 +770,17 @@ This application combines a modern GUI built with **PyQt6**, powerful cheminform
|
|
|
770
770
|
* **Stereochemistry Display:** Automatically identifies and displays R/S labels for chiral centers in the 3D view after conversion.
|
|
771
771
|
* **File I/O:**
|
|
772
772
|
* Save and load entire sessions, including 2D/3D data and constraints, with the native `.pmeprj` project file.
|
|
773
|
-
* Import structures from **MOL/SDF** files or **SMILES** strings.
|
|
773
|
+
* Import structures from **MOL/SDF/XYZ** files or **SMILES/InChI** strings.
|
|
774
774
|
* Export 3D structures to **MOL** or **XYZ** formats, which are compatible with most DFT calculation software.
|
|
775
|
-
* Export 2D
|
|
775
|
+
* Export 2D views as high-resolution PNG or SVG images, and 3D views as PNG images.
|
|
776
|
+
* Export the 3D model as **STL** or **OBJ/MTL** files for 3D printing and rendering.
|
|
776
777
|
* **Security note on `.pmeraw`:** the legacy `.pmeraw` project format uses Python pickle, which can execute arbitrary code when loaded. Only open `.pmeraw` files that you created yourself; prefer the JSON-based `.pmeprj` format for sharing.
|
|
777
778
|
|
|
778
779
|
### 4. Programmable & Extensible
|
|
779
780
|
|
|
780
781
|
* **Python Plugin System:** Drop your Python scripts into the plugin folder, and they instantly become part of the application menu.
|
|
781
782
|
* **Downloadable Plugins:** Explore and download specialized plugins from the [Plugin Explorer](https://hiroyokoyama.github.io/moleditpy-plugins/explorer/).
|
|
782
|
-
* **Full API Access:** Plugins have direct access to the `MainWindow`, `RDKit` molecule objects, and
|
|
783
|
+
* **Full API Access:** Plugins have direct access to the `MainWindow`, `RDKit` molecule objects, and the Qt graphics items of the 2D scene, allowing for limitless customization.
|
|
783
784
|
* **Rapid Prototyping:** Ideal for researchers who need to test new algorithms or workflow automations on the fly.
|
|
784
785
|
|
|
785
786
|
## Installation and Execution
|
|
@@ -835,7 +836,7 @@ moleditpy
|
|
|
835
836
|
* **GUI and 2D Drawing (PyQt6):** The editor is built on a `QGraphicsScene`, where custom `AtomItem` and `BondItem` objects are interactively manipulated. The Undo/Redo feature is implemented by serializing the application state.
|
|
836
837
|
* **Chemical Calculations (RDKit / Open Babel):** RDKit is used to generate molecule objects from 2D data, perform 3D coordinate generation, and calculate properties. Open Babel serves as a fallback for 3D conversion. All heavy computations are run on a separate `QThread` to keep the GUI responsive.
|
|
837
838
|
* **3D Visualization (PyVista / pyvistaqt):** 3D rendering is achieved by generating PyVista meshes (spheres and cylinders) from RDKit conformer coordinates. A custom `vtkInteractorStyle` enables direct drag-and-drop editing of atoms in the 3D view.
|
|
838
|
-
* **Modular Architecture:** The codebase is organized into dedicated packages for `core` logic, `ui` components, and `utils`. The
|
|
839
|
+
* **Modular Architecture:** The codebase is organized into dedicated packages for `core` logic, `ui` components, and `utils`. The `MainWindow` acts as a composition hub that delegates all work to specialized manager objects (state, I/O, 3D view, dialogs), ensuring long-term maintainability and easier verification.
|
|
839
840
|
|
|
840
841
|
## License & Disclaimer
|
|
841
842
|
|
|
@@ -905,16 +906,17 @@ Additionally, please cite the plugins you used.
|
|
|
905
906
|
* **立体化学表示:** 3D変換後、キラル中心を自動的に認識し、R/Sラベルを3Dビューに表示します。
|
|
906
907
|
* **ファイル入出力:**
|
|
907
908
|
* 2D/3Dデータや制約情報を含むセッション全体を、独自のプロジェクトファイル (`.pmeprj`) として保存・読み込みできます。
|
|
908
|
-
* **MOL/SDF**ファイルや**SMILES**文字列から構造をインポートできます。
|
|
909
|
+
* **MOL/SDF/XYZ**ファイルや**SMILES/InChI**文字列から構造をインポートできます。
|
|
909
910
|
* 3D構造を**MOL**または**XYZ**形式でエクスポートでき、これらは多くのDFT計算ソフトウェアと互換性があります。
|
|
910
|
-
* 2D
|
|
911
|
+
* 2Dビューを高解像度のPNG・SVG画像として、3DビューをPNG画像としてエクスポートできます。
|
|
912
|
+
* 3Dモデルを**STL**または**OBJ/MTL**ファイルとしてエクスポートでき、3Dプリントやレンダリングに利用できます。
|
|
911
913
|
* **`.pmeraw` に関するセキュリティ上の注意:** 旧形式の `.pmeraw` プロジェクトは Python の pickle を使用しており、読み込み時に任意のコードが実行される可能性があります。自分で作成した `.pmeraw` ファイルのみを開き、共有には JSON ベースの `.pmeprj` 形式を使用してください。
|
|
912
914
|
|
|
913
915
|
### 4. プログラマブルで拡張可能
|
|
914
916
|
|
|
915
917
|
* **Pythonプラグインシステム:** Pythonスクリプトをプラグインフォルダに入れるだけで、即座にアプリケーションメニューの一部として機能します。
|
|
916
918
|
* **プラグインのダウンロード:** [Plugin Explorer](https://hiroyokoyama.github.io/moleditpy-plugins/explorer/) から特化したプラグインを探索・ダウンロードできます。
|
|
917
|
-
* **フルAPIアクセス:** プラグインは `MainWindow`、`RDKit`
|
|
919
|
+
* **フルAPIアクセス:** プラグインは `MainWindow`、`RDKit` 分子オブジェクト、2D シーンの Qt グラフィックスアイテムに直接アクセスでき、無限のカスタマイズが可能です。
|
|
918
920
|
* **迅速なプロトタイピング:** 新しいアルゴリズムやワークフローの自動化をその場でテストしたい研究者に最適です。
|
|
919
921
|
|
|
920
922
|
## インストールと実行
|
|
@@ -970,6 +972,7 @@ moleditpy
|
|
|
970
972
|
* **GUIと2D描画 (PyQt6):** `QGraphicsScene`上にカスタムの`AtomItem`(原子)と`BondItem`(結合)を配置し、対話的に操作します。Undo/Redo機能は、アプリケーションの状態をシリアライズしてスタックに保存することで実現しています。
|
|
971
973
|
* **化学計算 (RDKit / Open Babel):** 2DデータからRDKit分子オブジェクトを生成し、3D座標生成や分子特性計算を実行します。RDKitでの3D座標生成が失敗した際は、Open Babelにフォールバックします。重い計算処理は別スレッド (`QThread`) で実行し、GUIの応答性を維持しています。
|
|
972
974
|
* **3D可視化 (PyVista / pyvistaqt):** RDKitのコンフォーマ座標からPyVistaのメッシュ(球や円柱)を生成して描画します。カスタムの`vtkInteractorStyle`を実装し、3Dビュー内での原子の直接的なドラッグ&ドロップ編集を可能にしています。
|
|
975
|
+
* **モジュラーアーキテクチャ:** コードベースは `core` ロジック、`ui` コンポーネント、`utils` の専用パッケージに整理されています。`MainWindow` は、状態管理・ファイル入出力・3D ビュー・ダイアログなどを専門のマネージャーオブジェクトに委譲するコンポジションハブとして機能し、長期的な保守性と検証の容易さを確保しています。
|
|
973
976
|
|
|
974
977
|
## ライセンス & 免責事項
|
|
975
978
|
|
|
@@ -67,16 +67,17 @@ This application combines a modern GUI built with **PyQt6**, powerful cheminform
|
|
|
67
67
|
* **Stereochemistry Display:** Automatically identifies and displays R/S labels for chiral centers in the 3D view after conversion.
|
|
68
68
|
* **File I/O:**
|
|
69
69
|
* Save and load entire sessions, including 2D/3D data and constraints, with the native `.pmeprj` project file.
|
|
70
|
-
* Import structures from **MOL/SDF** files or **SMILES** strings.
|
|
70
|
+
* Import structures from **MOL/SDF/XYZ** files or **SMILES/InChI** strings.
|
|
71
71
|
* Export 3D structures to **MOL** or **XYZ** formats, which are compatible with most DFT calculation software.
|
|
72
|
-
* Export 2D
|
|
72
|
+
* Export 2D views as high-resolution PNG or SVG images, and 3D views as PNG images.
|
|
73
|
+
* Export the 3D model as **STL** or **OBJ/MTL** files for 3D printing and rendering.
|
|
73
74
|
* **Security note on `.pmeraw`:** the legacy `.pmeraw` project format uses Python pickle, which can execute arbitrary code when loaded. Only open `.pmeraw` files that you created yourself; prefer the JSON-based `.pmeprj` format for sharing.
|
|
74
75
|
|
|
75
76
|
### 4. Programmable & Extensible
|
|
76
77
|
|
|
77
78
|
* **Python Plugin System:** Drop your Python scripts into the plugin folder, and they instantly become part of the application menu.
|
|
78
79
|
* **Downloadable Plugins:** Explore and download specialized plugins from the [Plugin Explorer](https://hiroyokoyama.github.io/moleditpy-plugins/explorer/).
|
|
79
|
-
* **Full API Access:** Plugins have direct access to the `MainWindow`, `RDKit` molecule objects, and
|
|
80
|
+
* **Full API Access:** Plugins have direct access to the `MainWindow`, `RDKit` molecule objects, and the Qt graphics items of the 2D scene, allowing for limitless customization.
|
|
80
81
|
* **Rapid Prototyping:** Ideal for researchers who need to test new algorithms or workflow automations on the fly.
|
|
81
82
|
|
|
82
83
|
## Installation and Execution
|
|
@@ -132,7 +133,7 @@ moleditpy
|
|
|
132
133
|
* **GUI and 2D Drawing (PyQt6):** The editor is built on a `QGraphicsScene`, where custom `AtomItem` and `BondItem` objects are interactively manipulated. The Undo/Redo feature is implemented by serializing the application state.
|
|
133
134
|
* **Chemical Calculations (RDKit / Open Babel):** RDKit is used to generate molecule objects from 2D data, perform 3D coordinate generation, and calculate properties. Open Babel serves as a fallback for 3D conversion. All heavy computations are run on a separate `QThread` to keep the GUI responsive.
|
|
134
135
|
* **3D Visualization (PyVista / pyvistaqt):** 3D rendering is achieved by generating PyVista meshes (spheres and cylinders) from RDKit conformer coordinates. A custom `vtkInteractorStyle` enables direct drag-and-drop editing of atoms in the 3D view.
|
|
135
|
-
* **Modular Architecture:** The codebase is organized into dedicated packages for `core` logic, `ui` components, and `utils`. The
|
|
136
|
+
* **Modular Architecture:** The codebase is organized into dedicated packages for `core` logic, `ui` components, and `utils`. The `MainWindow` acts as a composition hub that delegates all work to specialized manager objects (state, I/O, 3D view, dialogs), ensuring long-term maintainability and easier verification.
|
|
136
137
|
|
|
137
138
|
## License & Disclaimer
|
|
138
139
|
|
|
@@ -202,16 +203,17 @@ Additionally, please cite the plugins you used.
|
|
|
202
203
|
* **立体化学表示:** 3D変換後、キラル中心を自動的に認識し、R/Sラベルを3Dビューに表示します。
|
|
203
204
|
* **ファイル入出力:**
|
|
204
205
|
* 2D/3Dデータや制約情報を含むセッション全体を、独自のプロジェクトファイル (`.pmeprj`) として保存・読み込みできます。
|
|
205
|
-
* **MOL/SDF**ファイルや**SMILES**文字列から構造をインポートできます。
|
|
206
|
+
* **MOL/SDF/XYZ**ファイルや**SMILES/InChI**文字列から構造をインポートできます。
|
|
206
207
|
* 3D構造を**MOL**または**XYZ**形式でエクスポートでき、これらは多くのDFT計算ソフトウェアと互換性があります。
|
|
207
|
-
* 2D
|
|
208
|
+
* 2Dビューを高解像度のPNG・SVG画像として、3DビューをPNG画像としてエクスポートできます。
|
|
209
|
+
* 3Dモデルを**STL**または**OBJ/MTL**ファイルとしてエクスポートでき、3Dプリントやレンダリングに利用できます。
|
|
208
210
|
* **`.pmeraw` に関するセキュリティ上の注意:** 旧形式の `.pmeraw` プロジェクトは Python の pickle を使用しており、読み込み時に任意のコードが実行される可能性があります。自分で作成した `.pmeraw` ファイルのみを開き、共有には JSON ベースの `.pmeprj` 形式を使用してください。
|
|
209
211
|
|
|
210
212
|
### 4. プログラマブルで拡張可能
|
|
211
213
|
|
|
212
214
|
* **Pythonプラグインシステム:** Pythonスクリプトをプラグインフォルダに入れるだけで、即座にアプリケーションメニューの一部として機能します。
|
|
213
215
|
* **プラグインのダウンロード:** [Plugin Explorer](https://hiroyokoyama.github.io/moleditpy-plugins/explorer/) から特化したプラグインを探索・ダウンロードできます。
|
|
214
|
-
* **フルAPIアクセス:** プラグインは `MainWindow`、`RDKit`
|
|
216
|
+
* **フルAPIアクセス:** プラグインは `MainWindow`、`RDKit` 分子オブジェクト、2D シーンの Qt グラフィックスアイテムに直接アクセスでき、無限のカスタマイズが可能です。
|
|
215
217
|
* **迅速なプロトタイピング:** 新しいアルゴリズムやワークフローの自動化をその場でテストしたい研究者に最適です。
|
|
216
218
|
|
|
217
219
|
## インストールと実行
|
|
@@ -267,6 +269,7 @@ moleditpy
|
|
|
267
269
|
* **GUIと2D描画 (PyQt6):** `QGraphicsScene`上にカスタムの`AtomItem`(原子)と`BondItem`(結合)を配置し、対話的に操作します。Undo/Redo機能は、アプリケーションの状態をシリアライズしてスタックに保存することで実現しています。
|
|
268
270
|
* **化学計算 (RDKit / Open Babel):** 2DデータからRDKit分子オブジェクトを生成し、3D座標生成や分子特性計算を実行します。RDKitでの3D座標生成が失敗した際は、Open Babelにフォールバックします。重い計算処理は別スレッド (`QThread`) で実行し、GUIの応答性を維持しています。
|
|
269
271
|
* **3D可視化 (PyVista / pyvistaqt):** RDKitのコンフォーマ座標からPyVistaのメッシュ(球や円柱)を生成して描画します。カスタムの`vtkInteractorStyle`を実装し、3Dビュー内での原子の直接的なドラッグ&ドロップ編集を可能にしています。
|
|
272
|
+
* **モジュラーアーキテクチャ:** コードベースは `core` ロジック、`ui` コンポーネント、`utils` の専用パッケージに整理されています。`MainWindow` は、状態管理・ファイル入出力・3D ビュー・ダイアログなどを専門のマネージャーオブジェクトに委譲するコンポジションハブとして機能し、長期的な保守性と検証の容易さを確保しています。
|
|
270
273
|
|
|
271
274
|
## ライセンス & 免責事項
|
|
272
275
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy-linux
|
|
3
|
-
Version: 4.3.
|
|
3
|
+
Version: 4.3.2
|
|
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
|
|
@@ -770,16 +770,17 @@ This application combines a modern GUI built with **PyQt6**, powerful cheminform
|
|
|
770
770
|
* **Stereochemistry Display:** Automatically identifies and displays R/S labels for chiral centers in the 3D view after conversion.
|
|
771
771
|
* **File I/O:**
|
|
772
772
|
* Save and load entire sessions, including 2D/3D data and constraints, with the native `.pmeprj` project file.
|
|
773
|
-
* Import structures from **MOL/SDF** files or **SMILES** strings.
|
|
773
|
+
* Import structures from **MOL/SDF/XYZ** files or **SMILES/InChI** strings.
|
|
774
774
|
* Export 3D structures to **MOL** or **XYZ** formats, which are compatible with most DFT calculation software.
|
|
775
|
-
* Export 2D
|
|
775
|
+
* Export 2D views as high-resolution PNG or SVG images, and 3D views as PNG images.
|
|
776
|
+
* Export the 3D model as **STL** or **OBJ/MTL** files for 3D printing and rendering.
|
|
776
777
|
* **Security note on `.pmeraw`:** the legacy `.pmeraw` project format uses Python pickle, which can execute arbitrary code when loaded. Only open `.pmeraw` files that you created yourself; prefer the JSON-based `.pmeprj` format for sharing.
|
|
777
778
|
|
|
778
779
|
### 4. Programmable & Extensible
|
|
779
780
|
|
|
780
781
|
* **Python Plugin System:** Drop your Python scripts into the plugin folder, and they instantly become part of the application menu.
|
|
781
782
|
* **Downloadable Plugins:** Explore and download specialized plugins from the [Plugin Explorer](https://hiroyokoyama.github.io/moleditpy-plugins/explorer/).
|
|
782
|
-
* **Full API Access:** Plugins have direct access to the `MainWindow`, `RDKit` molecule objects, and
|
|
783
|
+
* **Full API Access:** Plugins have direct access to the `MainWindow`, `RDKit` molecule objects, and the Qt graphics items of the 2D scene, allowing for limitless customization.
|
|
783
784
|
* **Rapid Prototyping:** Ideal for researchers who need to test new algorithms or workflow automations on the fly.
|
|
784
785
|
|
|
785
786
|
## Installation and Execution
|
|
@@ -835,7 +836,7 @@ moleditpy
|
|
|
835
836
|
* **GUI and 2D Drawing (PyQt6):** The editor is built on a `QGraphicsScene`, where custom `AtomItem` and `BondItem` objects are interactively manipulated. The Undo/Redo feature is implemented by serializing the application state.
|
|
836
837
|
* **Chemical Calculations (RDKit / Open Babel):** RDKit is used to generate molecule objects from 2D data, perform 3D coordinate generation, and calculate properties. Open Babel serves as a fallback for 3D conversion. All heavy computations are run on a separate `QThread` to keep the GUI responsive.
|
|
837
838
|
* **3D Visualization (PyVista / pyvistaqt):** 3D rendering is achieved by generating PyVista meshes (spheres and cylinders) from RDKit conformer coordinates. A custom `vtkInteractorStyle` enables direct drag-and-drop editing of atoms in the 3D view.
|
|
838
|
-
* **Modular Architecture:** The codebase is organized into dedicated packages for `core` logic, `ui` components, and `utils`. The
|
|
839
|
+
* **Modular Architecture:** The codebase is organized into dedicated packages for `core` logic, `ui` components, and `utils`. The `MainWindow` acts as a composition hub that delegates all work to specialized manager objects (state, I/O, 3D view, dialogs), ensuring long-term maintainability and easier verification.
|
|
839
840
|
|
|
840
841
|
## License & Disclaimer
|
|
841
842
|
|
|
@@ -905,16 +906,17 @@ Additionally, please cite the plugins you used.
|
|
|
905
906
|
* **立体化学表示:** 3D変換後、キラル中心を自動的に認識し、R/Sラベルを3Dビューに表示します。
|
|
906
907
|
* **ファイル入出力:**
|
|
907
908
|
* 2D/3Dデータや制約情報を含むセッション全体を、独自のプロジェクトファイル (`.pmeprj`) として保存・読み込みできます。
|
|
908
|
-
* **MOL/SDF**ファイルや**SMILES**文字列から構造をインポートできます。
|
|
909
|
+
* **MOL/SDF/XYZ**ファイルや**SMILES/InChI**文字列から構造をインポートできます。
|
|
909
910
|
* 3D構造を**MOL**または**XYZ**形式でエクスポートでき、これらは多くのDFT計算ソフトウェアと互換性があります。
|
|
910
|
-
* 2D
|
|
911
|
+
* 2Dビューを高解像度のPNG・SVG画像として、3DビューをPNG画像としてエクスポートできます。
|
|
912
|
+
* 3Dモデルを**STL**または**OBJ/MTL**ファイルとしてエクスポートでき、3Dプリントやレンダリングに利用できます。
|
|
911
913
|
* **`.pmeraw` に関するセキュリティ上の注意:** 旧形式の `.pmeraw` プロジェクトは Python の pickle を使用しており、読み込み時に任意のコードが実行される可能性があります。自分で作成した `.pmeraw` ファイルのみを開き、共有には JSON ベースの `.pmeprj` 形式を使用してください。
|
|
912
914
|
|
|
913
915
|
### 4. プログラマブルで拡張可能
|
|
914
916
|
|
|
915
917
|
* **Pythonプラグインシステム:** Pythonスクリプトをプラグインフォルダに入れるだけで、即座にアプリケーションメニューの一部として機能します。
|
|
916
918
|
* **プラグインのダウンロード:** [Plugin Explorer](https://hiroyokoyama.github.io/moleditpy-plugins/explorer/) から特化したプラグインを探索・ダウンロードできます。
|
|
917
|
-
* **フルAPIアクセス:** プラグインは `MainWindow`、`RDKit`
|
|
919
|
+
* **フルAPIアクセス:** プラグインは `MainWindow`、`RDKit` 分子オブジェクト、2D シーンの Qt グラフィックスアイテムに直接アクセスでき、無限のカスタマイズが可能です。
|
|
918
920
|
* **迅速なプロトタイピング:** 新しいアルゴリズムやワークフローの自動化をその場でテストしたい研究者に最適です。
|
|
919
921
|
|
|
920
922
|
## インストールと実行
|
|
@@ -970,6 +972,7 @@ moleditpy
|
|
|
970
972
|
* **GUIと2D描画 (PyQt6):** `QGraphicsScene`上にカスタムの`AtomItem`(原子)と`BondItem`(結合)を配置し、対話的に操作します。Undo/Redo機能は、アプリケーションの状態をシリアライズしてスタックに保存することで実現しています。
|
|
971
973
|
* **化学計算 (RDKit / Open Babel):** 2DデータからRDKit分子オブジェクトを生成し、3D座標生成や分子特性計算を実行します。RDKitでの3D座標生成が失敗した際は、Open Babelにフォールバックします。重い計算処理は別スレッド (`QThread`) で実行し、GUIの応答性を維持しています。
|
|
972
974
|
* **3D可視化 (PyVista / pyvistaqt):** RDKitのコンフォーマ座標からPyVistaのメッシュ(球や円柱)を生成して描画します。カスタムの`vtkInteractorStyle`を実装し、3Dビュー内での原子の直接的なドラッグ&ドロップ編集を可能にしています。
|
|
975
|
+
* **モジュラーアーキテクチャ:** コードベースは `core` ロジック、`ui` コンポーネント、`utils` の専用パッケージに整理されています。`MainWindow` は、状態管理・ファイル入出力・3D ビュー・ダイアログなどを専門のマネージャーオブジェクトに委譲するコンポジションハブとして機能し、長期的な保守性と検証の容易さを確保しています。
|
|
973
976
|
|
|
974
977
|
## ライセンス & 免責事項
|
|
975
978
|
|
|
@@ -65,6 +65,8 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
|
|
|
65
65
|
self.constraints: list[Any] = [] # (type, atoms_indices, value)
|
|
66
66
|
self.constraint_labels: list[Any] = [] # 3D label actors
|
|
67
67
|
self._opt_thread: Any = None
|
|
68
|
+
# Closed dialogs must discard late optimization-thread results
|
|
69
|
+
self._closed = False
|
|
68
70
|
self.init_ui()
|
|
69
71
|
self.enable_picking()
|
|
70
72
|
|
|
@@ -599,6 +601,11 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
|
|
|
599
601
|
self.optimize_button.setEnabled(True)
|
|
600
602
|
|
|
601
603
|
def _on_optimization_error(self, err_msg: str) -> None:
|
|
604
|
+
if self._closed:
|
|
605
|
+
logging.warning(
|
|
606
|
+
"Constrained optimization failed after dialog close: %s", err_msg
|
|
607
|
+
)
|
|
608
|
+
return
|
|
602
609
|
self.optimize_button.setEnabled(True)
|
|
603
610
|
status_bar = self.main_window.statusBar()
|
|
604
611
|
if status_bar is not None:
|
|
@@ -606,6 +613,9 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
|
|
|
606
613
|
QMessageBox.critical(self, "Error", f"Optimization error: {err_msg}")
|
|
607
614
|
|
|
608
615
|
def _on_optimization_finished(self, ff_name: str, conf: Any) -> None:
|
|
616
|
+
if self._closed:
|
|
617
|
+
logging.info("Discarding constrained optimization result after close.")
|
|
618
|
+
return
|
|
609
619
|
self.optimize_button.setEnabled(True)
|
|
610
620
|
try:
|
|
611
621
|
# Apply optimized coordinates to the main window's numpy array
|
|
@@ -619,25 +629,7 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
|
|
|
619
629
|
pos.z,
|
|
620
630
|
]
|
|
621
631
|
|
|
622
|
-
#
|
|
623
|
-
self.main_window.view_3d_manager.draw_molecule_3d(self.mol)
|
|
624
|
-
self.main_window.view_3d_manager.update_chiral_labels()
|
|
625
|
-
self.main_window.edit_actions_manager.push_undo_state()
|
|
626
|
-
status_bar = self.main_window.statusBar()
|
|
627
|
-
if status_bar is not None:
|
|
628
|
-
status_bar.showMessage("Constrained optimization finished.")
|
|
629
|
-
|
|
630
|
-
try:
|
|
631
|
-
constrained_method_name = f"Constrained_{ff_name}"
|
|
632
|
-
self.main_window.compute_manager.last_successful_optimization_method = (
|
|
633
|
-
constrained_method_name
|
|
634
|
-
)
|
|
635
|
-
except (AttributeError, RuntimeError, ValueError, TypeError) as e:
|
|
636
|
-
logging.warning(
|
|
637
|
-
"Failed to set last_successful_optimization_method: %s", e
|
|
638
|
-
)
|
|
639
|
-
|
|
640
|
-
# Save constraints list to MainWindow on success (same logic as reject)
|
|
632
|
+
# Sync constraints to MainWindow before push_undo_state so the snapshot records them
|
|
641
633
|
try:
|
|
642
634
|
# Save as list for JSON compatibility
|
|
643
635
|
json_safe_constraints = []
|
|
@@ -669,6 +661,24 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
|
|
|
669
661
|
except (AttributeError, RuntimeError, ValueError, TypeError) as e:
|
|
670
662
|
logging.warning("Failed to save constraints post-optimization: %s", e)
|
|
671
663
|
|
|
664
|
+
# Update 3D view
|
|
665
|
+
self.main_window.view_3d_manager.draw_molecule_3d(self.mol)
|
|
666
|
+
self.main_window.view_3d_manager.update_chiral_labels()
|
|
667
|
+
self.main_window.edit_actions_manager.push_undo_state()
|
|
668
|
+
status_bar = self.main_window.statusBar()
|
|
669
|
+
if status_bar is not None:
|
|
670
|
+
status_bar.showMessage("Constrained optimization finished.")
|
|
671
|
+
|
|
672
|
+
try:
|
|
673
|
+
constrained_method_name = f"Constrained_{ff_name}"
|
|
674
|
+
self.main_window.compute_manager.last_successful_optimization_method = (
|
|
675
|
+
constrained_method_name
|
|
676
|
+
)
|
|
677
|
+
except (AttributeError, RuntimeError, ValueError, TypeError) as e:
|
|
678
|
+
logging.warning(
|
|
679
|
+
"Failed to set last_successful_optimization_method: %s", e
|
|
680
|
+
)
|
|
681
|
+
|
|
672
682
|
except (AttributeError, RuntimeError, ValueError, TypeError) as e:
|
|
673
683
|
logging.exception("Optimization failed: %s", e)
|
|
674
684
|
|
|
@@ -679,6 +689,7 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
|
|
|
679
689
|
|
|
680
690
|
def reject(self) -> None:
|
|
681
691
|
"""Clear labels, disable picking, and save constraints before closing."""
|
|
692
|
+
self._closed = True
|
|
682
693
|
self.clear_constraint_labels()
|
|
683
694
|
self.clear_selection_labels()
|
|
684
695
|
self.disable_picking()
|
|
@@ -706,6 +717,7 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
|
|
|
706
717
|
True # Mark as unsaved changes
|
|
707
718
|
)
|
|
708
719
|
self.main_window.state_manager.update_window_title()
|
|
720
|
+
self.main_window.edit_actions_manager.push_undo_state()
|
|
709
721
|
|
|
710
722
|
except (AttributeError, RuntimeError, ValueError, TypeError) as e:
|
|
711
723
|
logging.warning("Failed to save constraints to main window: %s", e)
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/custom_interactor_style.py
RENAMED
|
@@ -159,9 +159,8 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
159
159
|
try:
|
|
160
160
|
move_group_dialog.on_atom_picked(clicked_atom_idx)
|
|
161
161
|
except (AttributeError, RuntimeError):
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
"Suppressed non-critical error", exc_info=True
|
|
162
|
+
logging.warning(
|
|
163
|
+
"Move-dialog atom toggle failed", exc_info=True
|
|
165
164
|
)
|
|
166
165
|
|
|
167
166
|
QTimer.singleShot(0, _deferred_toggle)
|
|
@@ -273,9 +272,8 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
273
272
|
closest_atom_idx
|
|
274
273
|
)
|
|
275
274
|
except (AttributeError, RuntimeError):
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
"Suppressed non-critical error", exc_info=True
|
|
275
|
+
logging.warning(
|
|
276
|
+
"Measurement selection failed", exc_info=True
|
|
279
277
|
)
|
|
280
278
|
|
|
281
279
|
QTimer.singleShot(0, _deferred_measure)
|
|
@@ -383,6 +381,54 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
383
381
|
# Standard right-click
|
|
384
382
|
super().OnRightButtonDown()
|
|
385
383
|
|
|
384
|
+
def _heal_stuck_pointer_state(self, move_group_dialog: Any) -> None:
|
|
385
|
+
"""Self-heal drag/rotate state whose release event was lost.
|
|
386
|
+
|
|
387
|
+
A release can be lost mid-gesture (e.g. pyvista temporarily swapping
|
|
388
|
+
the interactor style, a dialog grabbing events), leaving this style or
|
|
389
|
+
a Move Group dialog in a permanent drag/rotate state that blocks
|
|
390
|
+
interaction. Verify against the real button state on every move and
|
|
391
|
+
reset when the button is not actually held.
|
|
392
|
+
"""
|
|
393
|
+
try:
|
|
394
|
+
buttons = QApplication.mouseButtons()
|
|
395
|
+
left_held = bool(buttons & Qt.MouseButton.LeftButton)
|
|
396
|
+
right_held = bool(buttons & Qt.MouseButton.RightButton)
|
|
397
|
+
any_held = buttons != Qt.MouseButton.NoButton
|
|
398
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
399
|
+
return
|
|
400
|
+
|
|
401
|
+
if self._is_dragging_atom and not left_held:
|
|
402
|
+
self.reset_interactor_state()
|
|
403
|
+
|
|
404
|
+
if not any_held:
|
|
405
|
+
try:
|
|
406
|
+
if self.GetState() != 0: # stuck ROTATE/PAN/etc. without a button
|
|
407
|
+
self.reset_interactor_state()
|
|
408
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
409
|
+
# Safe defensive fallback catching AttributeError, RuntimeError, TypeError
|
|
410
|
+
logging.debug("Suppressed non-critical error", exc_info=True)
|
|
411
|
+
|
|
412
|
+
if move_group_dialog is not None:
|
|
413
|
+
try:
|
|
414
|
+
if (
|
|
415
|
+
getattr(move_group_dialog, "is_dragging_group_vtk", False)
|
|
416
|
+
and not left_held
|
|
417
|
+
):
|
|
418
|
+
move_group_dialog.is_dragging_group_vtk = False
|
|
419
|
+
move_group_dialog.drag_start_pos_vtk = None
|
|
420
|
+
move_group_dialog.mouse_moved_vtk = False
|
|
421
|
+
if (
|
|
422
|
+
getattr(move_group_dialog, "is_rotating_group_vtk", False)
|
|
423
|
+
and not right_held
|
|
424
|
+
):
|
|
425
|
+
move_group_dialog.is_rotating_group_vtk = False
|
|
426
|
+
move_group_dialog.rotation_start_pos = None
|
|
427
|
+
move_group_dialog.rotation_mouse_moved = False
|
|
428
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
429
|
+
# Safe defensive fallback catching AttributeError, RuntimeError, TypeError
|
|
430
|
+
logging.debug("Suppressed non-critical error", exc_info=True)
|
|
431
|
+
|
|
386
432
|
def on_mouse_move(self, obj: Any, event: Any) -> None:
|
|
387
433
|
"""
|
|
388
434
|
Handle mouse move (drag vs camera/hover).
|
|
@@ -402,6 +448,8 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
402
448
|
break
|
|
403
449
|
except (AttributeError, RuntimeError, TypeError):
|
|
404
450
|
logging.warning("Caught exception in " + __file__, exc_info=True)
|
|
451
|
+
|
|
452
|
+
self._heal_stuck_pointer_state(move_group_dialog)
|
|
405
453
|
if move_group_dialog and getattr(
|
|
406
454
|
move_group_dialog, "is_dragging_group_vtk", False
|
|
407
455
|
):
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
import logging
|
|
14
|
+
from typing import Any, Optional
|
|
15
|
+
|
|
16
|
+
from PyQt6.QtCore import QEvent
|
|
17
|
+
from PyQt6.QtGui import QMouseEvent
|
|
18
|
+
from pyvistaqt import QtInteractor
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CustomQtInteractor(QtInteractor):
|
|
22
|
+
"""PyVista QtInteractor subclass that exposes the main window for event handling."""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
parent: Optional[Any] = None,
|
|
27
|
+
main_window: Optional[Any] = None,
|
|
28
|
+
**kwargs: Any,
|
|
29
|
+
) -> None:
|
|
30
|
+
super().__init__(parent, **kwargs)
|
|
31
|
+
self.main_window = main_window
|
|
32
|
+
|
|
33
|
+
def wheelEvent(self, event: Any) -> None:
|
|
34
|
+
"""
|
|
35
|
+
Override the mouse wheel event.
|
|
36
|
+
"""
|
|
37
|
+
# First call the parent class event to perform normal zoom processing
|
|
38
|
+
super().wheelEvent(event)
|
|
39
|
+
|
|
40
|
+
# Force focus back to the 2D view after zoom processing
|
|
41
|
+
if self.main_window and hasattr(self.main_window.init_manager, "view_2d"):
|
|
42
|
+
self.main_window.init_manager.view_2d.setFocus()
|
|
43
|
+
|
|
44
|
+
def mouseReleaseEvent(self, event: Any) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Override the Qt mouse release event to return focus to the 2D view after
|
|
47
|
+
all 3D view operations.
|
|
48
|
+
"""
|
|
49
|
+
super().mouseReleaseEvent(event) # Process parent class event first
|
|
50
|
+
if self.main_window and hasattr(self.main_window.init_manager, "view_2d"):
|
|
51
|
+
self.main_window.init_manager.view_2d.setFocus()
|
|
52
|
+
|
|
53
|
+
def mouseDoubleClickEvent(self, event: Any) -> None:
|
|
54
|
+
"""Re-dispatch double-clicks as plain presses so fast clicking works.
|
|
55
|
+
|
|
56
|
+
Qt turns every second fast click into a double-click event. Forwarding
|
|
57
|
+
it unchanged would reach VTK with a repeat count and be dropped by the
|
|
58
|
+
interactor style, so the click would be lost (e.g. when rapidly
|
|
59
|
+
selecting atoms in measurement mode). Synthesizing a normal press keeps
|
|
60
|
+
press/release pairing intact and makes each fast click act as a click.
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
synthetic_press = QMouseEvent(
|
|
64
|
+
QEvent.Type.MouseButtonPress,
|
|
65
|
+
event.position(),
|
|
66
|
+
event.globalPosition(),
|
|
67
|
+
event.button(),
|
|
68
|
+
event.buttons(),
|
|
69
|
+
event.modifiers(),
|
|
70
|
+
)
|
|
71
|
+
super().mousePressEvent(synthetic_press)
|
|
72
|
+
event.accept()
|
|
73
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
74
|
+
# Safe defensive fallback catching AttributeError, RuntimeError, TypeError
|
|
75
|
+
logging.debug("Suppressed non-critical error", exc_info=True)
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/dialog_3d_picking_mixin.py
RENAMED
|
@@ -82,9 +82,8 @@ class Dialog3DPickingMixin:
|
|
|
82
82
|
try:
|
|
83
83
|
self.on_atom_picked(int(closest_atom_idx))
|
|
84
84
|
except (AttributeError, RuntimeError):
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
"Suppressed non-critical error", exc_info=True
|
|
85
|
+
logging.warning(
|
|
86
|
+
"Dialog atom pick failed", exc_info=True
|
|
88
87
|
)
|
|
89
88
|
|
|
90
89
|
from PyQt6.QtCore import QTimer
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/edit_actions_logic.py
RENAMED
|
@@ -21,6 +21,7 @@ from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
|
21
21
|
import numpy as np
|
|
22
22
|
|
|
23
23
|
from ..core.mol_geometry import identify_valence_problems
|
|
24
|
+
from .app_state import _serialize_constraints
|
|
24
25
|
|
|
25
26
|
# RDKit imports (explicit to satisfy flake8 and used features)
|
|
26
27
|
from PyQt6.QtCore import QByteArray, QMimeData, QPointF, Qt, QTimer
|
|
@@ -116,6 +117,15 @@ class EditActionsManager:
|
|
|
116
117
|
self.redo_stack.clear()
|
|
117
118
|
self.push_undo_state()
|
|
118
119
|
|
|
120
|
+
def _constraints_for_comparison(self) -> List[Any]:
|
|
121
|
+
"""Current 3D constraints in the serialized form stored in snapshots."""
|
|
122
|
+
constraints = getattr(
|
|
123
|
+
getattr(self.host, "edit_3d_manager", None), "constraints_3d", None
|
|
124
|
+
)
|
|
125
|
+
if not isinstance(constraints, list):
|
|
126
|
+
return []
|
|
127
|
+
return _serialize_constraints(constraints)
|
|
128
|
+
|
|
119
129
|
def push_undo_state(self) -> None:
|
|
120
130
|
"""Saves current molecular state to undo stack for history tracking."""
|
|
121
131
|
if getattr(self.host, "is_restoring_state", False):
|
|
@@ -150,6 +160,7 @@ class EditActionsManager:
|
|
|
150
160
|
]
|
|
151
161
|
if self.host.view_3d_manager.current_mol
|
|
152
162
|
else None,
|
|
163
|
+
"constraints_3d": self._constraints_for_comparison(),
|
|
153
164
|
}
|
|
154
165
|
|
|
155
166
|
last_state_for_comparison = None
|
|
@@ -174,6 +185,7 @@ class EditActionsManager:
|
|
|
174
185
|
"_next_atom_id": last_state.get("_next_atom_id"),
|
|
175
186
|
"mol_3d": last_state.get("mol_3d", None),
|
|
176
187
|
"mol_3d_atom_ids": last_state.get("mol_3d_atom_ids", None),
|
|
188
|
+
"constraints_3d": last_state.get("constraints_3d") or [],
|
|
177
189
|
}
|
|
178
190
|
|
|
179
191
|
if (
|
|
@@ -116,8 +116,8 @@ class ExportManager:
|
|
|
116
116
|
if not file_path.lower().endswith(".obj"):
|
|
117
117
|
file_path += ".obj"
|
|
118
118
|
|
|
119
|
-
#
|
|
120
|
-
mtl_path =
|
|
119
|
+
# splitext, not str.replace: ".OBJ" must not make mtl_path collide with file_path
|
|
120
|
+
mtl_path = os.path.splitext(file_path)[0] + ".mtl"
|
|
121
121
|
|
|
122
122
|
self.create_multi_material_obj(meshes_with_colors, file_path, mtl_path)
|
|
123
123
|
|
|
@@ -242,6 +242,14 @@ class IOManager:
|
|
|
242
242
|
final_mol.xyz_atom_data = atoms_data
|
|
243
243
|
return final_mol
|
|
244
244
|
|
|
245
|
+
def _report_load_error(self, title: str, message: str) -> None:
|
|
246
|
+
"""Report a load failure on the status bar and, in GUI mode, a dialog."""
|
|
247
|
+
status_bar = self.host.statusBar()
|
|
248
|
+
if status_bar is not None:
|
|
249
|
+
status_bar.showMessage(message)
|
|
250
|
+
if not os.environ.get("MOLEDITPY_HEADLESS"):
|
|
251
|
+
QMessageBox.warning(self.host, title, message)
|
|
252
|
+
|
|
245
253
|
def load_xyz_file(self, file_path: str) -> Optional[Any]:
|
|
246
254
|
"""Load XYZ file and create RDKit Mol with charge prompt and bond determination."""
|
|
247
255
|
if not self.host.state_manager.check_unsaved_changes():
|
|
@@ -349,6 +357,19 @@ class IOManager:
|
|
|
349
357
|
# Unreachable via OK (validated inline)
|
|
350
358
|
return 0, True, False
|
|
351
359
|
|
|
360
|
+
@staticmethod
|
|
361
|
+
def _covalent_radius(symbol: str, atomic_num: int) -> float:
|
|
362
|
+
"""Covalent radius from the local table, RDKit's periodic table for
|
|
363
|
+
elements the table lacks (it stops at V), or 1.0 as a last resort."""
|
|
364
|
+
radius = COVALENT_RADII.get(symbol)
|
|
365
|
+
if radius:
|
|
366
|
+
return radius
|
|
367
|
+
try:
|
|
368
|
+
radius = Chem.GetPeriodicTable().GetRcovalent(atomic_num)
|
|
369
|
+
except (ValueError, RuntimeError):
|
|
370
|
+
radius = 0.0
|
|
371
|
+
return radius if radius > 0.0 else 1.0
|
|
372
|
+
|
|
352
373
|
def estimate_bonds_from_distances(self, mol: Chem.RWMol) -> int:
|
|
353
374
|
"""Estimate bonds based on interatomic distances using covalent radii."""
|
|
354
375
|
conf = mol.GetConformer()
|
|
@@ -364,8 +385,8 @@ class IOManager:
|
|
|
364
385
|
distance = rdMolTransforms.GetBondLength(conf, i, j)
|
|
365
386
|
symbol_i = atom_i.GetSymbol()
|
|
366
387
|
symbol_j = atom_j.GetSymbol()
|
|
367
|
-
radius_i =
|
|
368
|
-
radius_j =
|
|
388
|
+
radius_i = self._covalent_radius(symbol_i, atom_i.GetAtomicNum())
|
|
389
|
+
radius_j = self._covalent_radius(symbol_j, atom_j.GetAtomicNum())
|
|
369
390
|
expected = radius_i + radius_j
|
|
370
391
|
tolerance = 1.2 if (symbol_i == "H" or symbol_j == "H") else 1.3
|
|
371
392
|
if expected * 0.5 <= distance <= expected * tolerance:
|
|
@@ -493,8 +514,9 @@ class IOManager:
|
|
|
493
514
|
elif file_path.lower().endswith(".pmeraw"):
|
|
494
515
|
self.load_raw_data(file_path)
|
|
495
516
|
else:
|
|
496
|
-
self.
|
|
497
|
-
"
|
|
517
|
+
self._report_load_error(
|
|
518
|
+
"Project Load Error",
|
|
519
|
+
"Error: Unable to determine file format or file corrupted.",
|
|
498
520
|
)
|
|
499
521
|
|
|
500
522
|
def save_as_json(self) -> None:
|
|
@@ -592,14 +614,23 @@ class IOManager:
|
|
|
592
614
|
QTimer.singleShot(100, self.host.view_3d_manager.fit_to_view)
|
|
593
615
|
|
|
594
616
|
except FileNotFoundError:
|
|
595
|
-
self.
|
|
617
|
+
self._report_load_error(
|
|
618
|
+
"Project Load Error", f"File not found: {file_path}"
|
|
619
|
+
)
|
|
596
620
|
except json.JSONDecodeError as e:
|
|
597
|
-
self.
|
|
621
|
+
self._report_load_error("Project Load Error", f"Invalid JSON format: {e}")
|
|
598
622
|
except (OSError, IOError) as e:
|
|
599
|
-
self.
|
|
600
|
-
except (
|
|
601
|
-
|
|
602
|
-
|
|
623
|
+
self._report_load_error("Project Load Error", f"File I/O error: {e}")
|
|
624
|
+
except (
|
|
625
|
+
TypeError,
|
|
626
|
+
ValueError,
|
|
627
|
+
RuntimeError,
|
|
628
|
+
AttributeError,
|
|
629
|
+
KeyError,
|
|
630
|
+
IndexError,
|
|
631
|
+
) as e:
|
|
632
|
+
self._report_load_error(
|
|
633
|
+
"Project Load Error", f"Data corruption in PME Project file: {e}"
|
|
603
634
|
)
|
|
604
635
|
|
|
605
636
|
def save_raw_data(self) -> None:
|
|
@@ -757,7 +788,7 @@ class IOManager:
|
|
|
757
788
|
AttributeError,
|
|
758
789
|
KeyError,
|
|
759
790
|
) as e:
|
|
760
|
-
self.
|
|
791
|
+
self._report_load_error("MOL Import Error", f"Error loading file: {e}")
|
|
761
792
|
|
|
762
793
|
def save_as_mol(self) -> None:
|
|
763
794
|
"""Save current 2D structure as MOL file."""
|
|
@@ -842,8 +873,7 @@ class IOManager:
|
|
|
842
873
|
self.host.set_has_unsaved_changes(False)
|
|
843
874
|
self.host.state_manager.update_window_title()
|
|
844
875
|
except (OSError, IOError, ValueError, RuntimeError, AttributeError) as e:
|
|
845
|
-
|
|
846
|
-
self.host.statusBar().showMessage(f"XYZ Load failed: {e}")
|
|
876
|
+
self._report_load_error("XYZ Load Error", f"XYZ Load failed: {e}")
|
|
847
877
|
|
|
848
878
|
def load_mol_file_for_3d_viewing(self, file_path: Optional[str] = None) -> None:
|
|
849
879
|
"""Open MOL/SDF file in 3D viewer."""
|
|
@@ -897,7 +927,7 @@ class IOManager:
|
|
|
897
927
|
self.host.set_has_unsaved_changes(False)
|
|
898
928
|
self.host.state_manager.update_window_title()
|
|
899
929
|
except (OSError, IOError, ValueError, RuntimeError, AttributeError) as e:
|
|
900
|
-
self.
|
|
930
|
+
self._report_load_error("3D MOL Load Error", f"3D MOL Load failed: {e}")
|
|
901
931
|
|
|
902
932
|
def save_3d_as_mol(self) -> None:
|
|
903
933
|
"""Save current 3D structure as MOL file."""
|
|
@@ -1031,13 +1061,26 @@ class IOManager:
|
|
|
1031
1061
|
QTimer.singleShot(100, self._plotter_render)
|
|
1032
1062
|
|
|
1033
1063
|
except FileNotFoundError:
|
|
1034
|
-
self.
|
|
1064
|
+
self._report_load_error(
|
|
1065
|
+
"Project Load Error", f"File not found: {file_path}"
|
|
1066
|
+
)
|
|
1035
1067
|
except (OSError, IOError) as e:
|
|
1036
|
-
self.
|
|
1037
|
-
except pickle.UnpicklingError as e:
|
|
1038
|
-
self.
|
|
1039
|
-
|
|
1040
|
-
|
|
1068
|
+
self._report_load_error("Project Load Error", f"File I/O error: {e}")
|
|
1069
|
+
except (pickle.UnpicklingError, EOFError, ImportError) as e:
|
|
1070
|
+
self._report_load_error(
|
|
1071
|
+
"Project Load Error", f"Invalid project file format: {e}"
|
|
1072
|
+
)
|
|
1073
|
+
except (
|
|
1074
|
+
TypeError,
|
|
1075
|
+
ValueError,
|
|
1076
|
+
RuntimeError,
|
|
1077
|
+
AttributeError,
|
|
1078
|
+
KeyError,
|
|
1079
|
+
IndexError,
|
|
1080
|
+
) as e:
|
|
1081
|
+
self._report_load_error(
|
|
1082
|
+
"Project Load Error", f"Error loading project file: {e}"
|
|
1083
|
+
)
|
|
1041
1084
|
|
|
1042
1085
|
def _set_mol_prop(self, mol: Chem.Mol, prop_name: str, value: Any) -> None:
|
|
1043
1086
|
"""Set an RDKit molecule property safely."""
|
|
@@ -202,7 +202,8 @@ class MainInitManager:
|
|
|
202
202
|
self.host.edit_actions_manager.update_edit_menu_actions()
|
|
203
203
|
|
|
204
204
|
if initial_file:
|
|
205
|
-
|
|
205
|
+
# Deferred: a load-error dialog raised in the constructor would block the window from showing
|
|
206
|
+
QTimer.singleShot(0, lambda: self.load_command_line_file(initial_file))
|
|
206
207
|
|
|
207
208
|
QTimer.singleShot(0, self.apply_initial_settings)
|
|
208
209
|
# Camera initialization flag (permits reset only during the first draw)
|
|
@@ -1195,6 +1196,7 @@ class MainInitManager:
|
|
|
1195
1196
|
|
|
1196
1197
|
file_menu.addSeparator()
|
|
1197
1198
|
quit_action = QAction("Quit", self.host)
|
|
1199
|
+
quit_action.setShortcut("Ctrl+Q")
|
|
1198
1200
|
quit_action.triggered.connect(self.host.close)
|
|
1199
1201
|
file_menu.addAction(quit_action)
|
|
1200
1202
|
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/molecular_scene_handler.py
RENAMED
|
@@ -1099,11 +1099,13 @@ class KeyboardMixin:
|
|
|
1099
1099
|
# 3. Update BondItem properties based on key
|
|
1100
1100
|
if key == Qt.Key.Key_W:
|
|
1101
1101
|
if bond.stereo == 1:
|
|
1102
|
+
# Flip mutates the model without changing order/stereo, so set the undo flag here
|
|
1102
1103
|
bond_data = self.data.bonds.pop(current_key)
|
|
1103
1104
|
new_key = (current_key[1], current_key[0])
|
|
1104
1105
|
self.data.bonds[new_key] = bond_data
|
|
1105
1106
|
bond.atom1, bond.atom2 = bond.atom2, bond.atom1
|
|
1106
1107
|
bond.update_position()
|
|
1108
|
+
any_bond_changed = True
|
|
1107
1109
|
else:
|
|
1108
1110
|
bond.order = 1
|
|
1109
1111
|
bond.stereo = 1
|
|
@@ -1115,6 +1117,7 @@ class KeyboardMixin:
|
|
|
1115
1117
|
self.data.bonds[new_key] = bond_data
|
|
1116
1118
|
bond.atom1, bond.atom2 = bond.atom2, bond.atom1
|
|
1117
1119
|
bond.update_position()
|
|
1120
|
+
any_bond_changed = True
|
|
1118
1121
|
else:
|
|
1119
1122
|
bond.order = 1
|
|
1120
1123
|
bond.stereo = 2
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/move_selected_atoms_dialog.py
RENAMED
|
@@ -696,9 +696,13 @@ class MoveSelectedAtomsDialog(BasePickingDialog):
|
|
|
696
696
|
else:
|
|
697
697
|
btn.setText("Box Selection: OFF")
|
|
698
698
|
plotter.disable_picking()
|
|
699
|
-
# Restore original style
|
|
699
|
+
# Restore original style via pyvista's bookkeeping so its
|
|
700
|
+
# update_style() re-asserts ours instead of RubberBandPick
|
|
700
701
|
if self.original_style is not None:
|
|
701
|
-
|
|
702
|
+
try:
|
|
703
|
+
plotter.iren.style = self.original_style
|
|
704
|
+
except AttributeError:
|
|
705
|
+
plotter.interactor.SetInteractorStyle(self.original_style)
|
|
702
706
|
|
|
703
707
|
def on_rectangle_picked(self, selection: Any) -> None:
|
|
704
708
|
"""Handle PyVista rectangle picking callback."""
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/plugin_menu_manager.py
RENAMED
|
@@ -143,6 +143,20 @@ class PluginMenuManager:
|
|
|
143
143
|
except Exception:
|
|
144
144
|
logging.warning("Plugin rebuild: menu cleanup error", exc_info=True)
|
|
145
145
|
|
|
146
|
+
# Drop tagged (plugin-created) top-level menus the clean pass emptied
|
|
147
|
+
try:
|
|
148
|
+
menu_bar = self._im.host.menuBar()
|
|
149
|
+
for top_action in list(menu_bar.actions()):
|
|
150
|
+
if top_action.data() != self._PLUGIN_ACTION_TAG:
|
|
151
|
+
continue
|
|
152
|
+
submenu = top_action.menu()
|
|
153
|
+
if submenu is None or not any(
|
|
154
|
+
not a.isSeparator() for a in submenu.actions()
|
|
155
|
+
):
|
|
156
|
+
menu_bar.removeAction(top_action)
|
|
157
|
+
except Exception:
|
|
158
|
+
logging.warning("Plugin rebuild: menubar cleanup error", exc_info=True)
|
|
159
|
+
|
|
146
160
|
self._im.plugin_menubar_separator_added = False
|
|
147
161
|
|
|
148
162
|
for method, label in [
|
|
@@ -182,9 +196,11 @@ class PluginMenuManager:
|
|
|
182
196
|
|
|
183
197
|
if not current_menu:
|
|
184
198
|
if not self._im.plugin_menubar_separator_added:
|
|
185
|
-
self._im.host.menuBar().addSeparator()
|
|
199
|
+
sep = self._im.host.menuBar().addSeparator()
|
|
200
|
+
sep.setData(self._PLUGIN_ACTION_TAG)
|
|
186
201
|
self._im.plugin_menubar_separator_added = True
|
|
187
202
|
current_menu = self._im.host.menuBar().addMenu(top_level_title)
|
|
203
|
+
current_menu.menuAction().setData(self._PLUGIN_ACTION_TAG)
|
|
188
204
|
|
|
189
205
|
for part in parts[1:-1]:
|
|
190
206
|
sub = next(
|
|
@@ -322,9 +322,51 @@ class UIManager(QObject):
|
|
|
322
322
|
style = CustomInteractorStyle(self.host)
|
|
323
323
|
|
|
324
324
|
# Set interactor style
|
|
325
|
-
self.
|
|
325
|
+
self._install_interactor_style(style)
|
|
326
326
|
self.host.view_3d_manager.plotter.interactor.Initialize()
|
|
327
327
|
|
|
328
|
+
# Watchdog: if anything silently replaces the custom style (observed as
|
|
329
|
+
# "rotation works but pick/select is dead"), report it and reinstall.
|
|
330
|
+
self._expected_style = style
|
|
331
|
+
self._style_watchdog = QTimer(self)
|
|
332
|
+
self._style_watchdog.setInterval(2000)
|
|
333
|
+
self._style_watchdog.timeout.connect(self._check_interactor_style)
|
|
334
|
+
self._style_watchdog.start()
|
|
335
|
+
|
|
336
|
+
def _install_interactor_style(self, style: Any) -> None:
|
|
337
|
+
"""Install *style* through pyvista's bookkeeping, not raw VTK.
|
|
338
|
+
|
|
339
|
+
pyvista's RenderWindowInteractor re-asserts its own ``_style_class``
|
|
340
|
+
via ``update_style()`` whenever its built-in double-click chart
|
|
341
|
+
handler fires (fast clicks!). Installing through the ``iren.style``
|
|
342
|
+
property keeps that bookkeeping pointing at our style so the
|
|
343
|
+
re-assert is a no-op instead of an eviction.
|
|
344
|
+
"""
|
|
345
|
+
plotter = self.host.view_3d_manager.plotter
|
|
346
|
+
try:
|
|
347
|
+
plotter.iren.style = style
|
|
348
|
+
except AttributeError:
|
|
349
|
+
plotter.interactor.SetInteractorStyle(style)
|
|
350
|
+
|
|
351
|
+
def _check_interactor_style(self) -> None:
|
|
352
|
+
"""Reinstall CustomInteractorStyle if it was silently replaced."""
|
|
353
|
+
try:
|
|
354
|
+
interactor = self.host.view_3d_manager.plotter.interactor
|
|
355
|
+
current = interactor.GetInteractorStyle()
|
|
356
|
+
if current is self._expected_style:
|
|
357
|
+
return
|
|
358
|
+
name = type(current).__name__
|
|
359
|
+
if "RubberBand" in name: # legitimate temporary box-selection style
|
|
360
|
+
return
|
|
361
|
+
logging.debug(
|
|
362
|
+
"3D interactor style was replaced by %s — reinstalling "
|
|
363
|
+
"CustomInteractorStyle",
|
|
364
|
+
name,
|
|
365
|
+
)
|
|
366
|
+
self._install_interactor_style(self._expected_style)
|
|
367
|
+
except (AttributeError, RuntimeError):
|
|
368
|
+
logging.debug("Style watchdog check failed", exc_info=True)
|
|
369
|
+
|
|
328
370
|
def handle_drag_enter_event(self, event: QDragEnterEvent) -> None:
|
|
329
371
|
"""Internal handler for drag enter event (bypasses PyQt type checks in tests)."""
|
|
330
372
|
mime_data = event.mimeData()
|
|
@@ -1,105 +0,0 @@
|
|
|
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
|
-
import time
|
|
14
|
-
from typing import Any, Optional
|
|
15
|
-
|
|
16
|
-
from pyvistaqt import QtInteractor
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class CustomQtInteractor(QtInteractor):
|
|
20
|
-
"""PyVista QtInteractor subclass that exposes the main window for event handling."""
|
|
21
|
-
|
|
22
|
-
def __init__(
|
|
23
|
-
self,
|
|
24
|
-
parent: Optional[Any] = None,
|
|
25
|
-
main_window: Optional[Any] = None,
|
|
26
|
-
**kwargs: Any,
|
|
27
|
-
) -> None:
|
|
28
|
-
super().__init__(parent, **kwargs)
|
|
29
|
-
self.main_window = main_window
|
|
30
|
-
self._last_click_time = 0.0
|
|
31
|
-
self._click_count = 0
|
|
32
|
-
self._ignore_next_release = False
|
|
33
|
-
|
|
34
|
-
def wheelEvent(self, event: Any) -> None:
|
|
35
|
-
"""
|
|
36
|
-
Override the mouse wheel event.
|
|
37
|
-
"""
|
|
38
|
-
# First call the parent class event to perform normal zoom processing
|
|
39
|
-
super().wheelEvent(event)
|
|
40
|
-
|
|
41
|
-
# Force focus back to the 2D view after zoom processing
|
|
42
|
-
if self.main_window and hasattr(self.main_window.init_manager, "view_2d"):
|
|
43
|
-
self.main_window.init_manager.view_2d.setFocus()
|
|
44
|
-
|
|
45
|
-
def mouseReleaseEvent(self, event: Any) -> None:
|
|
46
|
-
"""
|
|
47
|
-
Override the Qt mouse release event to return focus to the 2D view after
|
|
48
|
-
all 3D view operations. Also filters out "Ghost Release" (release without
|
|
49
|
-
a corresponding press).
|
|
50
|
-
"""
|
|
51
|
-
if self._ignore_next_release:
|
|
52
|
-
self._ignore_next_release = False
|
|
53
|
-
event.accept()
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
super().mouseReleaseEvent(event) # Process parent class event first
|
|
57
|
-
if self.main_window and hasattr(self.main_window.init_manager, "view_2d"):
|
|
58
|
-
self.main_window.init_manager.view_2d.setFocus()
|
|
59
|
-
|
|
60
|
-
def mousePressEvent(self, event: Any) -> None:
|
|
61
|
-
"""
|
|
62
|
-
Custom mouse press handling to track accumulated clicks and filter out
|
|
63
|
-
triple-clicks.
|
|
64
|
-
"""
|
|
65
|
-
current_time = time.time()
|
|
66
|
-
# Reset count if too much time has passed (0.5s is standard double-click time)
|
|
67
|
-
if current_time - self._last_click_time > 0.5:
|
|
68
|
-
self._click_count = 0
|
|
69
|
-
|
|
70
|
-
self._click_count += 1
|
|
71
|
-
self._last_click_time = current_time
|
|
72
|
-
|
|
73
|
-
# If this is the 3rd click (or more), swallow it to prevent
|
|
74
|
-
# the internal state desync that happens with rapid clicking sequences.
|
|
75
|
-
if self._click_count >= 3:
|
|
76
|
-
self._ignore_next_release = True
|
|
77
|
-
event.accept()
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
super().mousePressEvent(event)
|
|
81
|
-
|
|
82
|
-
def mouseDoubleClickEvent(self, event: Any) -> None:
|
|
83
|
-
"""Ignore mouse double-clicks on the 3D widget to avoid accidental actions.
|
|
84
|
-
|
|
85
|
-
Swallow the double-click event so it doesn't trigger selection, editing,
|
|
86
|
-
or camera jumps. We intentionally do not call the superclass handler.
|
|
87
|
-
Crucially, we also flag the NEXT release event to be swallowed, preventing
|
|
88
|
-
a "Ghost Release" (Release without Press) from reaching VTK.
|
|
89
|
-
"""
|
|
90
|
-
current_time = time.time()
|
|
91
|
-
self._last_click_time = current_time
|
|
92
|
-
# Set to 2 to ensure the next click counts as 3rd
|
|
93
|
-
if current_time - self._last_click_time < 0.5:
|
|
94
|
-
self._click_count = 2
|
|
95
|
-
else:
|
|
96
|
-
self._click_count = 2 # Force sync
|
|
97
|
-
|
|
98
|
-
self._ignore_next_release = True
|
|
99
|
-
|
|
100
|
-
try:
|
|
101
|
-
# Accept the event to mark it handled and prevent further processing.
|
|
102
|
-
event.accept()
|
|
103
|
-
except (AttributeError, RuntimeError, TypeError):
|
|
104
|
-
# If event doesn't support accept for some reason, just return.
|
|
105
|
-
return
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/MoleditPy_linux.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/MoleditPy_linux.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/plugins/plugin_interface.py
RENAMED
|
File without changes
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/plugins/plugin_manager.py
RENAMED
|
File without changes
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/plugins/plugin_manager_window.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/align_plane_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/base_picking_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/bond_length_dialog.py
RENAMED
|
File without changes
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/calculation_worker.py
RENAMED
|
File without changes
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/color_settings_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/geometry_base_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/periodic_table_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/settings_tabs/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/template_preview_item.py
RENAMED
|
File without changes
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/template_preview_view.py
RENAMED
|
File without changes
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/translation_dialog.py
RENAMED
|
File without changes
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/ui/user_template_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/utils/default_settings.py
RENAMED
|
File without changes
|
{moleditpy_linux-4.3.0 → moleditpy_linux-4.3.2}/src/moleditpy_linux/utils/sip_isdeleted_safe.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|