MoleditPy-linux 3.0.5__tar.gz → 3.1.0__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-3.0.5 → moleditpy_linux-3.1.0}/PKG-INFO +1 -1
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/pyproject.toml +1 -1
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/MoleditPy_linux.egg-info/PKG-INFO +1 -1
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/__main__.py +1 -1
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/main.py +60 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/plugins/plugin_manager.py +42 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/plugins/plugin_manager_window.py +2 -38
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/align_plane_dialog.py +0 -2
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/angle_dialog.py +0 -2
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/app_state.py +2 -2
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/bond_length_dialog.py +0 -2
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/dialog_3d_picking_mixin.py +26 -3
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/dialog_logic.py +6 -10
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/dihedral_dialog.py +0 -2
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/io_logic.py +21 -25
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/main_window_init.py +6 -2
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/planarize_dialog.py +0 -1
- moleditpy_linux-3.1.0/src/moleditpy_linux/ui/string_importers.py +208 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/translation_dialog.py +31 -8
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/user_template_dialog.py +0 -4
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/view_3d_logic.py +3 -2
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/utils/constants.py +1 -1
- moleditpy_linux-3.0.5/src/moleditpy_linux/ui/string_importers.py +0 -257
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/LICENSE +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/README.md +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/setup.cfg +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/MoleditPy_linux.egg-info/SOURCES.txt +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/MoleditPy_linux.egg-info/dependency_links.txt +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/MoleditPy_linux.egg-info/entry_points.txt +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/MoleditPy_linux.egg-info/requires.txt +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/MoleditPy_linux.egg-info/top_level.txt +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/__init__.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/assets/file_icon.ico +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/assets/icon.icns +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/assets/icon.ico +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/assets/icon.png +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/core/__init__.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/core/mol_geometry.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/core/molecular_data.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/plugins/__init__.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/plugins/plugin_interface.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/__init__.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/about_dialog.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/alignment_dialog.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/analysis_window.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/atom_item.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/base_picking_dialog.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/bond_item.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/calculation_worker.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/color_settings_dialog.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/compute_logic.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/constrained_optimization_dialog.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/custom_interactor_style.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/custom_qt_interactor.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/edit_3d_logic.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/edit_actions_logic.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/export_logic.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/geometry_base_dialog.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/main_window.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/mirror_dialog.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/molecular_scene_handler.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/molecule_scene.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/move_group_dialog.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/periodic_table_dialog.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/settings_dialog.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/settings_tabs/__init__.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/settings_tabs/settings_2d_tab.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/settings_tabs/settings_3d_tabs.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/settings_tabs/settings_other_tab.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/settings_tabs/settings_tab_base.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/sip_isdeleted_safe.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/template_preview_item.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/template_preview_view.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/ui_manager.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/zoomable_view.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/utils/__init__.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/utils/default_settings.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/utils/sip_isdeleted_safe.py +0 -0
- {moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/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: 3.0
|
|
3
|
+
Version: 3.1.0
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy-linux
|
|
3
|
-
Version: 3.0
|
|
3
|
+
Version: 3.1.0
|
|
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
|
|
@@ -11,7 +11,7 @@ DOI: 10.5281/zenodo.17268532
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
print("-----------------------------------------------------")
|
|
14
|
-
print("MoleditPy
|
|
14
|
+
print("MoleditPy - A Python-based molecular editing software")
|
|
15
15
|
print("-----------------------------------------------------\n")
|
|
16
16
|
|
|
17
17
|
try:
|
|
@@ -80,9 +80,69 @@ def main():
|
|
|
80
80
|
default=False,
|
|
81
81
|
help="Start in safe mode: skip loading all plugins",
|
|
82
82
|
)
|
|
83
|
+
parser.add_argument(
|
|
84
|
+
"--install-plugin",
|
|
85
|
+
metavar="PATH",
|
|
86
|
+
help="Install a plugin from a .py file, .zip, or folder (Headless)",
|
|
87
|
+
)
|
|
83
88
|
# parse_known_args so Qt's own argv flags (e.g. -platform) are passed through
|
|
84
89
|
args, remaining = parser.parse_known_args()
|
|
85
90
|
|
|
91
|
+
# --- Headless Plugin Installation ---
|
|
92
|
+
if args.install_plugin:
|
|
93
|
+
plugin_path = os.path.abspath(args.install_plugin)
|
|
94
|
+
if not os.path.exists(plugin_path):
|
|
95
|
+
print(f"Error: Plugin path not found: {plugin_path}")
|
|
96
|
+
sys.exit(1)
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
from moleditpy_linux.plugins.plugin_manager import PluginManager
|
|
100
|
+
except ImportError:
|
|
101
|
+
from .plugins.plugin_manager import PluginManager
|
|
102
|
+
|
|
103
|
+
pm = PluginManager()
|
|
104
|
+
sha256 = pm._compute_sha256(plugin_path)
|
|
105
|
+
|
|
106
|
+
# Extract metadata
|
|
107
|
+
metadata_file = plugin_path
|
|
108
|
+
if os.path.isdir(plugin_path):
|
|
109
|
+
init_py = os.path.join(plugin_path, "__init__.py")
|
|
110
|
+
if os.path.exists(init_py):
|
|
111
|
+
metadata_file = init_py
|
|
112
|
+
|
|
113
|
+
info = (
|
|
114
|
+
pm.get_plugin_info_safe(metadata_file)
|
|
115
|
+
if metadata_file.endswith(".py")
|
|
116
|
+
else {}
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
print("\n" + "=" * 40)
|
|
120
|
+
print(" PLUGIN INSTALLATION (HEADLESS)")
|
|
121
|
+
print("=" * 40)
|
|
122
|
+
print(f" Name: {info.get('name', os.path.basename(plugin_path))}")
|
|
123
|
+
print(f" Author: {info.get('author', 'Unknown')}")
|
|
124
|
+
print(f" Version: {info.get('version', 'Unknown')}")
|
|
125
|
+
print(f" Description: {info.get('description', 'No description')}")
|
|
126
|
+
print("-" * 40)
|
|
127
|
+
print(f" Path: {plugin_path}")
|
|
128
|
+
print(f" SHA-256: {sha256}")
|
|
129
|
+
print("=" * 40)
|
|
130
|
+
|
|
131
|
+
confirm = (
|
|
132
|
+
input("\nDo you want to proceed with installation? (y/N): ").strip().lower()
|
|
133
|
+
)
|
|
134
|
+
if confirm == "y":
|
|
135
|
+
success, msg = pm.install_plugin(plugin_path)
|
|
136
|
+
if success:
|
|
137
|
+
print(f"Success: {msg}")
|
|
138
|
+
sys.exit(0)
|
|
139
|
+
else:
|
|
140
|
+
print(f"Error: {msg}")
|
|
141
|
+
sys.exit(1)
|
|
142
|
+
else:
|
|
143
|
+
print("Installation aborted.")
|
|
144
|
+
sys.exit(0)
|
|
145
|
+
|
|
86
146
|
app = QApplication([sys.argv[0]] + remaining)
|
|
87
147
|
window = MainWindow(initial_file=args.file, safe_mode=args.safe)
|
|
88
148
|
window.show()
|
{moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/plugins/plugin_manager.py
RENAMED
|
@@ -12,6 +12,7 @@ DOI: 10.5281/zenodo.17268532
|
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
import ast
|
|
15
|
+
import hashlib
|
|
15
16
|
import importlib.util
|
|
16
17
|
import logging
|
|
17
18
|
import os
|
|
@@ -32,6 +33,46 @@ except ImportError:
|
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
class PluginManager:
|
|
36
|
+
def _compute_sha256(self, path: str) -> str:
|
|
37
|
+
"""Computes SHA-256 for a file or a directory (concatenated hashes of all files)."""
|
|
38
|
+
if os.path.isfile(path):
|
|
39
|
+
return self._sha256_for_file(path)
|
|
40
|
+
if os.path.isdir(path):
|
|
41
|
+
return self._sha256_for_directory(path)
|
|
42
|
+
return "N/A"
|
|
43
|
+
|
|
44
|
+
def _sha256_for_file(self, path: str) -> str:
|
|
45
|
+
"""Computes SHA-256 for a single file."""
|
|
46
|
+
hasher = hashlib.sha256()
|
|
47
|
+
try:
|
|
48
|
+
with open(path, "rb") as f:
|
|
49
|
+
for chunk in iter(lambda: f.read(8192), b""):
|
|
50
|
+
hasher.update(chunk)
|
|
51
|
+
return hasher.hexdigest()
|
|
52
|
+
except (AttributeError, OSError, RuntimeError, ValueError, TypeError):
|
|
53
|
+
return "N/A"
|
|
54
|
+
|
|
55
|
+
def _sha256_for_directory(self, dir_path: str) -> str:
|
|
56
|
+
"""Computes SHA-256 for a directory by hashing all files in sorted order."""
|
|
57
|
+
hasher = hashlib.sha256()
|
|
58
|
+
try:
|
|
59
|
+
root = os.path.abspath(dir_path)
|
|
60
|
+
for current_root, _dirs, files in os.walk(root):
|
|
61
|
+
rel_root = os.path.relpath(current_root, root)
|
|
62
|
+
for filename in sorted(files):
|
|
63
|
+
file_path = os.path.join(current_root, filename)
|
|
64
|
+
rel_path = os.path.normpath(os.path.join(rel_root, filename))
|
|
65
|
+
# Hash the path to ensure directory structure is captured
|
|
66
|
+
hasher.update(rel_path.encode("utf-8", errors="replace"))
|
|
67
|
+
hasher.update(b"\0")
|
|
68
|
+
with open(file_path, "rb") as f:
|
|
69
|
+
for chunk in iter(lambda: f.read(8192), b""):
|
|
70
|
+
hasher.update(chunk)
|
|
71
|
+
hasher.update(b"\0")
|
|
72
|
+
return hasher.hexdigest()
|
|
73
|
+
except (AttributeError, OSError, RuntimeError, ValueError, TypeError):
|
|
74
|
+
return "N/A"
|
|
75
|
+
|
|
35
76
|
def __init__(self, main_window: Any = None) -> None:
|
|
36
77
|
self.plugin_dir: str = os.path.join(
|
|
37
78
|
os.path.expanduser("~"), ".moleditpy", "plugins"
|
|
@@ -265,6 +306,7 @@ class PluginManager:
|
|
|
265
306
|
parent_name = ".".join(parts[:i])
|
|
266
307
|
if parent_name not in sys.modules:
|
|
267
308
|
import types as _types
|
|
309
|
+
|
|
268
310
|
stub = _types.ModuleType(parent_name)
|
|
269
311
|
stub.__path__ = []
|
|
270
312
|
stub.__package__ = parent_name
|
{moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/plugins/plugin_manager_window.py
RENAMED
|
@@ -12,7 +12,7 @@ DOI: 10.5281/zenodo.17268532
|
|
|
12
12
|
|
|
13
13
|
import os
|
|
14
14
|
import shutil
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
|
|
17
17
|
from PyQt6.QtCore import Qt, QUrl
|
|
18
18
|
from PyQt6.QtGui import QDesktopServices, QDragEnterEvent, QDropEvent
|
|
@@ -259,7 +259,7 @@ class PluginManagerWindow(QDialog):
|
|
|
259
259
|
is_folder = True
|
|
260
260
|
|
|
261
261
|
if is_valid:
|
|
262
|
-
sha256_value = self._compute_sha256(file_path)
|
|
262
|
+
sha256_value = self.plugin_manager._compute_sha256(file_path)
|
|
263
263
|
# Extract info and confirm
|
|
264
264
|
info = {
|
|
265
265
|
"name": os.path.basename(file_path),
|
|
@@ -314,39 +314,3 @@ class PluginManagerWindow(QDialog):
|
|
|
314
314
|
summary += "Errors:\n" + "\n".join(errors)
|
|
315
315
|
|
|
316
316
|
QMessageBox.information(self, "Plugin Installation", summary)
|
|
317
|
-
|
|
318
|
-
def _compute_sha256(self, path):
|
|
319
|
-
if os.path.isfile(path):
|
|
320
|
-
return self._sha256_for_file(path)
|
|
321
|
-
if os.path.isdir(path):
|
|
322
|
-
return self._sha256_for_directory(path)
|
|
323
|
-
return "N/A"
|
|
324
|
-
|
|
325
|
-
def _sha256_for_file(self, path):
|
|
326
|
-
hasher = hashlib.sha256()
|
|
327
|
-
try:
|
|
328
|
-
with open(path, "rb") as f:
|
|
329
|
-
for chunk in iter(lambda: f.read(8192), b""):
|
|
330
|
-
hasher.update(chunk)
|
|
331
|
-
return hasher.hexdigest()
|
|
332
|
-
except (AttributeError, OSError, RuntimeError, ValueError, TypeError):
|
|
333
|
-
return "N/A"
|
|
334
|
-
|
|
335
|
-
def _sha256_for_directory(self, dir_path):
|
|
336
|
-
hasher = hashlib.sha256()
|
|
337
|
-
try:
|
|
338
|
-
root = os.path.abspath(dir_path)
|
|
339
|
-
for current_root, _dirs, files in os.walk(root):
|
|
340
|
-
rel_root = os.path.relpath(current_root, root)
|
|
341
|
-
for filename in sorted(files):
|
|
342
|
-
file_path = os.path.join(current_root, filename)
|
|
343
|
-
rel_path = os.path.normpath(os.path.join(rel_root, filename))
|
|
344
|
-
hasher.update(rel_path.encode("utf-8", errors="replace"))
|
|
345
|
-
hasher.update(b"\0")
|
|
346
|
-
with open(file_path, "rb") as f:
|
|
347
|
-
for chunk in iter(lambda: f.read(8192), b""):
|
|
348
|
-
hasher.update(chunk)
|
|
349
|
-
hasher.update(b"\0")
|
|
350
|
-
return hasher.hexdigest()
|
|
351
|
-
except (AttributeError, OSError, RuntimeError, ValueError, TypeError):
|
|
352
|
-
return "N/A"
|
|
@@ -364,9 +364,9 @@ class StateManager:
|
|
|
364
364
|
# 'Save As' if not PMEPRJ
|
|
365
365
|
file_path = self.host.init_manager.current_file_path
|
|
366
366
|
if not file_path or not file_path.lower().endswith(".pmeprj"):
|
|
367
|
-
self.save_project_as()
|
|
367
|
+
self.host.io_manager.save_project_as()
|
|
368
368
|
else:
|
|
369
|
-
self.save_project()
|
|
369
|
+
self.host.io_manager.save_project()
|
|
370
370
|
return (
|
|
371
371
|
not self.host.state_manager.has_unsaved_changes
|
|
372
372
|
) # Return True only if save was successful
|
{moleditpy_linux-3.0.5 → moleditpy_linux-3.1.0}/src/moleditpy_linux/ui/dialog_3d_picking_mixin.py
RENAMED
|
@@ -192,9 +192,14 @@ class Dialog3DPickingMixin:
|
|
|
192
192
|
color : str, optional
|
|
193
193
|
Label colour (default ``'yellow'``).
|
|
194
194
|
"""
|
|
195
|
-
|
|
195
|
+
plotter = self.main_window.view_3d_manager.plotter
|
|
196
|
+
try:
|
|
197
|
+
cam = plotter.camera_position
|
|
198
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
199
|
+
cam = None
|
|
196
200
|
|
|
197
|
-
|
|
201
|
+
pos = self.main_window.view_3d_manager.atom_positions_3d[atom_idx]
|
|
202
|
+
label_actor = plotter.add_point_labels(
|
|
198
203
|
[pos],
|
|
199
204
|
[label_text],
|
|
200
205
|
point_size=20,
|
|
@@ -204,6 +209,12 @@ class Dialog3DPickingMixin:
|
|
|
204
209
|
)
|
|
205
210
|
self.selection_labels.append(label_actor)
|
|
206
211
|
|
|
212
|
+
if cam is not None:
|
|
213
|
+
try:
|
|
214
|
+
plotter.camera_position = cam
|
|
215
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
216
|
+
pass
|
|
217
|
+
|
|
207
218
|
def show_atom_labels_for(self, atoms_and_labels, color="yellow"):
|
|
208
219
|
"""Clear existing labels and add new ones for each *(idx, text)* pair.
|
|
209
220
|
|
|
@@ -214,11 +225,17 @@ class Dialog3DPickingMixin:
|
|
|
214
225
|
color : str, optional
|
|
215
226
|
Label colour (default ``'yellow'``).
|
|
216
227
|
"""
|
|
228
|
+
plotter = self.main_window.view_3d_manager.plotter
|
|
229
|
+
try:
|
|
230
|
+
cam = plotter.camera_position
|
|
231
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
232
|
+
cam = None
|
|
233
|
+
|
|
217
234
|
self.clear_atom_labels()
|
|
218
235
|
|
|
219
236
|
for atom_idx, label_text in atoms_and_labels:
|
|
220
237
|
pos = self.main_window.view_3d_manager.atom_positions_3d[atom_idx]
|
|
221
|
-
label_actor =
|
|
238
|
+
label_actor = plotter.add_point_labels(
|
|
222
239
|
[pos],
|
|
223
240
|
[label_text],
|
|
224
241
|
point_size=20,
|
|
@@ -227,3 +244,9 @@ class Dialog3DPickingMixin:
|
|
|
227
244
|
always_visible=True,
|
|
228
245
|
)
|
|
229
246
|
self.selection_labels.append(label_actor)
|
|
247
|
+
|
|
248
|
+
if cam is not None:
|
|
249
|
+
try:
|
|
250
|
+
plotter.camera_position = cam
|
|
251
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
252
|
+
pass
|
|
@@ -200,10 +200,6 @@ class DialogManager:
|
|
|
200
200
|
with open(filepath, "w", encoding="utf-8") as f:
|
|
201
201
|
json.dump(template_data, f, indent=2, ensure_ascii=False)
|
|
202
202
|
|
|
203
|
-
# Mark as saved (no unsaved changes for this operation)
|
|
204
|
-
self.host.state_manager.has_unsaved_changes = False
|
|
205
|
-
self.host.state_manager.update_window_title()
|
|
206
|
-
|
|
207
203
|
QMessageBox.information(
|
|
208
204
|
self.host, "Success", f"Template '{name}' saved successfully."
|
|
209
205
|
)
|
|
@@ -215,14 +211,14 @@ class DialogManager:
|
|
|
215
211
|
|
|
216
212
|
def open_translation_dialog(self):
|
|
217
213
|
"""Open the translation dialog"""
|
|
214
|
+
# Get preselected atoms
|
|
215
|
+
preselected_atoms = self._get_preselected_atoms_3d()
|
|
216
|
+
|
|
218
217
|
# Disable measurement mode
|
|
219
218
|
if self.host.edit_3d_manager.measurement_mode:
|
|
220
219
|
self.host.init_manager.measurement_action.setChecked(False)
|
|
221
220
|
self.host.edit_3d_manager.toggle_measurement_mode(False)
|
|
222
221
|
|
|
223
|
-
# Get preselected atoms
|
|
224
|
-
preselected_atoms = self._get_preselected_atoms_3d()
|
|
225
|
-
|
|
226
222
|
dialog = TranslationDialog(
|
|
227
223
|
self.host.view_3d_manager.current_mol,
|
|
228
224
|
self.host,
|
|
@@ -241,14 +237,14 @@ class DialogManager:
|
|
|
241
237
|
|
|
242
238
|
def open_move_group_dialog(self):
|
|
243
239
|
"""Open Move Group dialog"""
|
|
240
|
+
# Get preselected atoms
|
|
241
|
+
preselected_atoms = self._get_preselected_atoms_3d()
|
|
242
|
+
|
|
244
243
|
# Disable measurement mode
|
|
245
244
|
if self.host.edit_3d_manager.measurement_mode:
|
|
246
245
|
self.host.init_manager.measurement_action.setChecked(False)
|
|
247
246
|
self.host.edit_3d_manager.toggle_measurement_mode(False)
|
|
248
247
|
|
|
249
|
-
# Get preselected atoms
|
|
250
|
-
preselected_atoms = self._get_preselected_atoms_3d()
|
|
251
|
-
|
|
252
248
|
dialog = MoveGroupDialog(
|
|
253
249
|
self.host.view_3d_manager.current_mol,
|
|
254
250
|
self.host,
|
|
@@ -564,10 +564,7 @@ class IOManager:
|
|
|
564
564
|
self.host.statusBar().showMessage(f"Export error: {e}")
|
|
565
565
|
|
|
566
566
|
def load_mol_file(self, file_path: Optional[str] = None) -> None:
|
|
567
|
-
"""
|
|
568
|
-
if not self.host.state_manager.check_unsaved_changes():
|
|
569
|
-
return
|
|
570
|
-
|
|
567
|
+
"""Import a MOL/SDF file and add its contents to the 2D editor."""
|
|
571
568
|
if not file_path:
|
|
572
569
|
default_dir = (
|
|
573
570
|
os.path.dirname(self.host.init_manager.current_file_path)
|
|
@@ -601,11 +598,6 @@ class IOManager:
|
|
|
601
598
|
raise ValueError("Failed to read molecule from file.")
|
|
602
599
|
|
|
603
600
|
Chem.Kekulize(mol)
|
|
604
|
-
self.host.ui_manager.restore_ui_for_editing()
|
|
605
|
-
self.host.edit_actions_manager.clear_2d_editor(push_to_undo=False)
|
|
606
|
-
self.host.view_3d_manager.current_mol = None
|
|
607
|
-
self.host.view_3d_manager.plotter.clear()
|
|
608
|
-
self.host.init_manager.analysis_action.setEnabled(False)
|
|
609
601
|
|
|
610
602
|
if mol.GetNumConformers() == 0:
|
|
611
603
|
AllChem.Compute2DCoords(mol)
|
|
@@ -615,9 +607,22 @@ class IOManager:
|
|
|
615
607
|
AllChem.WedgeMolBonds(mol, conf)
|
|
616
608
|
|
|
617
609
|
SCALE_FACTOR = 50.0
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
610
|
+
existing_atoms = self.host.state_manager.data.atoms
|
|
611
|
+
if existing_atoms:
|
|
612
|
+
max_x = max(
|
|
613
|
+
v["pos"].x() if hasattr(v["pos"], "x") else v["pos"][0]
|
|
614
|
+
for v in existing_atoms.values()
|
|
615
|
+
)
|
|
616
|
+
avg_y = sum(
|
|
617
|
+
v["pos"].y() if hasattr(v["pos"], "y") else v["pos"][1]
|
|
618
|
+
for v in existing_atoms.values()
|
|
619
|
+
) / len(existing_atoms)
|
|
620
|
+
place_center = QPointF(max_x + 80.0, avg_y)
|
|
621
|
+
else:
|
|
622
|
+
place_center = self.host.init_manager.view_2d.mapToScene(
|
|
623
|
+
self.host.init_manager.view_2d.viewport().rect().center()
|
|
624
|
+
)
|
|
625
|
+
|
|
621
626
|
positions = [conf.GetAtomPosition(i) for i in range(mol.GetNumAtoms())]
|
|
622
627
|
mol_center_x = (
|
|
623
628
|
sum(p.x for p in positions) / len(positions) if positions else 0.0
|
|
@@ -630,8 +635,8 @@ class IOManager:
|
|
|
630
635
|
for i in range(mol.GetNumAtoms()):
|
|
631
636
|
atom = mol.GetAtomWithIdx(i)
|
|
632
637
|
pos = conf.GetAtomPosition(i)
|
|
633
|
-
scene_x = ((pos.x - mol_center_x) * SCALE_FACTOR) +
|
|
634
|
-
scene_y = (-(pos.y - mol_center_y) * SCALE_FACTOR) +
|
|
638
|
+
scene_x = ((pos.x - mol_center_x) * SCALE_FACTOR) + place_center.x()
|
|
639
|
+
scene_y = (-(pos.y - mol_center_y) * SCALE_FACTOR) + place_center.y()
|
|
635
640
|
atom_id = self.host.init_manager.scene.create_atom(
|
|
636
641
|
atom.GetSymbol(),
|
|
637
642
|
QPointF(scene_x, scene_y),
|
|
@@ -662,18 +667,9 @@ class IOManager:
|
|
|
662
667
|
bond_stereo=stereo,
|
|
663
668
|
)
|
|
664
669
|
|
|
665
|
-
self.host.statusBar().showMessage(f"Successfully
|
|
666
|
-
self.host.state_manager.reset_undo_stack()
|
|
667
|
-
self.host.init_manager.current_file_path = file_path
|
|
668
|
-
self.host.state_manager.has_unsaved_changes = False
|
|
669
|
-
self.host.state_manager.update_window_title()
|
|
670
|
+
self.host.statusBar().showMessage(f"Successfully imported {file_path}")
|
|
670
671
|
self.host.init_manager.scene.update_all_items()
|
|
671
|
-
|
|
672
|
-
# Reset camera/zoom after drawing
|
|
673
|
-
QTimer.singleShot(
|
|
674
|
-
50, lambda: self.host.view_3d_manager.plotter.view_isometric()
|
|
675
|
-
)
|
|
676
|
-
QTimer.singleShot(100, lambda: self.host.view_3d_manager.plotter.render())
|
|
672
|
+
self.host.edit_actions_manager.push_undo_state()
|
|
677
673
|
QTimer.singleShot(100, self.host.view_3d_manager.fit_to_view)
|
|
678
674
|
except Exception as e:
|
|
679
675
|
self.host.statusBar().showMessage(f"Error loading file: {e}")
|
|
@@ -264,7 +264,10 @@ class MainInitManager:
|
|
|
264
264
|
file_ext = ext_with_dot.lstrip(".")
|
|
265
265
|
|
|
266
266
|
# 1. Custom Plugin Openers
|
|
267
|
-
if
|
|
267
|
+
if (
|
|
268
|
+
self.host.plugin_manager
|
|
269
|
+
and ext_with_dot in self.host.plugin_manager.file_openers
|
|
270
|
+
):
|
|
268
271
|
openers = self.host.plugin_manager.file_openers[ext_with_dot]
|
|
269
272
|
# Iterate through openers (already sorted by priority)
|
|
270
273
|
for opener_info in openers:
|
|
@@ -279,7 +282,8 @@ class MainInitManager:
|
|
|
279
282
|
except Exception as e:
|
|
280
283
|
logging.warning(
|
|
281
284
|
"Plugin opener failed for '%s': %s",
|
|
282
|
-
opener_info.get(
|
|
285
|
+
opener_info.get("plugin", "Unknown"),
|
|
286
|
+
e,
|
|
283
287
|
)
|
|
284
288
|
# If this opener fails, try the next one or fall through to default
|
|
285
289
|
continue
|