MoleditPy-linux 4.9.3__tar.gz → 4.10.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.
Files changed (85) hide show
  1. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/PKG-INFO +1 -1
  2. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/pyproject.toml +1 -1
  3. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/MoleditPy_linux.egg-info/PKG-INFO +1 -1
  4. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/plugins/plugin_manager.py +72 -2
  5. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/plugins/plugin_manager_window.py +43 -1
  6. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/main_window_init.py +0 -1
  7. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/plugin_menu_manager.py +0 -1
  8. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/LICENSE +0 -0
  9. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/README.md +0 -0
  10. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/setup.cfg +0 -0
  11. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/MoleditPy_linux.egg-info/SOURCES.txt +0 -0
  12. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/MoleditPy_linux.egg-info/dependency_links.txt +0 -0
  13. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/MoleditPy_linux.egg-info/entry_points.txt +0 -0
  14. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/MoleditPy_linux.egg-info/requires.txt +0 -0
  15. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/MoleditPy_linux.egg-info/top_level.txt +0 -0
  16. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/__init__.py +0 -0
  17. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/__main__.py +0 -0
  18. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/assets/file_icon.ico +0 -0
  19. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/assets/icon.icns +0 -0
  20. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/assets/icon.ico +0 -0
  21. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/assets/icon.png +0 -0
  22. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/core/__init__.py +0 -0
  23. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/core/mol_geometry.py +0 -0
  24. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/core/molecular_data.py +0 -0
  25. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/main.py +0 -0
  26. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/plugins/__init__.py +0 -0
  27. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/plugins/plugin_interface.py +0 -0
  28. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/__init__.py +0 -0
  29. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/about_dialog.py +0 -0
  30. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/align_plane_dialog.py +0 -0
  31. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/alignment_dialog.py +0 -0
  32. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/analysis_window.py +0 -0
  33. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/angle_dialog.py +0 -0
  34. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/app_state.py +0 -0
  35. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/atom_item.py +0 -0
  36. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/atom_picking.py +0 -0
  37. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/base_picking_dialog.py +0 -0
  38. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/bond_item.py +0 -0
  39. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/bond_length_dialog.py +0 -0
  40. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/calculation_worker.py +0 -0
  41. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/chain_mixin.py +0 -0
  42. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/color_settings_dialog.py +0 -0
  43. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/compute_logic.py +0 -0
  44. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/constrained_optimization_dialog.py +0 -0
  45. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/custom_interactor_style.py +0 -0
  46. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/custom_qt_interactor.py +0 -0
  47. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/dialog_3d_picking_mixin.py +0 -0
  48. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/dialog_logic.py +0 -0
  49. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/dihedral_dialog.py +0 -0
  50. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/edit_3d_logic.py +0 -0
  51. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/edit_actions_logic.py +0 -0
  52. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/export_logic.py +0 -0
  53. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/geometry_base_dialog.py +0 -0
  54. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/io_logic.py +0 -0
  55. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/main_window.py +0 -0
  56. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/mirror_dialog.py +0 -0
  57. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/molecular_scene_handler.py +0 -0
  58. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/molecule_scene.py +0 -0
  59. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/move_group_dialog.py +0 -0
  60. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/move_selected_atoms_dialog.py +0 -0
  61. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/periodic_table_dialog.py +0 -0
  62. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/planarize_dialog.py +0 -0
  63. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/preview_molecule.py +0 -0
  64. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/settings_dialog.py +0 -0
  65. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/settings_tabs/__init__.py +0 -0
  66. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/settings_tabs/settings_2d_cleanup_tab.py +0 -0
  67. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/settings_tabs/settings_2d_tab.py +0 -0
  68. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/settings_tabs/settings_3d_tabs.py +0 -0
  69. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/settings_tabs/settings_other_tab.py +0 -0
  70. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/settings_tabs/settings_tab_base.py +0 -0
  71. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/string_importers.py +0 -0
  72. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/template_preview_item.py +0 -0
  73. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/template_preview_view.py +0 -0
  74. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/translation_dialog.py +0 -0
  75. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/ui_manager.py +0 -0
  76. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/user_template_dialog.py +0 -0
  77. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/view_3d_logic.py +0 -0
  78. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/ui/zoomable_view.py +0 -0
  79. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/utils/__init__.py +0 -0
  80. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/utils/constants.py +0 -0
  81. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/utils/default_settings.py +0 -0
  82. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/utils/hit_radius.py +0 -0
  83. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/utils/sip_isdeleted_safe.py +0 -0
  84. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.0}/src/moleditpy_linux/utils/suppress_log.py +0 -0
  85. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.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: 4.9.3
3
+ Version: 4.10.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
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "MoleditPy-linux"
7
7
 
8
- version = "4.9.3"
8
+ version = "4.10.0"
9
9
 
10
10
  license = {file = "LICENSE"}
11
11
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy-linux
3
- Version: 4.9.3
3
+ Version: 4.10.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
@@ -14,6 +14,7 @@ from __future__ import annotations
14
14
  import ast
15
15
  import hashlib
16
16
  import importlib.util
17
+ import json
17
18
  import logging
18
19
  import os
19
20
  import shutil
@@ -80,6 +81,10 @@ class PluginManager:
80
81
  self.plugin_dir: str = os.path.join(
81
82
  os.path.expanduser("~"), ".moleditpy", "plugins"
82
83
  )
84
+ self.disabled_plugins_path: str = os.path.join(
85
+ os.path.expanduser("~"), ".moleditpy", "disabled_plugins.json"
86
+ )
87
+ self.disabled_plugins: set[str] = self._read_disabled_plugins()
83
88
  self.plugins: List[Dict[str, Any]] = [] # List of dicts
84
89
  self.main_window: Any = main_window
85
90
 
@@ -118,6 +123,42 @@ class PluginManager:
118
123
  except OSError as e:
119
124
  logging.warning(f"Error creating plugin directory: {e}")
120
125
 
126
+ def _read_disabled_plugins(self) -> set[str]:
127
+ """Read disabled plugin paths without making a malformed file fatal."""
128
+ try:
129
+ with open(self.disabled_plugins_path, "r", encoding="utf-8") as file:
130
+ data = json.load(file)
131
+ if isinstance(data, list):
132
+ values = data
133
+ elif isinstance(data, dict) and isinstance(
134
+ data.get("disabled_plugins"), list
135
+ ):
136
+ values = data["disabled_plugins"]
137
+ else:
138
+ values = []
139
+ return {str(value).replace("\\", "/") for value in values if value}
140
+ except (OSError, TypeError, ValueError, json.JSONDecodeError):
141
+ return set()
142
+
143
+ def save_disabled_plugins(self, disabled_paths: set[str]) -> None:
144
+ """Persist disabled plugin paths for the next application launch."""
145
+ normalized = sorted(path.replace("\\", "/") for path in disabled_paths if path)
146
+ try:
147
+ os.makedirs(os.path.dirname(self.disabled_plugins_path), exist_ok=True)
148
+ with open(self.disabled_plugins_path, "w", encoding="utf-8") as file:
149
+ json.dump(normalized, file, indent=2, ensure_ascii=False)
150
+ self.disabled_plugins = set(normalized)
151
+ except (OSError, TypeError, ValueError):
152
+ logging.warning("Unable to save disabled plugin list", exc_info=True)
153
+
154
+ def plugin_path_key(self, filepath: str) -> str:
155
+ """Return the stable, portable key used in the disabled-plugin file."""
156
+ return os.path.relpath(filepath, self.plugin_dir).replace("\\", "/")
157
+
158
+ def is_plugin_disabled(self, filepath: str) -> bool:
159
+ """Return whether *filepath* is disabled in the current settings."""
160
+ return self.plugin_path_key(filepath) in self.disabled_plugins
161
+
121
162
  def open_plugin_folder(self) -> None:
122
163
  """Opens the plugin directory in the OS file explorer."""
123
164
  self.ensure_plugin_dir()
@@ -237,6 +278,7 @@ class PluginManager:
237
278
  self.main_window = parent
238
279
 
239
280
  self.ensure_plugin_dir()
281
+ self.disabled_plugins = self._read_disabled_plugins()
240
282
  self.plugins = []
241
283
  # Clear registries
242
284
  self.menu_actions = []
@@ -276,7 +318,10 @@ class PluginManager:
276
318
  # Module name is the folder name
277
319
  module_name = os.path.basename(root)
278
320
 
279
- self._load_single_plugin(entry_point, module_name, category)
321
+ if self.is_plugin_disabled(entry_point):
322
+ self._register_disabled_plugin(entry_point, module_name, category)
323
+ else:
324
+ self._load_single_plugin(entry_point, module_name, category)
280
325
 
281
326
  else:
282
327
  # === Case 2: Category Folder (Load individual .py files) ===
@@ -290,10 +335,35 @@ class PluginManager:
290
335
  entry_point = os.path.join(root, filename)
291
336
  module_name = os.path.splitext(filename)[0]
292
337
 
293
- self._load_single_plugin(entry_point, module_name, category)
338
+ if self.is_plugin_disabled(entry_point):
339
+ self._register_disabled_plugin(
340
+ entry_point, module_name, category
341
+ )
342
+ else:
343
+ self._load_single_plugin(entry_point, module_name, category)
294
344
 
295
345
  return self.plugins
296
346
 
347
+ def _register_disabled_plugin(
348
+ self, filepath: str, module_name: str, category: str
349
+ ) -> None:
350
+ """Add a disabled plugin without importing or initializing its code."""
351
+ info = self.get_plugin_info_safe(filepath)
352
+ self.plugins.append(
353
+ {
354
+ "name": info.get("name", module_name),
355
+ "version": info.get("version", "Unknown"),
356
+ "author": info.get("author", "Unknown"),
357
+ "description": info.get("description", ""),
358
+ "module": None,
359
+ "category": category or info.get("category", ""),
360
+ "status": "Disabled",
361
+ "filepath": filepath,
362
+ "has_run": False,
363
+ "disabled": True,
364
+ }
365
+ )
366
+
297
367
  def _load_single_plugin(
298
368
  self, filepath: str, module_name: str, category: str
299
369
  ) -> None:
@@ -42,6 +42,7 @@ class PluginManagerWindow(QDialog):
42
42
  self.btn_remove: Any = None
43
43
  self.table: Any = None
44
44
  self.plugin_manager = plugin_manager
45
+ self._preferences_applied = False
45
46
  self.setWindowTitle("Plugin Manager")
46
47
  self.resize(800, 500)
47
48
  self.setAcceptDrops(True) # Enable drag & drop for the whole window
@@ -119,6 +120,12 @@ class PluginManagerWindow(QDialog):
119
120
  for row, p in enumerate(plugins):
120
121
  status_item = QTableWidgetItem(str(p.get("status", "Unknown")))
121
122
  status_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
123
+ status_item.setFlags(status_item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
124
+ status_item.setCheckState(
125
+ Qt.CheckState.Unchecked
126
+ if p.get("disabled", False)
127
+ else Qt.CheckState.Checked
128
+ )
122
129
  self.table.setItem(row, 0, status_item)
123
130
  self.table.setItem(row, 1, QTableWidgetItem(str(p.get("name", "Unknown"))))
124
131
  self.table.setItem(row, 2, QTableWidgetItem(str(p.get("version", ""))))
@@ -145,12 +152,46 @@ class PluginManagerWindow(QDialog):
145
152
  color = Qt.GlobalColor.red
146
153
  elif status == "Loaded":
147
154
  color = Qt.GlobalColor.darkGreen
148
- elif status == "No Entry Point":
155
+ elif status in ("No Entry Point", "Disabled"):
149
156
  color = Qt.GlobalColor.gray
150
157
 
151
158
  if color:
152
159
  self.table.item(row, 0).setForeground(color)
153
160
 
161
+ def _save_checkbox_preferences(self) -> set[str]:
162
+ """Collect and persist checkbox choices."""
163
+ disabled_paths = set()
164
+ for row, plugin in enumerate(self.plugin_manager.plugins):
165
+ status_item = self.table.item(row, 0)
166
+ if (
167
+ status_item is not None
168
+ and status_item.checkState() == Qt.CheckState.Unchecked
169
+ ):
170
+ filepath = plugin.get("filepath")
171
+ if filepath:
172
+ disabled_paths.add(self.plugin_manager.plugin_path_key(filepath))
173
+
174
+ self.plugin_manager.save_disabled_plugins(disabled_paths)
175
+ return disabled_paths
176
+
177
+ def _apply_plugin_preferences(self) -> None:
178
+ """Persist checkbox choices and reload plugins."""
179
+ if self._preferences_applied:
180
+ return
181
+ self._preferences_applied = True
182
+
183
+ self._save_checkbox_preferences()
184
+ if self.plugin_manager.main_window:
185
+ self.plugin_manager.discover_plugins(self.plugin_manager.main_window)
186
+ self.plugin_manager.rebuild_plugin_menus()
187
+ else:
188
+ self.plugin_manager.discover_plugins()
189
+
190
+ def done(self, result: int) -> None:
191
+ """Apply plugin preferences for every way the dialog can close."""
192
+ self._apply_plugin_preferences()
193
+ super().done(result)
194
+
154
195
  def update_button_state(self) -> None:
155
196
  """Enable or disable the Remove button based on table selection."""
156
197
  has_selection = self.table.currentRow() >= 0
@@ -159,6 +200,7 @@ class PluginManagerWindow(QDialog):
159
200
 
160
201
  def on_reload(self, silent: bool = False) -> None:
161
202
  """Reload all plugins from disk, rebuild main-window UI, and refresh the table."""
203
+ self._save_checkbox_preferences()
162
204
  if self.plugin_manager.main_window:
163
205
  self.plugin_manager.discover_plugins(self.plugin_manager.main_window)
164
206
  self.plugin_manager.rebuild_plugin_menus()
@@ -1663,7 +1663,6 @@ class MainInitManager:
1663
1663
 
1664
1664
  dlg = PluginManagerWindow(self.host.plugin_manager, self.host)
1665
1665
  dlg.exec()
1666
- self.plugin_menu_manager.update_plugin_menu(self.plugin_menu)
1667
1666
 
1668
1667
  manage_plugins_action.triggered.connect(show_plugin_manager)
1669
1668
  self.plugin_menu.addAction(manage_plugins_action)
@@ -428,7 +428,6 @@ class PluginMenuManager:
428
428
 
429
429
  dlg = PluginManagerWindow(self._im.host.plugin_manager, self._im.host)
430
430
  dlg.exec()
431
- self.update_plugin_menu(plugin_menu)
432
431
 
433
432
  def _clear_all_plugin_actions(self) -> None:
434
433
  """Remove all tagged plugin actions from every menu and the export button.